@ibgib/core-gib 0.1.63 → 0.1.65

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 (32) hide show
  1. package/dist/keystone/keystone-constants.d.mts +1 -0
  2. package/dist/keystone/keystone-constants.d.mts.map +1 -1
  3. package/dist/keystone/keystone-constants.mjs +1 -0
  4. package/dist/keystone/keystone-constants.mjs.map +1 -1
  5. package/dist/keystone/keystone-helpers.d.mts +27 -1
  6. package/dist/keystone/keystone-helpers.d.mts.map +1 -1
  7. package/dist/keystone/keystone-helpers.mjs +261 -16
  8. package/dist/keystone/keystone-helpers.mjs.map +1 -1
  9. package/dist/keystone/keystone-service-v1.d.mts +113 -1
  10. package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
  11. package/dist/keystone/keystone-service-v1.mjs +442 -27
  12. package/dist/keystone/keystone-service-v1.mjs.map +1 -1
  13. package/dist/keystone/keystone-service-v1.respec.mjs +641 -47
  14. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
  15. package/dist/keystone/keystone-types.d.mts +31 -2
  16. package/dist/keystone/keystone-types.d.mts.map +1 -1
  17. package/dist/keystone/keystone-types.mjs +15 -0
  18. package/dist/keystone/keystone-types.mjs.map +1 -1
  19. package/dist/keystone/policy/profiles/profile-domain.json +2 -61
  20. package/dist/keystone/policy/profiles/profile-sync.json +2 -16
  21. package/dist/keystone/policy/schemas/keystone.high.schema.json +1 -1
  22. package/dist/keystone/policy/schemas/keystone.medium.schema.json +1 -1
  23. package/package.json +2 -2
  24. package/src/keystone/keystone-constants.mts +1 -0
  25. package/src/keystone/keystone-helpers.mts +308 -15
  26. package/src/keystone/keystone-service-v1.mts +535 -28
  27. package/src/keystone/keystone-service-v1.respec.mts +2130 -1459
  28. package/src/keystone/keystone-types.mts +50 -2
  29. package/src/keystone/policy/profiles/profile-domain.json +3 -62
  30. package/src/keystone/policy/profiles/profile-sync.json +2 -16
  31. package/src/keystone/policy/schemas/keystone.high.schema.json +1 -1
  32. package/src/keystone/policy/schemas/keystone.medium.schema.json +1 -1
@@ -1,13 +1,14 @@
1
- import { respecfully, iReckon, ifWe, firstOfAll, firstOfEach } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
1
+ import { respecfully, iReckon, firstOfAll, firstOfEach, ifWeMight } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
2
2
  const maam = `[${import.meta.url}]`, sir = maam;
3
3
  import { clone, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
4
4
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
5
5
  import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
6
6
  import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
7
+ import { KeystoneReplenishStrategy, KeystoneClaimType } from './keystone-types.mjs';
7
8
  import { createRevocationPoolConfig, createStandardPoolConfig } from './keystone-config-builder.mjs';
8
- import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE, POOL_ID_MANAGE, KEYSTONE_VERB_SYNC } from './keystone-constants.mjs';
9
+ import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE, POOL_ID_MANAGE, KEYSTONE_VERB_SYNC, POOL_ID_CUSTODIAN_MANAGE } from './keystone-constants.mjs';
9
10
  import { KeystoneService_V1, } from './keystone-service-v1.mjs';
10
- import { selectChallengeIds, validateChallengePool, validateKeystoneIb, validateKeystoneTransition, validateGenesisKeystone } from './keystone-helpers.mjs';
11
+ import { selectChallengeIds, validateChallengePool, validateKeystoneIb, validateKeystoneTransition, validateGenesisKeystone, rotatePoolChallenges } from './keystone-helpers.mjs';
11
12
  import { KeystoneProfileBuilder } from './policy/keystone-profile-builder.mjs';
12
13
  import { getTjpAddr } from '../common/other/ibgib-helper.mjs';
13
14
  import { deriveDelegateSecret } from './strategy/hash-reveal-v1/hash-reveal-v1.mjs';
@@ -160,20 +161,20 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
160
161
  });
161
162
  });
