@ibgib/core-gib 0.1.8 → 0.1.10

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 (80) hide show
  1. package/dist/agent-helpers.d.mts +45 -0
  2. package/dist/agent-helpers.d.mts.map +1 -0
  3. package/dist/agent-helpers.mjs +36 -0
  4. package/dist/agent-helpers.mjs.map +1 -0
  5. package/dist/keystone/keystone-config-builder.respec.d.mts +2 -0
  6. package/dist/keystone/keystone-config-builder.respec.d.mts.map +1 -0
  7. package/dist/keystone/keystone-config-builder.respec.mjs +34 -0
  8. package/dist/keystone/keystone-config-builder.respec.mjs.map +1 -0
  9. package/dist/keystone/keystone-constants.d.mts +2 -0
  10. package/dist/keystone/keystone-constants.d.mts.map +1 -1
  11. package/dist/keystone/keystone-constants.mjs +2 -0
  12. package/dist/keystone/keystone-constants.mjs.map +1 -1
  13. package/dist/keystone/keystone-helpers.d.mts +54 -1
  14. package/dist/keystone/keystone-helpers.d.mts.map +1 -1
  15. package/dist/keystone/keystone-helpers.mjs +185 -1
  16. package/dist/keystone/keystone-helpers.mjs.map +1 -1
  17. package/dist/keystone/keystone-service-v1.d.mts +49 -16
  18. package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
  19. package/dist/keystone/keystone-service-v1.mjs +151 -328
  20. package/dist/keystone/keystone-service-v1.mjs.map +1 -1
  21. package/dist/keystone/keystone-service-v1.respec.mjs +401 -20
  22. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
  23. package/dist/keystone/keystone-types.d.mts +22 -0
  24. package/dist/keystone/keystone-types.d.mts.map +1 -1
  25. package/dist/sync/sync-constants.d.mts +17 -0
  26. package/dist/sync/sync-constants.d.mts.map +1 -0
  27. package/dist/sync/sync-constants.mjs +16 -0
  28. package/dist/sync/sync-constants.mjs.map +1 -0
  29. package/dist/sync/sync-helpers.d.mts +15 -0
  30. package/dist/sync/sync-helpers.d.mts.map +1 -0
  31. package/dist/sync/sync-helpers.mjs +46 -0
  32. package/dist/sync/sync-helpers.mjs.map +1 -0
  33. package/dist/sync/sync-local-spaces.respec.d.mts +2 -0
  34. package/dist/sync/sync-local-spaces.respec.d.mts.map +1 -0
  35. package/dist/sync/sync-local-spaces.respec.mjs +159 -0
  36. package/dist/sync/sync-local-spaces.respec.mjs.map +1 -0
  37. package/dist/sync/sync-saga-coordinator.d.mts +118 -0
  38. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -0
  39. package/dist/sync/sync-saga-coordinator.mjs +399 -0
  40. package/dist/sync/sync-saga-coordinator.mjs.map +1 -0
  41. package/dist/sync/sync-saga-coordinator.respec.d.mts +2 -0
  42. package/dist/sync/sync-saga-coordinator.respec.d.mts.map +1 -0
  43. package/dist/sync/sync-saga-coordinator.respec.mjs +40 -0
  44. package/dist/sync/sync-saga-coordinator.respec.mjs.map +1 -0
  45. package/dist/sync/sync-types.d.mts +103 -0
  46. package/dist/sync/sync-types.d.mts.map +1 -0
  47. package/dist/sync/sync-types.mjs +2 -0
  48. package/dist/sync/sync-types.mjs.map +1 -0
  49. package/dist/test/mock-space.d.mts +39 -0
  50. package/dist/test/mock-space.d.mts.map +1 -0
  51. package/dist/test/mock-space.mjs +79 -0
  52. package/dist/test/mock-space.mjs.map +1 -0
  53. package/dist/witness/space/inner-space/inner-space-v1.respec.mjs +163 -201
  54. package/dist/witness/space/inner-space/inner-space-v1.respec.mjs.map +1 -1
  55. package/dist/witness/space/space-helper.d.mts.map +1 -1
  56. package/dist/witness/space/space-helper.mjs +43 -4
  57. package/dist/witness/space/space-helper.mjs.map +1 -1
  58. package/dist/witness/space/space-helper.respec.d.mts +2 -0
  59. package/dist/witness/space/space-helper.respec.d.mts.map +1 -0
  60. package/dist/witness/space/space-helper.respec.mjs +30 -0
  61. package/dist/witness/space/space-helper.respec.mjs.map +1 -0
  62. package/package.json +2 -2
  63. package/src/agent-helpers.mts +58 -0
  64. package/src/keystone/keystone-config-builder.respec.mts +49 -0
  65. package/src/keystone/keystone-constants.mts +2 -0
  66. package/src/keystone/keystone-helpers.mts +211 -2
  67. package/src/keystone/keystone-service-v1.mts +183 -367
  68. package/src/keystone/keystone-service-v1.respec.mts +484 -21
  69. package/src/keystone/keystone-types.mts +24 -0
  70. package/src/sync/sync-constants.mts +24 -0
  71. package/src/sync/sync-helpers.mts +59 -0
  72. package/src/sync/sync-local-spaces.respec.mts +200 -0
  73. package/src/sync/sync-saga-coordinator.mts +477 -0
  74. package/src/sync/sync-saga-coordinator.respec.mts +52 -0
  75. package/src/sync/sync-types.mts +120 -0
  76. package/src/test/mock-space.mts +85 -0
  77. package/src/witness/space/inner-space/inner-space-v1.respec.mts +181 -228
  78. package/src/witness/space/space-helper.mts +42 -4
  79. package/src/witness/space/space-helper.respec.mts +42 -0
  80. package/tmp.md +11 -0