162
163
  await respecfully(sir, 'Derivation Logic', async () => {
163
- await ifWe(sir, 'derivePoolSecret with same inputs returns same output', async () => {
164
+ await ifWeMight(sir, 'derivePoolSecret with same inputs returns same output', async () => {
164
165
  const strategy = KeystoneStrategyFactory.create({ config });
165
166
  const secretA = await strategy.derivePoolSecret({ masterSecret });
166
167
  const secretB = await strategy.derivePoolSecret({ masterSecret });
167
168
  iReckon(sir, secretA).asTo('secret consistency').willEqual(secretB);
168
169
  iReckon(sir, secretA).asTo('secret length').isGonnaBeTruthy();
169
170
  });
170
- await ifWe(sir, 'derivePoolSecret with different master secret returns different output', async () => {
171
+ await ifWeMight(sir, 'derivePoolSecret with different master secret returns different output', async () => {
171
172
  const strategy = KeystoneStrategyFactory.create({ config });
172
173
  const secretA = await strategy.derivePoolSecret({ masterSecret });
173
174
  const secretB = await strategy.derivePoolSecret({ masterSecret: masterSecret + "_diff" });
174
175
  iReckon(sir, secretA).asTo('secrets differ').not.willEqual(secretB);
175
176
  });
176
- await ifWe(sir, 'derivePoolSecret with different salt returns different output', async () => {
177
+ await ifWeMight(sir, 'derivePoolSecret with different salt returns different output', async () => {
177
178
  // Modify salt in a copy of config
178
179
  const configB = { ...config, salt: "OtherPool" };
179
180
  const strategyA = KeystoneStrategyFactory.create({ config });
@@ -182,7 +183,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
182
183
  const secretB = await strategyB.derivePoolSecret({ masterSecret });
183
184
  iReckon(sir, secretA).asTo('salt affects secret').not.willEqual(secretB);
184
185
  });
185
- await ifWe(sir, 'deriveDelegateSecret derives a consistent secret using KDF', async () => {
186
+ await ifWeMight(sir, 'deriveDelegateSecret derives a consistent secret using KDF', async () => {
186
187
  const secretA = await deriveDelegateSecret({ masterSecret });
187
188
  const secretB = await deriveDelegateSecret({ masterSecret });
188
189
  iReckon(sir, secretA).asTo('consistency').willEqual(secretB);
@@ -192,7 +193,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
192
193
  });
193
194
  });
194
195
  await respecfully(sir, 'Challenge/Solution Logic', async () => {
195
- await ifWe(sir, 'generateSolution -> generateChallenge -> validateSolution loop works', async () => {
196
+ await ifWeMight(sir, 'generateSolution -> generateChallenge -> validateSolution loop works', async () => {
196
197
  const strategy = KeystoneStrategyFactory.create({ config });
197
198
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
198
199
  const challengeId = "a3ff7843552870fc28bef2b"; // arbitrary random challengeId
@@ -207,7 +208,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
207
208
  const isValid = await strategy.validateSolution({ solution, challenge });
208
209
  iReckon(sir, isValid).asTo('valid pair should pass').isGonnaBeTrue();
209
210
  });
210
- await ifWe(sir, 'validateSolution fails for mismatched values', async () => {
211
+ await ifWeMight(sir, 'validateSolution fails for mismatched values', async () => {
211
212
  const strategy = KeystoneStrategyFactory.create({ config });
212
213
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
213
214
  const challengeId = "8c994f3ed598f150e25513"; // arbitrary random challengeId
@@ -219,7 +220,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
219
220
  const isValid = await strategy.validateSolution({ solution: badSolution, challenge });
220
221
  iReckon(sir, isValid).asTo('tampered solution should fail').isGonnaBeFalse();
221
222
  });
222
- await ifWe(sir, 'validateSolution fails for mismatched challenge hashes', async () => {
223
+ await ifWeMight(sir, 'validateSolution fails for mismatched challenge hashes', async () => {
223
224
  const strategy = KeystoneStrategyFactory.create({ config });
224
225
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
225
226
  // Generate pair A
@@ -250,7 +251,7 @@ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
250
251
  mockMetaspace = new MockMetaspaceService(mockSpace);
251
252
  });
252
253
  await respecfully(sir, 'Genesis', async () => {
253
- await ifWe(sir, 'creates a valid genesis frame and persists it', async () => {
254
+ await ifWeMight(sir, 'creates a valid genesis frame and persists it', async () => {
254
255
  const salt = (await getUUID()).substring(0, 16);
255
256
  const config = createStandardPoolConfig({
256
257
  id: POOL_ID_DEFAULT,
@@ -276,7 +277,7 @@ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
276
277
  });
277
278
  });
278
279
  await respecfully(sir, 'Signing (Evolution)', async () => {
279
- await ifWe(sir, 'evolves the keystone with a valid proof', async () => {
280
+ await ifWeMight(sir, 'evolves the keystone with a valid proof', async () => {
280
281
  const claim = {
281
282
  target: "comment 123^gib",
282
283
  verb: "post"
@@ -301,7 +302,7 @@ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
301
302
  });
302
303
  });
303
304
  await respecfully(sir, 'Validation', async () => {
304
- await ifWe(sir, 'validates the genesis->signed transition', async () => {
305
+ await ifWeMight(sir, 'validates the genesis->signed transition', async () => {
305
306
  const errors = await service.validate({
306
307
  prevIbGib: genesisKeystone,
307
308
  currentIbGib: signedKeystone,
@@ -342,7 +343,7 @@ await respecfully(sir, 'Suite C: Security Vectors', async () => {
342
343
  });
343
344
  });
344
345
  await respecfully(sir, 'Wrong Secret (Forgery)', async () => {
345
- await ifWe(sir, 'prevents creation of forged frames', async () => {
346
+ await ifWeMight(sir, 'prevents creation of forged frames', async () => {
346
347
  const claim = { target: "comment 123^gib", verb: "post" };
347
348
  let errorCaught = false;
348
349
  let errorMsg = "";
@@ -368,7 +369,7 @@ await respecfully(sir, 'Suite C: Security Vectors', async () => {
368
369
  });
369
370
  });
370
371
  await respecfully(sir, 'Policy Violation (Restricted Verbs)', async () => {
371
- await ifWe(sir, 'throws error if signing forbidden verb with restricted pool', async () => {
372
+ await ifWeMight(sir, 'throws error if signing forbidden verb with restricted pool', async () => {
372
373
  // Create a specific restricted pool config manually
373
374
  const restrictedPoolId = "read_only_pool";
374
375
  const restrictedConfig = createStandardPoolConfig({
@@ -436,7 +437,7 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
436
437
  });
437
438
  await respecfully(sir, 'Revoke Lifecycle', async () => {
438
439
  let revokedKeystone;
439
- await ifWe(sir, 'successfully creates a revocation frame', async () => {
440
+ await ifWeMight(sir, 'successfully creates a revocation frame', async () => {
440
441
  revokedKeystone = await service.revoke({
441
442
  latestKeystone: genesisKeystone,
442
443
  masterSecret,
@@ -451,7 +452,7 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
451
452
  iReckon(sir, data.revocationInfo.reason).willEqual("Key compromised");
452
453
  iReckon(sir, data.revocationInfo.proof.claim.verb).willEqual(KEYSTONE_VERB_REVOKE);
453
454
  });
454
- await ifWe(sir, 'validates the revocation frame', async () => {
455
+ await ifWeMight(sir, 'validates the revocation frame', async () => {
455
456
  const errors = await service.validate({
456
457
  prevIbGib: genesisKeystone,
457
458
  currentIbGib: revokedKeystone,
@@ -460,7 +461,7 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
460
461
  });
461
462
  iReckon(sir, errors.length).asTo('no validation errors').willEqual(0);
462
463
  });
463
- await ifWe(sir, 'consumed the revocation pool (Scorched Earth)', async () => {
464
+ await ifWeMight(sir, 'consumed the revocation pool (Scorched Earth)', async () => {
464
465
  const data = revokedKeystone.data;
465
466
  const revokePool = data.challengePools.find(p => p.id === POOL_ID_REVOKE);
466
467
  // The pool should exist...
@@ -469,6 +470,42 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
469
470
  const remaining = Object.keys(revokePool.challenges);
470
471
  iReckon(sir, remaining.length).asTo('pool depleted').willEqual(0);
471
472
  });
473
+ await ifWeMight(sir, 'fails to sign any new claims after revocation', async () => {
474
+ let errorCaught = false;
475
+ try {
476
+ await service.sign({
477
+ latestKeystone: revokedKeystone,
478
+ masterSecret,
479
+ claim: { verb: "post", target: "some_target" },
480
+ metaspace: mockMetaspace,
481
+ space: mockSpace,
482
+ });
483
+ }
484
+ catch (err) {
485
+ errorCaught = true;
486
+ iReckon(sir, err.message).includes("has been revoked");
487
+ }
488
+ iReckon(sir, errorCaught).asTo('signing blocked after revocation').isGonnaBeTrue();
489
+ });
490
+ await ifWeMight(sir, 'fails to evolve or add pools after revocation', async () => {
491
+ let errorCaught = false;
492
+ try {
493
+ const dummyConfig = createStandardPoolConfig({ id: "dummy", salt: "dummy" });
494
+ const dummyPool = { id: "dummy", config: dummyConfig, challenges: {} };
495
+ await service.addPools({
496
+ latestKeystone: revokedKeystone,
497
+ masterSecret,
498
+ newPools: [dummyPool],
499
+ metaspace: mockMetaspace,
500
+ space: mockSpace,
501
+ });
502
+ }
503
+ catch (err) {
504
+ errorCaught = true;
505
+ iReckon(sir, err.message).includes("has been revoked");
506
+ }
507
+ iReckon(sir, errorCaught).asTo('adding pools blocked after revocation').isGonnaBeTrue();
508
+ });
472
509
  });
473
510
  });
474
511
  // ===========================================================================
@@ -522,7 +559,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
522
559
  });
523
560
  });
524
561
  await respecfully(sir, 'Happy Path', async () => {
525
- await ifWe(sir, 'authorizes and adds a foreign pool', async () => {
562
+ await ifWeMight(sir, 'authorizes and adds a foreign pool', async () => {
526
563
  const bobPool = await createForeignPool("pool_bob", ["post"]);
527
564
  const updatedKeystone = await service.addPools({
528
565
  latestKeystone: aliceKeystone,
@@ -554,7 +591,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
554
591
  });
555
592
  });
556
593
  await respecfully(sir, 'Permissions & Logic', async () => {
557
- await ifWe(sir, 'fails if no pool allows "manage" verb', async () => {
594
+ await ifWeMight(sir, 'fails if no pool allows "manage" verb', async () => {
558
595
  // 1. Create a restricted keystone
559
596
  let id = "read_only";
560
597
  const restrictedConfig = createStandardPoolConfig({ id, salt: id });
@@ -583,7 +620,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
583
620
  }
584
621
  iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
585
622
  });
586
- await ifWe(sir, 'fails on ID collision', async () => {
623
+ await ifWeMight(sir, 'fails on ID collision', async () => {
587
624
  // Try to add "pool_bob" again (it was added in Happy Path)
588
625
  const duplicatePool = await createForeignPool("pool_bob");
589
626
  let errorCaught = false;
@@ -658,7 +695,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
658
695
  });
659
696
  });
660
697
  await respecfully(sir, 'Happy Path', async () => {
661
- await ifWe(sir, 'authorizes and adds a foreign pool', async () => {
698
+ await ifWeMight(sir, 'authorizes and adds a foreign pool', async () => {
662
699
  const bobPool = await createForeignPool("pool_bob", ["post"]);
663
700
  const updatedKeystone = await service.addPools({
664
701
  latestKeystone: aliceKeystone,
@@ -690,7 +727,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
690
727
  });
691
728
  });
692
729
  await respecfully(sir, 'Permissions & Logic', async () => {
693
- await ifWe(sir, 'fails if no pool allows "manage" verb', async () => {
730
+ await ifWeMight(sir, 'fails if no pool allows "manage" verb', async () => {
694
731
  // 1. Create a restricted keystone (read-only)
695
732
  let id = "read_only";
696
733
  const restrictedConfig = createStandardPoolConfig({ id, salt: id });
@@ -719,7 +756,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
719
756
  }
720
757
  iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
721
758
  });
722
- await ifWe(sir, 'fails on ID collision', async () => {
759
+ await ifWeMight(sir, 'fails on ID collision', async () => {
723
760
  // Try to add "pool_bob" again (it was added in Happy Path)
724
761
  const duplicatePool = await createForeignPool("pool_bob");
725
762
  let errorCaught = false;
@@ -771,7 +808,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
771
808
  });
772
809
  });
773
810
  await respecfully(sir, 'Proof Granularity & Math', async () => {
774
- await ifWe(sir, 'generates exactly the expected number of solutions', async () => {
811
+ await ifWeMight(sir, 'generates exactly the expected number of solutions', async () => {
775
812
  signedKeystone = await service.sign({
776
813
  latestKeystone: genesisKeystone,
777
814
  masterSecret: aliceSecret,
@@ -785,7 +822,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
785
822
  // 2 Sequential + 2 Random = 4
786
823
  iReckon(sir, solutions.length).asTo('solution count').willEqual(4);
787
824
  });
788
- await ifWe(sir, 'verifies the math manually (White-box Crypto Check)', async () => {
825
+ await ifWeMight(sir, 'verifies the math manually (White-box Crypto Check)', async () => {
789
826
  const proof = signedKeystone.data.proofs[0];
790
827
  const poolSnapshot = genesisKeystone.data.challengePools.find(p => p.id === poolId);
791
828
  // We iterate every solution in the proof and MANUALLY verify the hash relationship
@@ -808,7 +845,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
808
845
  iReckon(sir, calculatedHash).asTo(`Manual hash verification for ${solution.challengeId}`).willEqual(challenge.hash);
809
846
  }
810
847
  });
811
- await ifWe(sir, 'verifies FIFO logic (Deterministic Selection)', async () => {
848
+ await ifWeMight(sir, 'verifies FIFO logic (Deterministic Selection)', async () => {
812
849
  const proof = signedKeystone.data.proofs[0];
813
850
  const poolSnapshot = genesisKeystone.data.challengePools.find(p => p.id === poolId);
814
851
  // The first N keys in the pool should be the FIFO targets.
@@ -822,7 +859,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
822
859
  iReckon(sir, hasFirst).asTo(`Solution includes 1st FIFO ID (${expectedFifoIds[0]})`).isGonnaBeTrue();
823
860
  iReckon(sir, hasSecond).asTo(`Solution includes 2nd FIFO ID (${expectedFifoIds[1]})`).isGonnaBeTrue();
824
861
  });
825
- await ifWe(sir, 'verifies Next-Gen Hash-Based Target Binding Selection', async () => {
862
+ await ifWeMight(sir, 'verifies Next-Gen Hash-Based Target Binding Selection', async () => {
826
863
  const bindingConfig = createStandardPoolConfig({
827
864
  id: "binding_pool",
828
865
  salt: "binding_salt",
@@ -864,7 +901,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
864
901
  iReckon(sir, proofIds.includes(id)).asTo(`proof includes selected ID ${id}`).isGonnaBeTrue();
865
902
  }
866
903
  });
867
- await ifWe(sir, 'fails validation if total requested challenges >= size', async () => {
904
+ await ifWeMight(sir, 'fails validation if total requested challenges >= size', async () => {
868
905
  const invalidConfig = createStandardPoolConfig({
869
906
  id: "invalid_pool",
870
907
  salt: "invalid_salt",
@@ -880,7 +917,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
880
917
  });
881
918
  });
882
919
  await respecfully(sir, 'DTO & Serialization', async () => {
883
- await ifWe(sir, 'survives a clone/JSON-cycle without corruption', async () => {
920
+ await ifWeMight(sir, 'survives a clone/JSON-cycle without corruption', async () => {
884
921
  // 1. Create a DTO (simulate network transmission/storage)
885
922
  // 'clone' does a JSON stringify/parse under the hood (usually) or structured clone.
886
923
  const dto = clone(signedKeystone);
@@ -896,7 +933,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
896
933
  });
897
934
  iReckon(sir, errors.length).asTo('DTO validation errors').willEqual(0);
898
935
  });
899
- await ifWe(sir, 'ensures data contains no functions or circular refs', async () => {
936
+ await ifWeMight(sir, 'ensures data contains no functions or circular refs', async () => {
900
937
  // A crude but effective test: ensure JSON.stringify doesn't throw
901
938
  // and the result is equal to the object (if we parsed it back).
902
939
  const jsonStr = JSON.stringify(signedKeystone);
@@ -915,7 +952,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
915
952
  });
916
953
  });
917
954
  await respecfully(sir, 'KeystoneProfileBuilder and Modular Schemas', async () => {
918
- await ifWe(sir, 'compiles configs successfully with medium profile', async () => {
955
+ await ifWeMight(sir, 'compiles configs successfully with medium profile', async () => {
919
956
  const builder = KeystoneProfileBuilder.buildKeystone('medium')
920
957
  .withUsername('alice')
921
958
  .withEmail('alice@example.com')
@@ -934,7 +971,17 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
934
971
  iReckon(sir, details.description).asTo('description').willEqual('Alice test keystone');
935
972
  iReckon(sir, details.extraInfo).asTo('extraInfo').willEqual('abc');
936
973
  });
937
- await ifWe(sir, 'supports customized overrides', async () => {
974
+ await ifWeMight(sir, 'compiles configs successfully with domain profile (omitting connect)', async () => {
975
+ const builder = KeystoneProfileBuilder.buildKeystone('domain')
976
+ .withUsername('alice')
977
+ .withEmail('alice@example.com')
978
+ .withDescription('Alice domain keystone');
979
+ const configs = await builder.compileConfigs();
980
+ iReckon(sir, configs.length).asTo('number of pools').willEqual(1);
981
+ const connectPool = configs.find(c => c.id === 'connect');
982
+ iReckon(sir, connectPool).asTo('connect pool omitted').isGonnaBeUndefined();
983
+ });
984
+ await ifWeMight(sir, 'supports customized overrides', async () => {
938
985
  const builder = KeystoneProfileBuilder.buildKeystone('test')
939
986
  .customizePool('sync', {
940
987
  behavior: {
@@ -949,7 +996,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
949
996
  });
950
997
  });
951
998
  await respecfully(sir, 'Keystone Metadata Validation', async () => {
952
- await ifWe(sir, 'validates correct username and description', async () => {
999
+ await ifWeMight(sir, 'validates correct username and description', async () => {
953
1000
  const service = new KeystoneService_V1();
954
1001
  const space = new MockIbGibSpace();
955
1002
  const metaspace = new MockMetaspaceService(space);
@@ -970,7 +1017,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
970
1017
  const errors = await service.validateGenesisKeystone({ keystoneIbGib });
971
1018
  iReckon(sir, errors.length).asTo('errors count').willEqual(0);
972
1019
  });
973
- await ifWe(sir, 'fails validation for invalid username length or character', async () => {
1020
+ await ifWeMight(sir, 'fails validation for invalid username length or character', async () => {
974
1021
  const service = new KeystoneService_V1();
975
1022
  const space = new MockIbGibSpace();
976
1023
  const metaspace = new MockMetaspaceService(space);
@@ -990,7 +1037,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
990
1037
  iReckon(sir, errors.length).asTo('errors count').willEqual(1);
991
1038
  iReckon(sir, errors[0]).includes('invalid username');
992
1039
  });
993
- await ifWe(sir, 'fails validation for invalid description characters', async () => {
1040
+ await ifWeMight(sir, 'fails validation for invalid description characters', async () => {
994
1041
  const service = new KeystoneService_V1();
995
1042
  const space = new MockIbGibSpace();
996
1043
  const metaspace = new MockMetaspaceService(space);
@@ -1047,7 +1094,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1047
1094
  space: mockSpace,
1048
1095
  });
1049
1096
  });
1050
- await ifWe(sir, 'registers a delegate keystone and evolves parent using manage pool', async () => {
1097
+ await ifWeMight(sir, 'registers a delegate keystone and evolves parent using manage pool', async () => {
1051
1098
  // Register the delegate
1052
1099
  const evolvedParent = await service.registerDelegate({
1053
1100
  parentKeystone,
@@ -1067,7 +1114,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1067
1114
  iReckon(sir, delegateInfo).asTo('delegate info registered').isGonnaBeTruthy();
1068
1115
  iReckon(sir, delegateInfo?.delegateTjpAddr).asTo('delegate tjp matches').willEqual(delegateTjpAddr);
1069
1116
  });
1070
- await ifWe(sir, 'gets correct delegate status via parent TJP address', async () => {
1117
+ await ifWeMight(sir, 'gets correct delegate status via parent TJP address', async () => {
1071
1118
  const parentTjpAddr = getTjpAddr({ ibGib: parentKeystone });
1072
1119
  if (!parentTjpAddr) {
1073
1120
  throw new Error(`(UNEXPECTED) parentTjpAddr falsy? (E: b7ce2835ccd951d8c99257328be3b826)`);
@@ -1103,7 +1150,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1103
1150
  iReckon(sir, statusAfter.registered).asTo('registered status').isGonnaBeTrue();
1104
1151
  iReckon(sir, statusAfter.delegateInfo?.delegateTjpAddr).asTo('status tjp matches').willEqual(delegateTjpAddr);
1105
1152
  });
1106
- await ifWe(sir, 'unregisters a registered delegate keystone', async () => {
1153
+ await ifWeMight(sir, 'unregisters a registered delegate keystone', async () => {
1107
1154
  // Register first
1108
1155
  const evolvedParent = await service.registerDelegate({
1109
1156
  parentKeystone,
@@ -1129,7 +1176,7 @@ await respecfully(sir, 'Suite E: Delegation (Register/Unregister)', async () =>
1129
1176
  const info = finalParent.data?.delegates?.[delegateTjpAddr];
1130
1177
  iReckon(sir, info).asTo('delegate info removed').isGonnaBeUndefined();
1131
1178
  });
1132
- await ifWe(sir, 'throws if parent does not have a manage pool', async () => {
1179
+ await ifWeMight(sir, 'throws if parent does not have a manage pool', async () => {
1133
1180
  // Create a parent WITHOUT a manage pool
1134
1181
  const salt = (await getUUID()).substring(0, 16);
1135
1182
  const defaultConfig = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt });
@@ -1189,13 +1236,13 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1189
1236
  space: mockSpace,
1190
1237
  });
1191
1238
  });
1192
- await ifWe(sir, 'validateKeystoneIb returns true for valid keystone ib and false for others', async () => {
1239
+ await ifWeMight(sir, 'validateKeystoneIb returns true for valid keystone ib and false for others', async () => {
1193
1240
  iReckon(sir, await validateKeystoneIb({ ib: 'keystone' })).isGonnaBeTrue();
1194
1241
  iReckon(sir, await validateKeystoneIb({ ib: 'keystone user info' })).isGonnaBeTrue();
1195
1242
  iReckon(sir, await validateKeystoneIb({ ib: 'invalidAtom' })).isGonnaBeFalse();
1196
1243
  iReckon(sir, await validateKeystoneIb({ ib: 'not_keystone sub_atom' })).isGonnaBeFalse();
1197
1244
  });
1198
- await ifWe(sir, 'validateGenesisKeystone fails validation if ib is invalid', async () => {
1245
+ await ifWeMight(sir, 'validateGenesisKeystone fails validation if ib is invalid', async () => {
1199
1246
  const invalidGenesis = {
1200
1247
  ...parentKeystone,
1201
1248
  ib: 'not_a_keystone^' + parentKeystone.gib,
@@ -1204,7 +1251,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1204
1251
  iReckon(sir, errors.length > 0).isGonnaBeTrue();
1205
1252
  iReckon(sir, errors[0]).includes('invalid keystone ib');
1206
1253
  });
1207
- await ifWe(sir, 'validateKeystoneTransition passes for valid delegate registration', async () => {
1254
+ await ifWeMight(sir, 'validateKeystoneTransition passes for valid delegate registration', async () => {
1208
1255
  const evolvedParent = await service.registerDelegate({
1209
1256
  parentKeystone,
1210
1257
  delegateKeystone,
@@ -1219,7 +1266,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1219
1266
  });
1220
1267
  iReckon(sir, errors.length).willEqual(0);
1221
1268
  });
1222
- await ifWe(sir, 'validateKeystoneTransition fails if delegates map is mutated without manage proof', async () => {
1269
+ await ifWeMight(sir, 'validateKeystoneTransition fails if delegates map is mutated without manage proof', async () => {
1223
1270
  const evolvedParent = await service.registerDelegate({
1224
1271
  parentKeystone,
1225
1272
  delegateKeystone,
@@ -1242,7 +1289,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1242
1289
  iReckon(sir, errors.length > 0).isGonnaBeTrue();
1243
1290
  iReckon(sir, errors.some(e => e.includes('Policy Violation: Delegates map was mutated'))).isGonnaBeTrue();
1244
1291
  });
1245
- await ifWe(sir, 'validateKeystoneTransition fails if delegate key does not match delegateTjpAddr', async () => {
1292
+ await ifWeMight(sir, 'validateKeystoneTransition fails if delegate key does not match delegateTjpAddr', async () => {
1246
1293
  const evolvedParent = await service.registerDelegate({
1247
1294
  parentKeystone,
1248
1295
  delegateKeystone,
@@ -1266,7 +1313,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1266
1313
  iReckon(sir, errors.length > 0).isGonnaBeTrue();
1267
1314
  iReckon(sir, errors.some(e => e.includes('does not match delegateTjpAddr'))).isGonnaBeTrue();
1268
1315
  });
1269
- await ifWe(sir, 'validateKeystoneTransition fails if delegate addresses have invalid keystone ib', async () => {
1316
+ await ifWeMight(sir, 'validateKeystoneTransition fails if delegate addresses have invalid keystone ib', async () => {
1270
1317
  const evolvedParent = await service.registerDelegate({
1271
1318
  parentKeystone,
1272
1319
  delegateKeystone,
@@ -1288,7 +1335,7 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1288
1335
  iReckon(sir, errors.length > 0).isGonnaBeTrue();
1289
1336
  iReckon(sir, errors.some(e => e.includes('not a valid keystone address'))).isGonnaBeTrue();
1290
1337
  });
1291
- await ifWe(sir, 'validateKeystoneTransition fails if thisAddr is not in the parent timeline history', async () => {
1338
+ await ifWeMight(sir, 'validateKeystoneTransition fails if thisAddr is not in the parent timeline history', async () => {
1292
1339
  const evolvedParent = await service.registerDelegate({
1293
1340
  parentKeystone,
1294
1341
  delegateKeystone,
@@ -1311,4 +1358,551 @@ await respecfully(sir, 'Suite G: Delegate & IB Validation', async () => {
1311
1358
  iReckon(sir, errors.some(e => e.includes('not a valid historical address in the parent keystone timeline'))).isGonnaBeTrue();
1312
1359
  });
1313
1360
  });
1361
+ // ===========================================================================
1362
+ // SUITE G: COMBINED POOLS & PASSWORD ROTATION
1363
+ // ===========================================================================
1364
+ await respecfully(sir, 'Suite G: Combined Pools & Password Rotation', async () => {
1365
+ const service = new KeystoneService_V1();
1366
+ const userSecret = "UserMasterSecret_123";
1367
+ const custodianSecret = "CustodianSecret_456";
1368
+ let mockSpace;
1369
+ let mockMetaspace;
1370
+ firstOfEach(sir, async () => {
1371
+ mockSpace = new MockIbGibSpace();
1372
+ mockMetaspace = new MockMetaspaceService(mockSpace);
1373
+ });
1374
+ await ifWeMight(sir, 'revokes a keystone using a separate revocation pool', async () => {
1375
+ const stdSalt = "std_salt";
1376
+ const revokeSalt = "revoke_salt";
1377
+ const stdConfig = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt: stdSalt });
1378
+ const revokeConfig = createStandardPoolConfig({ id: POOL_ID_REVOKE, salt: revokeSalt });
1379
+ revokeConfig.allowedVerbs = [KEYSTONE_VERB_REVOKE];
1380
+ revokeConfig.behavior.replenish = KeystoneReplenishStrategy.deleteAll;
1381
+ const keystone = await service.genesis({
1382
+ masterSecret: userSecret,
1383
+ configs: [stdConfig, revokeConfig],
1384
+ metaspace: mockMetaspace,
1385
+ space: mockSpace,
1386
+ });
1387
+ const revoked = await service.revoke({
1388
+ latestKeystone: keystone,
1389
+ masterSecret: userSecret,
1390
+ metaspace: mockMetaspace,
1391
+ space: mockSpace,
1392
+ });
1393
+ iReckon(sir, revoked.data?.revocationInfo).asTo('revocationInfo present').isGonnaBeTruthy();
1394
+ const revokePool = revoked.data?.challengePools.find(p => p.id === POOL_ID_REVOKE);
1395
+ iReckon(sir, Object.keys(revokePool.challenges).length).asTo('revoke pool depleted').willEqual(0);
1396
+ });
1397
+ await ifWeMight(sir, 'revokes a keystone using a combined manage/revoke pool and empties it', async () => {
1398
+ const salt = "combined_salt";
1399
+ const manageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt });
1400
+ manageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1401
+ const keystone = await service.genesis({
1402
+ masterSecret: userSecret,
1403
+ configs: [manageConfig],
1404
+ metaspace: mockMetaspace,
1405
+ space: mockSpace,
1406
+ });
1407
+ const revoked = await service.revoke({
1408
+ latestKeystone: keystone,
1409
+ masterSecret: userSecret,
1410
+ metaspace: mockMetaspace,
1411
+ space: mockSpace,
1412
+ });
1413
+ iReckon(sir, revoked.data?.revocationInfo).asTo('revocationInfo present').isGonnaBeTruthy();
1414
+ const managePool = revoked.data?.challengePools.find(p => p.id === POOL_ID_MANAGE);
1415
+ iReckon(sir, Object.keys(managePool.challenges).length).asTo('combined manage pool depleted').willEqual(0);
1416
+ });
1417
+ await ifWeMight(sir, 'rotates user password successfully when signed by the user manage pool', async () => {
1418
+ const salt = "manage_salt";
1419
+ const manageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt });
1420
+ manageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1421
+ const keystone = await service.genesis({
1422
+ masterSecret: userSecret,
1423
+ configs: [manageConfig],
1424
+ metaspace: mockMetaspace,
1425
+ space: mockSpace,
1426
+ });
1427
+ const newSecret = "NewUserSecret_999";
1428
+ const rotated = await service.changePassword({
1429
+ latestKeystone: keystone,
1430
+ newMasterSecret: newSecret,
1431
+ signingSecret: userSecret,
1432
+ signingPoolId: POOL_ID_MANAGE,
1433
+ metaspace: mockMetaspace,
1434
+ space: mockSpace,
1435
+ });
1436
+ iReckon(sir, rotated).isGonnaBeTruthy();
1437
+ // 1. Try to sign using the new secret (should succeed)
1438
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1439
+ const signedNew = await service.sign({
1440
+ latestKeystone: rotated,
1441
+ masterSecret: newSecret,
1442
+ claim,
1443
+ poolId: POOL_ID_MANAGE,
1444
+ metaspace: mockMetaspace,
1445
+ space: mockSpace,
1446
+ });
1447
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1448
+ // 2. Try to sign using the old secret (should fail)
1449
+ let errorCaught = false;
1450
+ try {
1451
+ await service.sign({
1452
+ latestKeystone: rotated,
1453
+ masterSecret: userSecret,
1454
+ claim,
1455
+ poolId: POOL_ID_MANAGE,
1456
+ metaspace: mockMetaspace,
1457
+ space: mockSpace,
1458
+ });
1459
+ }
1460
+ catch {
1461
+ errorCaught = true;
1462
+ }
1463
+ iReckon(sir, errorCaught).asTo('old secret signing rejected').isGonnaBeTrue();
1464
+ });
1465
+ await ifWeMight(sir, 'recovers user password successfully when signed by the custodian manage pool', async () => {
1466
+ const userSalt = "user_salt";
1467
+ const custodianSalt = "custodian_salt";
1468
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1469
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1470
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1471
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1472
+ // We simulate constructing the custodian pool "offline" by the server
1473
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1474
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1475
+ const challenges = {};
1476
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1477
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1478
+ const challengeId = raw.substring(0, 16);
1479
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1480
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1481
+ }
1482
+ const custodianPool = {
1483
+ id: POOL_ID_CUSTODIAN_MANAGE,
1484
+ config: custodianManageConfig,
1485
+ challenges,
1486
+ isForeign: true
1487
+ };
1488
+ const keystone = await service.genesis({
1489
+ masterSecret: userSecret,
1490
+ configs: [userManageConfig],
1491
+ metaspace: mockMetaspace,
1492
+ space: mockSpace,
1493
+ });
1494
+ // Add the custodian pool to the keystone
1495
+ const keystoneWithCustodian = await service.addPools({
1496
+ latestKeystone: keystone,
1497
+ masterSecret: userSecret,
1498
+ newPools: [custodianPool],
1499
+ metaspace: mockMetaspace,
1500
+ space: mockSpace,
1501
+ });
1502
+ // The user forgets their secret. Recover using custodianSecret!
1503
+ const recoveredSecret = "RecoveredUserSecret_777";
1504
+ const recoveredKeystone = await service.changePassword({
1505
+ latestKeystone: keystoneWithCustodian,
1506
+ newMasterSecret: recoveredSecret,
1507
+ signingSecret: custodianSecret,
1508
+ signingPoolId: POOL_ID_CUSTODIAN_MANAGE,
1509
+ metaspace: mockMetaspace,
1510
+ space: mockSpace,
1511
+ });
1512
+ iReckon(sir, recoveredKeystone).isGonnaBeTruthy();
1513
+ // Verify we can sign with recovered secret
1514
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1515
+ const signedNew = await service.sign({
1516
+ latestKeystone: recoveredKeystone,
1517
+ masterSecret: recoveredSecret,
1518
+ claim,
1519
+ poolId: POOL_ID_MANAGE,
1520
+ metaspace: mockMetaspace,
1521
+ space: mockSpace,
1522
+ });
1523
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1524
+ });
1525
+ await ifWeMight(sir, 'rotates custodian manage pool successfully', async () => {
1526
+ const userSalt = "user_salt";
1527
+ const custodianSalt = "custodian_salt";
1528
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1529
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1530
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1531
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1532
+ // Custodian Pool genesis
1533
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1534
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1535
+ const challenges = {};
1536
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1537
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1538
+ const challengeId = raw.substring(0, 16);
1539
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1540
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1541
+ }
1542
+ const custodianPool = {
1543
+ id: POOL_ID_CUSTODIAN_MANAGE,
1544
+ config: custodianManageConfig,
1545
+ challenges,
1546
+ isForeign: true
1547
+ };
1548
+ const keystone = await service.genesis({
1549
+ masterSecret: userSecret,
1550
+ configs: [userManageConfig],
1551
+ metaspace: mockMetaspace,
1552
+ space: mockSpace,
1553
+ });
1554
+ const keystoneWithCustodian = await service.addPools({
1555
+ latestKeystone: keystone,
1556
+ masterSecret: userSecret,
1557
+ newPools: [custodianPool],
1558
+ metaspace: mockMetaspace,
1559
+ space: mockSpace,
1560
+ });
1561
+ // Rotate custodian credentials to new secret
1562
+ const newCustodianSecret = "NewCustodianSecret_888";
1563
+ const newCustodianConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: "new_custodian_salt" });
1564
+ newCustodianConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1565
+ const newStrategy = KeystoneStrategyFactory.create({ config: newCustodianConfig });
1566
+ const newPoolSecret = await newStrategy.derivePoolSecret({ masterSecret: newCustodianSecret });
1567
+ const newChallenges = {};
1568
+ for (let i = 0; i < newCustodianConfig.behavior.size; i++) {
1569
+ const raw = await hash({ s: `${newCustodianConfig.salt}${Date.now()}${i}` });
1570
+ const challengeId = raw.substring(0, 16);
1571
+ const solution = await newStrategy.generateSolution({ poolSecret: newPoolSecret, poolId: newCustodianConfig.id, challengeId });
1572
+ newChallenges[challengeId] = await newStrategy.generateChallenge({ solution });
1573
+ }
1574
+ const newCustodianPool = {
1575
+ id: POOL_ID_CUSTODIAN_MANAGE,
1576
+ config: newCustodianConfig,
1577
+ challenges: newChallenges,
1578
+ isForeign: true
1579
+ };
1580
+ // Rotate signed by user manage pool
1581
+ const rotated = await service.replacePool({
1582
+ latestKeystone: keystoneWithCustodian,
1583
+ newPool: newCustodianPool,
1584
+ signingSecret: userSecret,
1585
+ signingPoolId: POOL_ID_MANAGE,
1586
+ metaspace: mockMetaspace,
1587
+ space: mockSpace,
1588
+ });
1589
+ iReckon(sir, rotated).isGonnaBeTruthy();
1590
+ // Verify we can sign with new custodian secret
1591
+ const claim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target" };
1592
+ const signedNew = await service.sign({
1593
+ latestKeystone: rotated,
1594
+ masterSecret: newCustodianSecret,
1595
+ claim,
1596
+ poolId: POOL_ID_CUSTODIAN_MANAGE,
1597
+ metaspace: mockMetaspace,
1598
+ space: mockSpace,
1599
+ });
1600
+ iReckon(sir, signedNew).isGonnaBeTruthy();
1601
+ });
1602
+ await ifWeMight(sir, 'rotates all non-foreign pools during changePassword by default', async () => {
1603
+ const userSalt = "user_salt";
1604
+ const custodianSalt = "custodian_salt";
1605
+ const secondarySalt = "secondary_salt";
1606
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: userSalt });
1607
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1608
+ // Another native/non-foreign pool
1609
+ const secondaryNativeConfig = createStandardPoolConfig({ id: "secondary-native", salt: secondarySalt });
1610
+ secondaryNativeConfig.allowedVerbs = [KEYSTONE_VERB_SYNC];
1611
+ // A custodian/foreign pool
1612
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: custodianSalt });
1613
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1614
+ // Custodian Pool genesis
1615
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1616
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1617
+ const challenges = {};
1618
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1619
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1620
+ const challengeId = raw.substring(0, 16);
1621
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1622
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1623
+ }
1624
+ const custodianPool = {
1625
+ id: POOL_ID_CUSTODIAN_MANAGE,
1626
+ config: custodianManageConfig,
1627
+ challenges,
1628
+ isForeign: true
1629
+ };
1630
+ const keystone = await service.genesis({
1631
+ masterSecret: userSecret,
1632
+ configs: [userManageConfig, secondaryNativeConfig],
1633
+ metaspace: mockMetaspace,
1634
+ space: mockSpace,
1635
+ });
1636
+ const keystoneWithCustodian = await service.addPools({
1637
+ latestKeystone: keystone,
1638
+ masterSecret: userSecret,
1639
+ newPools: [custodianPool],
1640
+ metaspace: mockMetaspace,
1641
+ space: mockSpace,
1642
+ });
1643
+ // Rotate passphrase/password
1644
+ const newPassphrase = "SuperNewSecretPassphrase_999";
1645
+ const rotated = await service.changePassword({
1646
+ latestKeystone: keystoneWithCustodian,
1647
+ newMasterSecret: newPassphrase,
1648
+ signingSecret: userSecret,
1649
+ signingPoolId: POOL_ID_MANAGE,
1650
+ metaspace: mockMetaspace,
1651
+ space: mockSpace,
1652
+ });
1653
+ iReckon(sir, rotated).isGonnaBeTruthy();
1654
+ const data = rotated.data;
1655
+ const rotatedManage = data.challengePools.find(p => p.id === POOL_ID_MANAGE);
1656
+ const rotatedSecondary = data.challengePools.find(p => p.id === "secondary-native");
1657
+ const rotatedCustodian = data.challengePools.find(p => p.id === POOL_ID_CUSTODIAN_MANAGE);
1658
+ // Verify native pools rotated (salt changed)
1659
+ iReckon(sir, rotatedManage.config.salt).not.willEqual(userSalt);
1660
+ iReckon(sir, rotatedSecondary.config.salt).not.willEqual(secondarySalt);
1661
+ // Verify foreign pool was not modified (salt identical)
1662
+ iReckon(sir, rotatedCustodian.config.salt).willEqual(custodianSalt);
1663
+ // Verify we can validate transition
1664
+ const errors = await service.validate({
1665
+ prevIbGib: keystoneWithCustodian,
1666
+ currentIbGib: rotated
1667
+ });
1668
+ iReckon(sir, errors.length).willEqual(0);
1669
+ // Verify we can sign using the rotated 'manage' pool with the new passphrase
1670
+ const manageClaim = { verb: KEYSTONE_VERB_MANAGE, target: "test_target_manage" };
1671
+ const signedWithNewManage = await service.sign({
1672
+ latestKeystone: rotated,
1673
+ masterSecret: newPassphrase,
1674
+ claim: manageClaim,
1675
+ poolId: POOL_ID_MANAGE,
1676
+ metaspace: mockMetaspace,
1677
+ space: mockSpace,
1678
+ });
1679
+ iReckon(sir, signedWithNewManage).isGonnaBeTruthy();
1680
+ // Verify we can sign using the rotated 'secondary-native' pool with the new passphrase
1681
+ const syncClaim = { verb: KEYSTONE_VERB_SYNC, target: "test_target_sync" };
1682
+ const signedWithNewSync = await service.sign({
1683
+ latestKeystone: rotated,
1684
+ masterSecret: newPassphrase,
1685
+ claim: syncClaim,
1686
+ poolId: "secondary-native",
1687
+ metaspace: mockMetaspace,
1688
+ space: mockSpace,
1689
+ });
1690
+ iReckon(sir, signedWithNewSync).isGonnaBeTruthy();
1691
+ // Verify signing with the OLD master secret fails on both pools
1692
+ let oldManageFailed = false;
1693
+ try {
1694
+ await service.sign({
1695
+ latestKeystone: rotated,
1696
+ masterSecret: userSecret,
1697
+ claim: manageClaim,
1698
+ poolId: POOL_ID_MANAGE,
1699
+ metaspace: mockMetaspace,
1700
+ space: mockSpace,
1701
+ });
1702
+ }
1703
+ catch {
1704
+ oldManageFailed = true;
1705
+ }
1706
+ iReckon(sir, oldManageFailed).isGonnaBeTrue();
1707
+ let oldSyncFailed = false;
1708
+ try {
1709
+ await service.sign({
1710
+ latestKeystone: rotated,
1711
+ masterSecret: userSecret,
1712
+ claim: syncClaim,
1713
+ poolId: "secondary-native",
1714
+ metaspace: mockMetaspace,
1715
+ space: mockSpace,
1716
+ });
1717
+ }
1718
+ catch {
1719
+ oldSyncFailed = true;
1720
+ }
1721
+ iReckon(sir, oldSyncFailed).isGonnaBeTrue();
1722
+ });
1723
+ await ifWeMight(sir, 'fails validation if password rotation details or constraints are violated', async () => {
1724
+ // Construct invalid change-password transition
1725
+ const userManageConfig = createStandardPoolConfig({ id: POOL_ID_MANAGE, salt: "salt1" });
1726
+ userManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1727
+ const custodianManageConfig = createStandardPoolConfig({ id: POOL_ID_CUSTODIAN_MANAGE, salt: "salt2" });
1728
+ custodianManageConfig.allowedVerbs = [KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE];
1729
+ // Custodian Pool genesis
1730
+ const strategy = KeystoneStrategyFactory.create({ config: custodianManageConfig });
1731
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: custodianSecret });
1732
+ const challenges = {};
1733
+ for (let i = 0; i < custodianManageConfig.behavior.size; i++) {
1734
+ const raw = await hash({ s: `${custodianManageConfig.salt}${Date.now()}${i}` });
1735
+ const challengeId = raw.substring(0, 16);
1736
+ const solution = await strategy.generateSolution({ poolSecret, poolId: custodianManageConfig.id, challengeId });
1737
+ challenges[challengeId] = await strategy.generateChallenge({ solution });
1738
+ }
1739
+ const custodianPool = {
1740
+ id: POOL_ID_CUSTODIAN_MANAGE,
1741
+ config: custodianManageConfig,
1742
+ challenges,
1743
+ isForeign: true
1744
+ };
1745
+ const keystone = await service.genesis({
1746
+ masterSecret: userSecret,
1747
+ configs: [userManageConfig],
1748
+ metaspace: mockMetaspace,
1749
+ space: mockSpace,
1750
+ });
1751
+ const keystoneWithCustodian = await service.addPools({
1752
+ latestKeystone: keystone,
1753
+ masterSecret: userSecret,
1754
+ newPools: [custodianPool],
1755
+ metaspace: mockMetaspace,
1756
+ space: mockSpace,
1757
+ });
1758
+ // Create an invalid frame manually where a foreign pool is rotated during change-password
1759
+ const badRotatedPool = await rotatePoolChallenges({
1760
+ prevPool: custodianPool,
1761
+ newMasterSecret: "bad_secret"
1762
+ });
1763
+ const evolved = await service.changePassword({
1764
+ latestKeystone: keystoneWithCustodian,
1765
+ newMasterSecret: "new_pass",
1766
+ signingSecret: userSecret,
1767
+ signingPoolId: POOL_ID_MANAGE,
1768
+ metaspace: mockMetaspace,
1769
+ space: mockSpace,
1770
+ });
1771
+ // Inject the foreign pool rotation manually to simulate an attacker
1772
+ const attackerData = {
1773
+ ...evolved.data,
1774
+ challengePools: evolved.data.challengePools.map(p => p.id === POOL_ID_CUSTODIAN_MANAGE ? badRotatedPool : p)
1775
+ };
1776
+ // Update details claim to say we rotated custodian-manage
1777
+ attackerData.proofs[0].claim.details = {
1778
+ type: KeystoneClaimType.change_password,
1779
+ info: { change: [POOL_ID_MANAGE, POOL_ID_CUSTODIAN_MANAGE] }
1780
+ };
1781
+ const attackerKeystone = {
1782
+ ...evolved,
1783
+ data: attackerData
1784
+ };
1785
+ const errors = await service.validate({
1786
+ prevIbGib: keystoneWithCustodian,
1787
+ currentIbGib: attackerKeystone
1788
+ });
1789
+ iReckon(sir, errors.length > 0).isGonnaBeTrue();
1790
+ const hasForeignViolation = errors.some(e => e.includes("Security Violation") && e.includes("foreign pool"));
1791
+ iReckon(sir, hasForeignViolation).isGonnaBeTrue();
1792
+ });
1793
+ await ifWeMight(sir, 'verifies signing secrets correctly for a pool', async () => {
1794
+ const userSecret = "alice_secret_123";
1795
+ const builder = KeystoneProfileBuilder.buildKeystone('low')
1796
+ .withUsername('alice')
1797
+ .withEmail('alice@example.com')
1798
+ .withDescription('Alice test keystone');
1799
+ const configs = await builder.compileConfigs();
1800
+ const keystone = await service.genesis({
1801
+ masterSecret: userSecret,
1802
+ configs,
1803
+ frameDetails: builder.getFrameDetails(),
1804
+ metaspace: mockMetaspace,
1805
+ space: mockSpace,
1806
+ });
1807
+ // 1. Correct secret on manage pool
1808
+ const isCorrectManage = await service.verifySigningSecret({
1809
+ keystoneIbGib: keystone,
1810
+ signingSecret: userSecret,
1811
+ poolId: POOL_ID_MANAGE
1812
+ });
1813
+ iReckon(sir, isCorrectManage).asTo('correct manage secret').isGonnaBeTrue();
1814
+ // 2. Incorrect secret on manage pool
1815
+ const isWrongManage = await service.verifySigningSecret({
1816
+ keystoneIbGib: keystone,
1817
+ signingSecret: "wrong_secret",
1818
+ poolId: POOL_ID_MANAGE
1819
+ });
1820
+ iReckon(sir, isWrongManage).asTo('incorrect manage secret').isGonnaBeFalse();
1821
+ // 3. Correct secret on default/sign pool
1822
+ const isCorrectDefault = await service.verifySigningSecret({
1823
+ keystoneIbGib: keystone,
1824
+ signingSecret: userSecret,
1825
+ poolId: 'default'
1826
+ });
1827
+ iReckon(sir, isCorrectDefault).asTo('correct default secret').isGonnaBeTrue();
1828
+ // 4. Invalid/non-existent pool ID
1829
+ const isInvalidPool = await service.verifySigningSecret({
1830
+ keystoneIbGib: keystone,
1831
+ signingSecret: userSecret,
1832
+ poolId: 'invalid_pool_id_123'
1833
+ });
1834
+ iReckon(sir, isInvalidPool).asTo('invalid pool id').isGonnaBeFalse();
1835
+ });
1836
+ await ifWeMight(sir, 'removes pools successfully, validating transitions and auth', async () => {
1837
+ const userSecret = "alice_secret_123";
1838
+ const customPoolId = "custodian-manage-google";
1839
+ const configs = [
1840
+ createStandardPoolConfig({
1841
+ id: POOL_ID_MANAGE,
1842
+ salt: "manage_salt",
1843
+ }),
1844
+ createStandardPoolConfig({
1845
+ id: customPoolId,
1846
+ salt: "custodian_salt",
1847
+ })
1848
+ ];
1849
+ const keystone = await service.genesis({
1850
+ masterSecret: userSecret,
1851
+ configs,
1852
+ metaspace: mockMetaspace,
1853
+ space: mockSpace,
1854
+ });
1855
+ // 1. Success path: remove customPoolId using correct secret
1856
+ const evolvedKeystone = await service.removePools({
1857
+ latestKeystone: keystone,
1858
+ masterSecret: userSecret,
1859
+ poolIds: [customPoolId],
1860
+ metaspace: mockMetaspace,
1861
+ space: mockSpace,
1862
+ });
1863
+ iReckon(sir, evolvedKeystone).asTo('evolved keystone exists').isGonnaBeTruthy();
1864
+ iReckon(sir, evolvedKeystone.data?.challengePools.length).asTo('pool count reduced').willEqual(1);
1865
+ iReckon(sir, evolvedKeystone.data?.challengePools[0].id).asTo('manage pool remains').willEqual(POOL_ID_MANAGE);
1866
+ // Verify transition is fully valid
1867
+ const errors = await service.validate({
1868
+ prevIbGib: keystone,
1869
+ currentIbGib: evolvedKeystone
1870
+ });
1871
+ iReckon(sir, errors.length).asTo('transition validation succeeds').willEqual(0);
1872
+ // 2. Sad path: invalid master secret
1873
+ let cryptoErrorCaught = false;
1874
+ try {
1875
+ await service.removePools({
1876
+ latestKeystone: keystone,
1877
+ masterSecret: "wrong_secret",
1878
+ poolIds: [customPoolId],
1879
+ metaspace: mockMetaspace,
1880
+ space: mockSpace,
1881
+ });
1882
+ }
1883
+ catch (e) {
1884
+ if (e.message.includes('Crypto Violation')) {
1885
+ cryptoErrorCaught = true;
1886
+ }
1887
+ }
1888
+ iReckon(sir, cryptoErrorCaught).asTo('forgery check prevents removal').isGonnaBeTrue();
1889
+ // 3. Sad path: non-existent pool ID
1890
+ let notFoundErrorCaught = false;
1891
+ try {
1892
+ await service.removePools({
1893
+ latestKeystone: keystone,
1894
+ masterSecret: userSecret,
1895
+ poolIds: ["non-existent-pool"],
1896
+ metaspace: mockMetaspace,
1897
+ space: mockSpace,
1898
+ });
1899
+ }
1900
+ catch (e) {
1901
+ if (e.message.includes('not found in keystone')) {
1902
+ notFoundErrorCaught = true;
1903
+ }
1904
+ }
1905
+ iReckon(sir, notFoundErrorCaught).asTo('non-existent pool ID rejected').isGonnaBeTrue();
1906
+ });
1907
+ });
1314
1908
  //# sourceMappingURL=keystone-service-v1.respec.mjs.map