@@ -1,36 +1,20 @@
1
- import { extractErrorMsg, hash, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
1
+ import { extractErrorMsg, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
2
2
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
3
- import { mut8 } from '@ibgib/ts-gib/dist/V1/transforms/mut8.mjs';
4
- import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
5
- import { getGib, getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
6
- import { TransformResult } from '@ibgib/ts-gib/dist/types.mjs';
7
- import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
8
3
 
9
4
  import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
10
5
  import {
11
- KeystoneData_V1,
12
- KeystoneIbGib_V1,
13
- KeystonePoolConfig,
14
- KeystoneClaim,
15
- KeystoneProof,
16
- KeystoneChallengePool,
17
- KeystoneSolution,
18
- KeystoneReplenishStrategy,
19
- KeystoneRevocationInfo,
20
- KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES,
6
+ KeystoneData_V1, KeystoneIbGib_V1, KeystonePoolConfig, KeystoneClaim,
7
+ KeystoneChallengePool, KeystoneReplenishStrategy, KeystoneRevocationInfo,
21
8
  } from './keystone-types.mjs';
22
9
  import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
23
- import { KEYSTONE_ATOM, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE } from './keystone-constants.mjs';
10
+ import { POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE } from './keystone-constants.mjs';
24
11
  import {
25
- addToBindingMap, getDeterministicRequirements, getKeystoneIb,
26
- removeFromBindingMap,
27
- validateKeystoneTransition,
28
- verifyProofAgainstPool,
12
+ addToBindingMap, createKeystoneIbGibImpl, evolvePersistAndRegisterKeystone,
13
+ generateOpaqueChallengeId, resolveTargetPool, selectChallengeIds,
14
+ solveAndReplenish, validateKeystoneTransition,
29
15
  } from './keystone-helpers.mjs';
30
- import { getDependencyGraph } from '../common/other/graph-helper.mjs';
31
16
  import { IbGibSpaceAny } from '../witness/space/space-base-v1.mjs';
32
17
  import { MetaspaceService } from '../witness/space/metaspace/metaspace-types.mjs';
33
- import { IbGibSpaceOptionsCmd } from '../witness/space/space-types.mjs';
34
18
 
35
19
  const logalot = GLOBAL_LOG_A_LOT || true;
36
20
 
@@ -72,7 +56,7 @@ export class KeystoneService_V1 {
72
56
  const timestamp = Date.now().toString();
73
57
 
74
58
  for (let i = 0; i < targetSize; i++) {
75
- const challengeId = await this.generateOpaqueChallengeId({
59
+ const challengeId = await generateOpaqueChallengeId({
76
60
  salt: config.salt, timestamp, index: i
77
61
  });
78
62
 
@@ -94,8 +78,10 @@ export class KeystoneService_V1 {
94
78
  });
95
79
  }
96
80
 
81
+ if (challengePools.length === 0) { throw new Error(`No challenge pools created. (E: 38e538530996940e1f16a8b199995825)`); }
82
+
97
83
  const data: KeystoneData_V1 = { challengePools, proofs: [] };
98
- const keystoneIbGib = await this.createKeystoneIbGibImpl({ data, metaspace, space });
84
+ const keystoneIbGib = await createKeystoneIbGibImpl({ data, metaspace, space });
99
85
  return keystoneIbGib;
100
86
  } catch (error) {
101
87
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -106,23 +92,41 @@ export class KeystoneService_V1 {
106
92
  }
107
93
 
108
94
  /**
109
- * Signs a claim using a hybrid selection strategy.
110
- * Supports: Mandatory IDs (Alice) + Sequential (FIFO) + Random (Stochastic).
95
+ * Signs a claim by solving challenges from a specific pool and evolving the Keystone timeline.
96
+ *
97
+ * Uses a hybrid selection strategy: Mandatory IDs (Alice) + Sequential (FIFO) + Random (Stochastic).
98
+ *
99
+ * Supports Delegation via `poolFilter` to find specific foreign pools.
111
100
  */
112
101
  async sign({
113
102
  latestKeystone,
114
103
  masterSecret,
115
104
  claim,
116
105
  poolId,
106
+ poolFilter,
117
107
  requiredChallengeIds = [],
118
108
  frameDetails,
119
109
  metaspace,
120
110
  space,
121
111
  }: {
122
112
  latestKeystone: KeystoneIbGib_V1;
113
+ /**
114
+ * The secret used to solve the challenges.
115
+ * If signing with a native pool, this is the User's Master Secret.
116
+ * If signing with a foreign/delegated pool, this is the Delegate's Secret.
117
+ */
123
118
  masterSecret: string;
124
119
  claim: Partial<KeystoneClaim>;
120
+ /**
121
+ * Explicit ID of the pool to use.
122
+ */
125
123
  poolId?: string;
124
+ /**
125
+ * Optional predicate to find a pool.
126
+ * Useful for finding delegates via metadata without knowing the exact ID.
127
+ * e.g. (p) => p.metadata?.delegate === 'Bob'
128
+ */
129
+ poolFilter?: (pool: KeystoneChallengePool) => boolean;
126
130
  requiredChallengeIds?: string[];
127
131
  frameDetails?: any;
128
132
  metaspace: MetaspaceService;
@@ -130,117 +134,58 @@ export class KeystoneService_V1 {
130
134
  }): Promise<KeystoneIbGib_V1> {
131
135
  const lc = `${this.lc}[${this.sign.name}]`;
132
136
  try {
133
- if (logalot) { console.log(`${lc} starting... (I: 519e0810cce8647ce83bdb3b5019a825)`); }
137
+ if (logalot) { console.log(`${lc} starting...`); }
134
138
 
135
139
  const prevData = latestKeystone.data!;
136
- if (prevData.revocationInfo) { throw new Error(`keystone has been revoked (latestKeystone.data.revocationInfo is truthy). (E: 4f2198c39116d15c48ba191940316825)`); }
137
-
138
- let pool: KeystoneChallengePool | undefined;
139
- const poolsToSearch = prevData.challengePools;
140
- if (logalot) { console.log(`${lc} Searching pools: ${poolsToSearch.map(p => p.id).join(', ')} for target: ${poolId || 'auto'}`); }
141
-
142
- // ---------------------------------------------------------------
143
- // 0. POOL RESOLUTION (Deterministic Mapping)
144
- // ---------------------------------------------------------------
145
- if (poolId) {
146
- // Explicit selection
147
- pool = poolsToSearch.find(p => p.id === poolId);
148
- if (!pool) { throw new Error(`Pool not found: ${poolId} (E: genuuid)`); }
149
-
150
- // Enforce Policy on Explicit Selection
151
- // FIX: Check length > 0. If empty, it's a default pool (allow all).
152
- if (pool.config.allowedVerbs && pool.config.allowedVerbs.length > 0 && claim.verb) {
153
- if (!pool.config.allowedVerbs.includes(claim.verb)) {
154
- throw new Error(`Pool ${poolId} is not authorized for verb: ${claim.verb} (E: genuuid)`);
155
- }
156
- }
157
- } else {
158
- // Automatic Resolution based on Verb
159
- if (!claim.verb) { throw new Error(`Cannot auto-resolve pool without a verb in the claim. (E: genuuid)`); }
160
-
161
- // 1. Look for Specific Match
162
- pool = poolsToSearch.find(p =>
163
- p.config.allowedVerbs && p.config.allowedVerbs.includes(claim.verb!)
164
- );
165
-
166
- // 2. Look for General/Default (No restrictions)
167
- if (!pool) {
168
- pool = poolsToSearch.find(p =>
169
- !p.config.allowedVerbs || p.config.allowedVerbs.length === 0
170
- );
171
- }
172
-
173
- if (!pool) { throw new Error(`No suitable pool found for verb: ${claim.verb} (E: genuuid)`); }
174
- }
175
140
 
141
+ if (prevData.revocationInfo) { throw new Error(`Keystone has been revoked. Cannot sign. (E: 4f2198c39116d15c48ba191940316825)`); }
176
142
 
177
- // 1. Get Deterministic Requirements (Demands -> Binding -> FIFO)
178
- const { mandatoryIds, availableIds } = getDeterministicRequirements({
179
- pool,
180
- requiredChallengeIds,
181
- targetAddr: claim.target
143
+ // 1. Identify Authority (Resolve Pool)
144
+ const pool = resolveTargetPool({
145
+ pools: prevData.challengePools,
146
+ poolId,
147
+ poolFilter,
148
+ verb: claim.verb
182
149
  });
183
150
 
184
- // 2. Stochastic Selection
185
- const randomCount = pool.config.behavior.selectRandomly;
186
- const randomIds: string[] = [];
187
-
188
- if (logalot) {
189
- console.log(`${lc} Pool Info: id=${pool.id} invalid=${!pool} size=${Object.keys(pool.challenges || {}).length}`);
190
- console.log(`${lc} Behavior: seq=${pool.config.behavior.selectSequentially} rand=${randomCount} binding=${pool.config.behavior.targetBindingChars}`);
191
- console.log(`${lc} Requirements: mandatory=${mandatoryIds.size} available=${availableIds.length}`);
192
- }
193
-
194
- if (randomCount > 0) {
195
- if (availableIds.length < randomCount) {
196
- throw new Error(`Insufficient challenges for random requirement. Need ${randomCount}, have ${availableIds.length}`);
197
- }
198
- // Shuffle & Pick
199
- const shuffled = availableIds.sort(() => 0.5 - Math.random());
200
- randomIds.push(...shuffled.slice(0, randomCount));
201
- }
202
-
203
- // 3. Combine & Solve
204
- const finalSelectedIds = [...mandatoryIds, ...randomIds];
205
-
206
- const strategy = KeystoneStrategyFactory.create({ config: pool.config });
207
- const secretToUse = masterSecret;
208
- const poolSecret = await strategy.derivePoolSecret({ masterSecret: secretToUse });
209
- const solutions: KeystoneSolution[] = [];
151
+ if (logalot) { console.log(`${lc} Selected pool: ${pool.id} (size: ${Object.keys(pool.challenges).length}) (I: genuuid)`); }
210
152
 
211
- for (const id of finalSelectedIds) {
212
- const solution = await strategy.generateSolution({
213
- poolSecret, poolId: pool.id, challengeId: id,
214
- });
215
- solutions.push(solution);
216
- }
217
-
218
- // 4. Replenish
219
- const nextPools = await this.applyReplenishmentStrategy({
220
- prevPools: poolsToSearch,
221
- targetPoolId: pool.id,
222
- consumedIds: finalSelectedIds,
223
- masterSecret: secretToUse,
224
- strategy,
225
- config: pool.config
153
+ // 2. Calculate Costs (Select IDs)
154
+ const idsToSolve = selectChallengeIds({
155
+ pool,
156
+ targetAddr: claim.target,
157
+ requiredChallengeIds
226
158
  });
227
159
 
228
- // 5. Build Proof (Include requiredChallengeIds!)
229
- const proof: KeystoneProof = {
160
+ // 3. Pay the Cost (Solve & Replenish)
161
+ // This helper handles the Strategy creation, Secret derivation, Solving,
162
+ // and the calculation of the Next state for ALL pools.
163
+ const { proof, nextPools } = await solveAndReplenish({
164
+ targetPoolId: pool.id,
165
+ prevPools: prevData.challengePools,
166
+ masterSecret,
167
+ challengeIds: idsToSolve,
230
168
  claim,
231
- solutions,
232
- requiredChallengeIds: requiredChallengeIds.length > 0 ? requiredChallengeIds : undefined
233
- };
169
+ requiredChallengeIds
170
+ });
234
171
 
172
+ // 4. Construct New Data
235
173
  const newData: KeystoneData_V1 = {
236
174
  challengePools: nextPools,
237
175
  proofs: [proof],
238
176
  frameDetails,
177
+ // Revocation info is undefined for a standard sign operation
239
178
  };
240
179
 
241
- const newIbGib = await this.evolveKeystoneIbGibImpl({ prevIbGib: latestKeystone, newData, metaspace, space });
242
- return newIbGib;
180
+ // 5. Commit (Evolve, Persist, Register)
181
+ const resKeystone = await evolvePersistAndRegisterKeystone({
182
+ prevIbGib: latestKeystone,
183
+ newData,
184
+ metaspace,
185
+ space
186
+ });
243
187
 
188
+ return resKeystone;
244
189
  } catch (error) {
245
190
  console.error(`${lc} ${extractErrorMsg(error)}`);
246
191
  throw error;
@@ -278,8 +223,9 @@ export class KeystoneService_V1 {
278
223
  *
279
224
  * Logic:
280
225
  * 1. Locates the 'revoke' pool.
281
- * 2. Consumes ALL available challenges in that pool (Scorched Earth).
282
- * 3. Sets the revocationInfo on the new frame.
226
+ * 2. Solves required challenges to prove ownership.
227
+ * 3. Wipes the pool (via 'scorched-earth' strategy in solveAndReplenish).
228
+ * 4. Sets the revocationInfo on the new frame.
283
229
  */
284
230
  async revoke({
285
231
  latestKeystone,
@@ -294,72 +240,56 @@ export class KeystoneService_V1 {
294
240
  reason?: string;
295
241
  frameDetails?: any;
296
242
  metaspace: MetaspaceService;
297
- space?: IbGibSpaceAny;
243
+ space: IbGibSpaceAny;
298
244
  }): Promise<KeystoneIbGib_V1> {
299
245
  const lc = `${this.lc}[${this.revoke.name}]`;
300
246
  try {
247
+ if (logalot) { console.log(`${lc} starting...`); }
248
+
301
249
  const prevData = latestKeystone.data!;
302
- space ??= await metaspace.getLocalUserSpace({ lock: false });
303
- if (!space) { throw new Error(`(UNEXPECTED) space falsy and couldn't get default local user space from the metaspace? (E: 73c8bfc0e7383a540ea1d6b14b020125)`); }
304
250
 
305
- // 1. Find Revocation Pool
306
- const pool = prevData.challengePools.find(p => p.id === POOL_ID_REVOKE);
307
- if (!pool) { throw new Error(`Revocation pool not found (E: 8c4f18c5461c1d601283108878c79825)`); }
251
+ // 1. Identify Authority (Resolve Revoke Pool)
252
+ const pool = resolveTargetPool({
253
+ pools: prevData.challengePools,
254
+ poolId: POOL_ID_REVOKE // Explicitly require the special revoke pool
255
+ });
308
256
 
309
- // 2. Select Challenges (Standard Policy, NOT ALL)
257
+ // 2. Construct Claim
310
258
  const claim: Partial<KeystoneClaim> = {
311
259
  verb: KEYSTONE_VERB_REVOKE,
312
260
  target: getIbGibAddr({ ibGib: latestKeystone })
313
261
  };
314
262
 
315
- const { mandatoryIds, availableIds } = getDeterministicRequirements({
263
+ // 3. Calculate Costs
264
+ const idsToSolve = selectChallengeIds({
316
265
  pool,
317
- requiredChallengeIds: [], // Revocation usually doesn't have external demands
318
- targetAddr: claim.target
266
+ targetAddr: claim.target,
267
+ requiredChallengeIds: []
319
268
  });
320
269
 
321
- // Stochastic Selection
322
- const randomCount = pool.config.behavior.selectRandomly;
323
- const randomIds: string[] = [];
324
- if (randomCount > 0) {
325
- if (availableIds.length < randomCount) { throw new Error(`Insufficient challenges. availableIds.length (${availableIds.length}) is less than required random count (${randomCount}) (E: b2e3570ab998dfdbab5fbdda1e43d825)`); }
326
- const shuffled = availableIds.sort(() => 0.5 - Math.random());
327
- randomIds.push(...shuffled.slice(0, randomCount));
328
- }
270
+ if (idsToSolve.length === 0) { throw new Error(`Revocation policy selected 0 challenges? Check config for pool ${pool.id}. Revocation requires proof. (E: 97e5a8356d241ae7b882db791cb1f825)`); }
329
271
 
330
- const selectedIds = [...mandatoryIds, ...randomIds];
331
- if (selectedIds.length === 0) { throw new Error(`Revocation policy selected 0 challenges? Check config for pool. id: ${pool.id}. pool.config: ${pretty(pool.config)} (E: 97e5a8356d241ae7b882db791cb1f825)`); }
332
-
333
- // 3. Solve Selected
334
- const strategy = KeystoneStrategyFactory.create({ config: pool.config });
335
- const poolSecret = await strategy.derivePoolSecret({ masterSecret });
336
- const solutions: KeystoneSolution[] = [];
337
-
338
- for (const id of selectedIds) {
339
- const solution = await strategy.generateSolution({
340
- poolSecret, poolId: pool.id, challengeId: id,
341
- });
342
- solutions.push(solution);
343
- }
344
-
345
- // 4. Replenish (Scorched Earth)
346
- const nextPools = await this.applyReplenishmentStrategy({
347
- prevPools: prevData.challengePools,
272
+ // 4. Pay the Cost & Scorched Earth
273
+ // The revoke pool config should have 'replenish: scorched-earth',
274
+ // causing solveAndReplenish to return an empty pool in nextPools.
275
+ const { proof, nextPools } = await solveAndReplenish({
348
276
  targetPoolId: pool.id,
349
- consumedIds: selectedIds,
277
+ prevPools: prevData.challengePools,
350
278
  masterSecret,
351
- strategy,
352
- config: pool.config
353
- });
354
-
355
- // 5. Construct Proof
356
- const proof: KeystoneProof = {
279
+ challengeIds: idsToSolve,
357
280
  claim,
358
- solutions,
359
- };
281
+ requiredChallengeIds: []
282
+ });
283
+ // warn if nextPools contains pool.id that isn't empty (we were
284
+ // supposed to do "scorched earth" which empties the pool)
285
+ if (nextPools.find(p => p.id === pool.id && Object.keys(p.challenges).length > 0)) {
286
+ console.warn(`${lc} revocation pool ${pool.id} is not empty after revocation. Is the revocation pool replenish strategy set to ${KeystoneReplenishStrategy.scorchedEarth}? (W: 300c28bc8b98fc3e3c0b0d988344f825)`);
287
+ }
360
288
 
289
+ // 5. Construct Revocation Info
361
290
  const revocationInfo: KeystoneRevocationInfo = { reason, proof };
362
291
 
292
+ // 6. Construct New Data
363
293
  const newData: KeystoneData_V1 = {
364
294
  challengePools: nextPools,
365
295
  proofs: [proof],
@@ -367,13 +297,15 @@ export class KeystoneService_V1 {
367
297
  frameDetails
368
298
  };
369
299
 
370
- return await this.evolveKeystoneIbGibImpl({
300
+ // 7. Commit
301
+ const newKeystone = await evolvePersistAndRegisterKeystone({
371
302
  prevIbGib: latestKeystone,
372
303
  newData,
373
304
  metaspace,
374
- space,
305
+ space
375
306
  });
376
307
 
308
+ return newKeystone;
377
309
  } catch (error) {
378
310
  console.error(`${lc} ${extractErrorMsg(error)}`);
379
311
  throw error;
@@ -383,224 +315,108 @@ export class KeystoneService_V1 {
383
315
  }
384
316
 
385
317
  /**
386
- * Generates an opaque, collision-resistant ID for a challenge.
387
- * atow (2025/12/22) - Hash(Salt + Timestamp + Index)
318
+ * Structural evolution: Adds new challenge pools to the keystone.
319
+ *
320
+ * Use Case: Adding a delegate (Server) for SSO, adding a recovery key,
321
+ * or rotating to a new set of pools.
322
+ *
323
+ * Requires the Master Secret to authorize the change via a pool containing
324
+ * the 'manage' verb.
388
325
  */
389
- private async generateOpaqueChallengeId({
390
- salt,
391
- timestamp,
392
- index
393
- }: {
394
- salt: string,
395
- timestamp: string,
396
- index: number
397
- }): Promise<string> {
398
- // Use full SHA-256 hash, or slice it?
399
- // User suggested substring is fine for pool uniqueness.
400
- // Let's use first 16 chars of hex (64 bits) for brevity + safety.
401
- const raw = await hash({ s: `${salt}${timestamp}${index}` });
402
- return raw.substring(0, 16);
403
- }
404
-
405
- private async applyReplenishmentStrategy({
406
- prevPools,
407
- targetPoolId,
408
- consumedIds,
326
+ async addPools({
327
+ latestKeystone,
409
328
  masterSecret,
410
- strategy,
411
- config
412
- }: {
413
- prevPools: KeystoneChallengePool[];
414
- targetPoolId: string;
415
- consumedIds: string[];
416
- masterSecret: string;
417
- strategy: any;
418
- config: KeystonePoolConfig;
419
- }): Promise<KeystoneChallengePool[]> {
420
- const newPools = JSON.parse(JSON.stringify(prevPools));
421
- if (logalot) { console.log(`[applyReplenishmentStrategy] Cloned pools. Ref check: ${prevPools === newPools} (should be false)`); }
422
- const targetIdx = newPools.findIndex((p: any) => p.id === targetPoolId);
423
- if (targetIdx === -1) { throw new Error(`Target pool ${targetPoolId} not found in keytstone data. (E: 75200388d22744838634524233772545)`); }
424
- const pool = newPools[targetIdx];
425
-
426
- const poolSecret = await strategy.derivePoolSecret({ masterSecret });
427
- const timestamp = Date.now().toString();
428
-
429
- const strategyType = config.behavior.replenish;
430
-
431
- // Clean up Binding Map for consumed IDs
432
- consumedIds.forEach(id => {
433
- if (pool.bindingMap) { removeFromBindingMap(pool.bindingMap, id); }
434
- });
435
-
436
- if (strategyType === KeystoneReplenishStrategy.topUp) {
437
- // Remove consumed
438
- consumedIds.forEach(id => delete pool.challenges[id]);
439
-
440
- // Add New
441
- for (let i = 0; i < consumedIds.length; i++) {
442
- const newId = await this.generateOpaqueChallengeId({
443
- salt: config.salt, timestamp, index: i
444
- });
445
-
446
- const solution = await strategy.generateSolution({
447
- poolSecret, poolId: pool.id, challengeId: newId
448
- });
449
- pool.challenges[newId] = await strategy.generateChallenge({ solution });
450
-
451
- // Update Binding Map
452
- if (!pool.bindingMap) { pool.bindingMap = {}; }
453
- addToBindingMap(pool.bindingMap, newId);
454
- }
455
- } else if (strategyType === KeystoneReplenishStrategy.replaceAll) {
456
- pool.challenges = {};
457
- pool.bindingMap = {};
458
-
459
- for (let i = 0; i < config.behavior.size; i++) {
460
- const newId = await this.generateOpaqueChallengeId({
461
- salt: config.salt, timestamp, index: i
462
- });
463
- const solution = await strategy.generateSolution({
464
- poolSecret, poolId: pool.id, challengeId: newId
465
- });
466
- pool.challenges[newId] = await strategy.generateChallenge({ solution });
467
- addToBindingMap(pool.bindingMap, newId);
468
- }
469
- } else if (strategyType === KeystoneReplenishStrategy.consume) {
470
- consumedIds.forEach(id => delete pool.challenges[id]);
471
- } else if (strategyType === KeystoneReplenishStrategy.scorchedEarth) {
472
- pool.challenges = {};
473
- pool.bindingMap = {};
474
- } else {
475
- throw new Error(`Unknown replenish strategy: ${strategyType}. Valid list: ${pretty(KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES)} (E: 0acf56f1e1486240080e11e8046d0825)`);
476
- }
477
-
478
- return newPools;
479
- }
480
-
481
- /**
482
- * Creates a new keystone ibgib that has no dna and no past.
483
- */
484
- private async createKeystoneIbGibImpl({
485
- data,
329
+ newPools,
486
330
  metaspace,
487
331
  space,
488
332
  }: {
489
- data: KeystoneData_V1,
490
- metaspace: MetaspaceService,
491
- space?: IbGibSpaceAny,
333
+ latestKeystone: KeystoneIbGib_V1;
334
+ /**
335
+ * Alice's Master Secret.
336
+ * Required to solve challenges from the Admin/Manage pool to authorize this change.
337
+ */
338
+ masterSecret: string;
339
+ /**
340
+ * The pools to add.
341
+ * NOTE: These are fully constructed Pool objects.
342
+ * If they are foreign (Bob's), Alice must have constructed them
343
+ * using Bob's challenges + Her config restrictions + isForeign=true.
344
+ */
345
+ newPools: KeystoneChallengePool[];
346
+ metaspace: MetaspaceService;
347
+ space: IbGibSpaceAny;
492
348
  }): Promise<KeystoneIbGib_V1> {
493
- const lc = `${this.lc}[${this.createKeystoneIbGibImpl.name}]`;
349
+ const lc = `${this.lc}[${this.addPools.name}]`;
494
350
  try {
495
- if (logalot) { console.log(`${lc} starting... (I: 5e32389700e9899e788cbefacef7c825)`); }
496
-
497
- space ??= await metaspace.getLocalUserSpace({ lock: false });
498
- if (!space) { throw new Error(`(UNEXPECTED) space was falsy and we couldn't get default local user space from metaspace? (E: 9a6498cf16a8801f19ec376749742225)`); }
499
-
500
- // create the actual keystoneIbGib
501
- const resFirstGen = await Factory_V1.firstGen({
502
- parentIbGib: Factory_V1.primitive({ ib: KEYSTONE_ATOM }),
503
- ib: await getKeystoneIb({ keystoneData: data }),
504
- data,
505
- dna: false,
506
- nCounter: true,
507
- tjp: {
508
- timestamp: true,
509
- uuid: true,
510
- },
511
- }) as TransformResult<KeystoneIbGib_V1>;
512
- const keystoneIbGib = resFirstGen.newIbGib;
513
-
514
- if (!keystoneIbGib.data) { throw new Error(`(UNEXPECTED) keystoneIbGib.data falsy? We expect the data to be populated with real keystone data. (E: 38a358facdb89d16d81d48c8520d3d25)`); }
515
- if (!keystoneIbGib.rel8ns) { throw new Error(`(UNEXPECTED) keystoneIbGib.rel8ns falsy? we expect the rel8ns to have ancestor and past. (E: 20cb7723dc33ae1ef808fe76d1bf4b25)`); }
516
- if (!keystoneIbGib.rel8ns.past || keystoneIbGib.rel8ns.past.length === 0) {
517
- throw new Error(`(UNEXPECTED) keystoneIbGib.rel8ns.past falsy or empty? we expect the firstGen call to generate an interstitial ibgib that we will splice out. (E: 0fd8388d045ab9f37834c27d67e78825)`);
518
- }
519
-
520
- // reset n
521
- keystoneIbGib.data.n = 0;
522
- // reset tjp
523
- keystoneIbGib.data.isTjp = true;
524
- delete keystoneIbGib.rel8ns.tjp;
525
- // reset past
526
- delete keystoneIbGib.rel8ns.past;
351
+ if (logalot) { console.log(`${lc} starting...`); }
527
352
 
528
- // recalculate gib
529
- keystoneIbGib.gib = await getGib({ ibGib: keystoneIbGib });
353
+ if (!latestKeystone.data) { throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 7334c8faed128166a999d428c7805b25)`); }
354
+ const prevData = latestKeystone.data;
530
355
 
531
- // save and register
532
- await metaspace.put({ ibGib: keystoneIbGib, space, });
533
- await metaspace.registerNewIbGib({ ibGib: keystoneIbGib, space, });
356
+ if (prevData.revocationInfo) { throw new Error(`Keystone has been revoked. Cannot add pools. (E: 8599f8f51c78d722252ddb2894fdbe25)`); }
534
357
 
535
- return keystoneIbGib;
536
- } catch (error) {
537
- console.error(`${lc} ${extractErrorMsg(error)}`);
538
- throw error;
539
- } finally {
540
- if (logalot) { console.log(`${lc} complete.`); }
541
- }
542
- }
358
+ if (newPools.length === 0) { throw new Error(`No new pools provided to add. (E: 6599f8f51c78d722252ddb2894fdbe25)`); }
543
359
 
544
- private async evolveKeystoneIbGibImpl({
545
- prevIbGib,
546
- newData,
547
- metaspace,
548
- space,
549
- }: {
550
- prevIbGib: KeystoneIbGib_V1,
551
- newData: KeystoneData_V1
552
- metaspace: MetaspaceService,
553
- space: IbGibSpaceAny,
554
- }): Promise<KeystoneIbGib_V1> {
555
- const lc = `${this.lc}[${this.evolveKeystoneIbGibImpl.name}]`;
556
- try {
557
- if (logalot) { console.log(`${lc} starting... (I: 8b10e8920f08b7842803665834cf8925)`); }
558
-
559
- if (!prevIbGib.data) { throw new Error(`(UNEXPECTED) prevIbGib.data falsy? (E: 5e84875bf992c585b979e6c8ed5bf225)`); }
560
- if (prevIbGib.data.revocationInfo) { throw new Error(`Keystone has already been revoked (prevIbGib.data.revocationInfo truthy), so we cannot evolve the keystone. Keystone addr: ${getIbGibAddr({ ibGib: prevIbGib })} (E: 45d7f846556829de6b2a701838c3f825)`); }
561
-
562
- const prevData = prevIbGib.data;
563
- /**
564
- * we want to completely replace these keys, so we will remove them
565
- * from the data. This occurs first in the underlying mut8
566
- * transform.
567
- * @see {@link mut8}
568
- */
569
- let dataToRemove: Partial<KeystoneData_V1> | undefined = {}
570
- if (prevData.proofs) { dataToRemove.proofs = []; }
571
- if (prevData.challengePools) { dataToRemove.challengePools = []; }
572
- if (prevData.frameDetails) { dataToRemove.frameDetails = {}; }
573
- if (Object.keys(dataToRemove).length === 0) { dataToRemove = undefined; }
574
-
575
- const resMut8 = await mut8({
576
- src: prevIbGib,
577
- dataToRemove,
578
- dataToAddOrPatch: newData,
579
- // dna: false, // explicitly set to false just to show
580
- nCounter: true,
360
+ // 1. Identify Authority (Resolve Admin Pool)
361
+ // We need a pool that allows the 'manage' verb.
362
+ const adminPool = resolveTargetPool({
363
+ pools: prevData.challengePools,
364
+ verb: KEYSTONE_VERB_MANAGE,
581
365
  });
582
366
 
583
- if (!!resMut8.intermediateIbGibs) { throw new Error(`(UNEXPECTED) resMut8.intermediateIbGibs truthy? I'm not sure if we expect there to be intermediateIbGibs, but I feel like we shouldn't. Pretty sure we shouldn't, definitely don't *want* them. (E: ba40d55d7c2d36d438c413886f148625)`); }
584
- if (!!resMut8.dnas) { throw new Error(`(UNEXPECTED) resMut8.dnas truthy? We do not want dnas with keystones. (E: 49470513d018f97d28024f4e82da3b25)`); }
367
+ if (logalot) { console.log(`${lc} Authorized via pool: ${adminPool.id}`); }
585
368
 
369
+ // 2. Construct the Management Claim
370
+ const target = getIbGibAddr({ ibGib: latestKeystone });
371
+ const claim: Partial<KeystoneClaim> = {
372
+ verb: KEYSTONE_VERB_MANAGE,
373
+ target, // I am managing myself
374
+ // Scope creates a cryptographic commitment to WHICH pools are being added
375
+ scope: JSON.stringify({ add: newPools.map(p => p.id) })
376
+ };
586
377
 
587
- const newKeystoneIbGib = resMut8.newIbGib as KeystoneIbGib_V1;
378
+ // 3. Calculate Costs
379
+ const idsToSolve = selectChallengeIds({
380
+ pool: adminPool,
381
+ targetAddr: target
382
+ });
588
383
 
589
- // run validation here
590
- const errors = await this.validate({
591
- currentIbGib: newKeystoneIbGib,
592
- prevIbGib,
384
+ // 4. Pay the Cost (Solve & Replenish)
385
+ // This authorizes the change by evolving the admin pool.
386
+ const { proof, nextPools: replenishedExistingPools } = await solveAndReplenish({
387
+ targetPoolId: adminPool.id,
388
+ prevPools: prevData.challengePools,
389
+ masterSecret,
390
+ challengeIds: idsToSolve,
391
+ claim,
593
392
  });
594
- if (errors.length > 0) {
595
- console.error(`${lc} Validation Failed:\n${errors.join('\n')}`);
596
- throw new Error(`(UNEXPECTED) invalid keystone after we just evolved it? Errors: ${errors.join('; ')} (E: ae2c58406c1db7687879dfb89fc1f825)`);
393
+
394
+ // 5. Mutate Structure (Add the new pools)
395
+ // We verify ID uniqueness cheaply here to prevent blatant errors
396
+ const existingIds = new Set(replenishedExistingPools.map(p => p.id));
397
+ for (const newPool of newPools) {
398
+ if (existingIds.has(newPool.id)) {
399
+ throw new Error(`Cannot add pool. ID collision: ${newPool.id} (E: 8a4c2b1d3e5f6a7b8c9d0e1f2a3b4c5d)`);
400
+ }
597
401
  }
598
402
 
599
- // save and register
600
- await metaspace.put({ ibGib: newKeystoneIbGib, space, });
601
- await metaspace.registerNewIbGib({ ibGib: newKeystoneIbGib, space, });
403
+ const finalPools = [...replenishedExistingPools, ...newPools];
404
+
405
+ // 6. Construct New Data
406
+ const newData: KeystoneData_V1 = {
407
+ challengePools: finalPools,
408
+ proofs: [proof], // The proof authorizes the structure change
409
+ };
410
+
411
+ // 7. Commit
412
+ const newKeystone = await evolvePersistAndRegisterKeystone({
413
+ prevIbGib: latestKeystone,
414
+ newData,
415
+ metaspace,
416
+ space
417
+ });
602
418
 
603
- return newKeystoneIbGib;
419
+ return newKeystone;
604
420
  } catch (error) {
605
421
  console.error(`${lc} ${extractErrorMsg(error)}`);
606
422
  throw error;