import { respecfully, iReckon, ifWe, firstOfAll } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
const maam = `[${import.meta.url}]`, sir = maam;
import { clone, hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
import { createRevocationPoolConfig, createStandardPoolConfig } from './keystone-config-builder.mjs';
import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE } from './keystone-constants.mjs';
import { KeystoneService_V1 } from './keystone-service-v1.mjs';
import { addToBindingMap } from './keystone-helpers.mjs';
const logalot = GLOBAL_LOG_A_LOT;
// /**
//  * not sure where to put this, but we probably will want to reuse this in the
//  * future (assuming it works)
//  * @returns metaspace service reference
//  */
// async function getNewInitializedInMemoryMetaspaceForTesting({
//     defaultSpaceName,
// }: {
//     defaultSpaceName: string,
// }): Promise<MetaspaceService> {
//     const lc = `[${getNewInitializedInMemoryMetaspaceForTesting.name}]`;
//     try {
//         if (logalot) { console.log(`${lc} starting... (I: 766d7596addcb73f4820586469233b25)`); }
//         let metaspace = new Metaspace_Innerspace(/*cacheSvc*/undefined);
//         if (logalot) { console.log(`${lc} creating metaspace complete. initializing... (I: 61b74d62e8832c9fa853e4b8c4c2d825)`); }
//         getGibInfo()
//         await metaspace.initialize({
//             spaceName: defaultSpaceName,
//             /**
//              * passing in undefined will use the defaults. probably will need to
//              * adjust this for testing purposes, but let's see what happens with
//              * this first.
//              */
//             metaspaceFactory: {
//                 fnDtoToSpace: async () => {
//                     if (!currentSpace) { currentSpace = new IbGibTestSpace(); }
//                     return currentSpace;
//                 },
//                 fnZeroSpaceFactory: () => {
//                     if (!currentZeroSpace) { currentZeroSpace = new IbGibTestSpace(); }
//                     return currentZeroSpace;
//                 },
//                 fnDefaultLocalSpaceFactory: async () => {
//                     if (!currentSpace) { currentSpace = new IbGibTestSpace(); }
//                     return currentSpace;
//                 },
//                 // export type DtoToSpaceFunction = (spaceDto: IbGib_V1) => Promise<IbGibSpaceAny>;
//                 // export type ZeroSpaceFactoryFunction = () => IbGibSpaceAny;
//                 // export type LocalSpaceFactoryFunction = (opts: CreateLocalSpaceOptions) => Promise<IbGibSpaceAny | undefined>;
//             },
//             getFnAlert: () => { return async ({ title, msg }) => console.log(title, msg) },
//             getFnPrompt: () => {
//                 return async ({ title, msg }) => {
//                     // if this is needed, we might set up some way for testing
//                     // to prepare either a queue of prompts or some kind of map or getter
//                     // and put it on the metaspace itself
//                     throw new Error(`not implemented (E: c7ef688a02f8cb74487260f9274ac825)`);
//                     // promptForText({ title, msg, confirm: false });
//                 }
//             },
//             getFnPromptPassword: () => {
//                 return async () => {
//                     // similar to getFnPrompt, if we need a _different_
//                     // password, we might set up some way for testing to prepare
//                     // either a queue of passwords or some kind of map or getter
//                     // and put it on the metaspace itself
//                     return 'password';
//                     // promptForSecret({ confirm: true })
//                 }
//             },
//         });
//         return metaspace;
//     } catch (error) {
//         console.error(`${lc} ${extractErrorMsg(error)}`);
//         throw error;
//     } finally {
//         if (logalot) { console.log(`${lc} complete.`); }
//     }
// }
/**
 * A simple in-memory map acting as a Space.
 * Pure Storage. No Indexing logic.
 */
class MockIbGibSpace {
    name;
    store = new Map();
    constructor(name = "mock_space") {
        this.name = name;
    }
    async put({ ibGib }) {
        const addr = getIbGibAddr({ ibGib });
        this.store.set(addr, JSON.parse(JSON.stringify(ibGib))); // Deep copy
    }
    async get({ addr }) {
        const data = this.store.get(addr);
        return data ? JSON.parse(JSON.stringify(data)) : null;
    }
    async witness(arg) {
        const cmd = arg.data?.cmd;
        if (cmd === 'get') {
            const addrs = arg.data.ibGibAddrs || [];
            const ibGibs = [];
            for (const addr of addrs) {
                const ig = await this.get({ addr });
                if (ig)
                    ibGibs.push(ig);
            }
            return { ibGibs };
        }
        return undefined;
    }
}
/**
 * A partial mock of Metaspace.
 * Handles:
 * 1. Retrieving the local space.
 * 2. Delegating 'put' to the space.
 * 3. 'registerNewIbGib': Tracking the HEAD of a timeline.
 */
class MockMetaspaceService {
    space;
    /**
     * Map of TJP Gib (Timeline ID) -> Latest IbGib Addr (Head)
     */
    timelineHeads = new Map();
    constructor(space) {
        this.space = space;
    }
    async getLocalUserSpace({ lock }) {
        return this.space;
    }
    /**
     * Metaspace often acts as a facade for put, defaulting to local space.
     */
    async put(args) {
        const target = args.space || this.space;
        return target.put(args);
    }
    /**
     * Tracks the latest version of an ibGib timeline.
     */
    async registerNewIbGib(args) {
        const { ibGib } = args;
        const targetSpace = args.space || this.space;
        // 1. Ensure it is stored
        await targetSpace.put({ ibGib });
        // 2. Extract TJP (Timeline Identifier)
        // Simplified logic mirroring getGibInfo
        const gib = ibGib.gib || '';
        let tjpGib = gib;
        if (gib.includes('.')) {
            // It's a frame in a timeline: "punctiliarHash.tjpHash"
            // The TJP is the suffix.
            const parts = gib.split('.');
            tjpGib = parts.slice(1).join('.');
        }
        // Else: It's a Primitive or a TJP itself (Genesis).
        // If Genesis (isTjp=true), the gib IS the tjpGib.
        const addr = getIbGibAddr({ ibGib });
        this.timelineHeads.set(tjpGib, addr);
    }
}
// ===========================================================================
// SUITE A: STRATEGY VECTORS (The Math)
// ===========================================================================
await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
    // Setup generic variables
    const masterSecret = "TestSecret_12345";
    const salt = "TestPool";
    let config;
    firstOfAll(sir, async () => {
        // Use our standard builder to get a valid config object
        config = createStandardPoolConfig(salt);
    });
    await respecfully(sir, 'Derivation Logic', async () => {
        await ifWe(sir, 'derivePoolSecret with same inputs returns same output', async () => {
            const strategy = KeystoneStrategyFactory.create({ config });
            const secretA = await strategy.derivePoolSecret({ masterSecret });
            const secretB = await strategy.derivePoolSecret({ masterSecret });
            iReckon(sir, secretA).asTo('secret consistency').willEqual(secretB);
            iReckon(sir, secretA).asTo('secret length').isGonnaBeTruthy();
        });
        await ifWe(sir, 'derivePoolSecret with different master secret returns different output', async () => {
            const strategy = KeystoneStrategyFactory.create({ config });
            const secretA = await strategy.derivePoolSecret({ masterSecret });
            const secretB = await strategy.derivePoolSecret({ masterSecret: masterSecret + "_diff" });
            iReckon(sir, secretA).asTo('secrets differ').not.willEqual(secretB);
        });
        await ifWe(sir, 'derivePoolSecret with different salt returns different output', async () => {
            // Modify salt in a copy of config
            const configB = { ...config, salt: "OtherPool" };
            const strategyA = KeystoneStrategyFactory.create({ config });
            const strategyB = KeystoneStrategyFactory.create({ config: configB });
            const secretA = await strategyA.derivePoolSecret({ masterSecret });
            const secretB = await strategyB.derivePoolSecret({ masterSecret });
            iReckon(sir, secretA).asTo('salt affects secret').not.willEqual(secretB);
        });
    });
    await respecfully(sir, 'Challenge/Solution Logic', async () => {
        await ifWe(sir, 'generateSolution -> generateChallenge -> validateSolution loop works', async () => {
            const strategy = KeystoneStrategyFactory.create({ config });
            const poolSecret = await strategy.derivePoolSecret({ masterSecret });
            const challengeId = "a3ff7843552870fc28bef2b"; // arbitrary random challengeId
            // 1. Generate Solution
            const solution = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId });
            iReckon(sir, solution.value).asTo('solution value exists').isGonnaBeTruthy();
            iReckon(sir, solution.challengeId).asTo('id matches').willEqual(challengeId);
            // 2. Generate Public Challenge from Solution
            const challenge = await strategy.generateChallenge({ solution });
            iReckon(sir, challenge.hash).asTo('challenge hash exists').isGonnaBeTruthy();
            // 3. Validate
            const isValid = await strategy.validateSolution({ solution, challenge });
            iReckon(sir, isValid).asTo('valid pair should pass').isGonnaBeTrue();
        });
        await ifWe(sir, 'validateSolution fails for mismatched values', async () => {
            const strategy = KeystoneStrategyFactory.create({ config });
            const poolSecret = await strategy.derivePoolSecret({ masterSecret });
            const challengeId = "8c994f3ed598f150e25513"; // arbitrary random challengeId
            // Generate real pair
            const solution = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId });
            const challenge = await strategy.generateChallenge({ solution });
            // Tamper with solution value
            const badSolution = { ...solution, value: "hacked_value" };
            const isValid = await strategy.validateSolution({ solution: badSolution, challenge });
            iReckon(sir, isValid).asTo('tampered solution should fail').isGonnaBeFalse();
        });
        await ifWe(sir, 'validateSolution fails for mismatched challenge hashes', async () => {
            const strategy = KeystoneStrategyFactory.create({ config });
            const poolSecret = await strategy.derivePoolSecret({ masterSecret });
            // Generate pair A
            const challengeId_A = "416c38cfd6ee63dbf8d4e5ef36"; // arbitrary random challengeId
            const solutionA = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId: challengeId_A });
            // Generate pair B
            const challengeId_B = "c487ef6b7878fae798c3"; // arbitrary random challengeId
            const solutionB = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId: challengeId_B });
            const challengeB = await strategy.generateChallenge({ solution: solutionB });
            // Check A against B
            const isValid = await strategy.validateSolution({ solution: solutionA, challenge: challengeB });
            iReckon(sir, isValid).asTo('mismatched pair should fail').isGonnaBeFalse();
        });
    });
});
// ===========================================================================
// SUITE B: SERVICE LIFECYCLE (Genesis -> Sign -> Validate)
// ===========================================================================
await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
    const service = new KeystoneService_V1();
    const masterSecret = "AliceSecretKey_987654321";
    let mockSpace;
    let mockMetaspace;
    let genesisKeystone;
    let signedKeystone;
    firstOfAll(sir, async () => {
        mockSpace = new MockIbGibSpace();
        mockMetaspace = new MockMetaspaceService(mockSpace);
    });
    await respecfully(sir, 'Genesis', async () => {
        await ifWe(sir, 'creates a valid genesis frame and persists it', async () => {
            const config = createStandardPoolConfig(POOL_ID_DEFAULT);
            genesisKeystone = await service.genesis({
                masterSecret,
                configs: [config],
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            // Verify Object
            iReckon(sir, genesisKeystone).asTo('genesis object').isGonnaBeTruthy();
            iReckon(sir, genesisKeystone.data?.isTjp).asTo('isTjp').isGonnaBeTrue();
            // Verify Persistence
            const addr = getIbGibAddr({ ibGib: genesisKeystone });
            const saved = await mockSpace.get({ addr });
            iReckon(sir, saved).asTo('persisted to space').isGonnaBeTruthy();
            // Verify Registration (Timeline Tracking)
            // Genesis gib should be registered as a timeline head
            const head = mockMetaspace.timelineHeads.get(genesisKeystone.gib);
            iReckon(sir, head).asTo('genesis registered as timeline head').willEqual(addr);
        });
    });
    await respecfully(sir, 'Signing (Evolution)', async () => {
        await ifWe(sir, 'evolves the keystone with a valid proof', async () => {
            const claim = {
                target: "comment 123^gib",
                verb: "post"
            };
            const details = { note: "First post!" };
            signedKeystone = await service.sign({
                latestKeystone: genesisKeystone,
                masterSecret,
                claim,
                poolId: POOL_ID_DEFAULT,
                frameDetails: details,
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            iReckon(sir, signedKeystone).asTo('new frame created').isGonnaBeTruthy();
            iReckon(sir, signedKeystone).asTo('is different frame').not.isGonnaBe(genesisKeystone);
            // NOTE: If this fails, check if 'sign' calls 'space.put' or 'metaspace.put'!
            // In your current 'sign' implementation, you return the object but might have missed the save step.
            const addr = getIbGibAddr({ ibGib: signedKeystone });
            const saved = await mockSpace.get({ addr });
            iReckon(sir, saved).asTo('persisted to space').isGonnaBeTruthy();
        });
    });
    await respecfully(sir, 'Validation', async () => {
        await ifWe(sir, 'validates the genesis->signed transition', async () => {
            const errors = await service.validate({
                prevIbGib: genesisKeystone,
                currentIbGib: signedKeystone,
                // metaspace: mockMetaspace,
                // space: mockSpace as any,
            });
            iReckon(sir, errors.length).asTo('signature validation has no errors').willEqual(0);
        });
    });
});
// ===========================================================================
// SUITE C: SECURITY & SAD PATHS
// ===========================================================================
await respecfully(sir, 'Suite C: Security Vectors', async () => {
    const service = new KeystoneService_V1();
    const aliceSecret = "AliceSecret_111";
    const eveSecret = "EveSecret_666";
    let mockSpace;
    let mockMetaspace;
    let genesisKeystone;
    firstOfAll(sir, async () => {
        mockSpace = new MockIbGibSpace();
        mockMetaspace = new MockMetaspaceService(mockSpace);
        // Setup Alice's Identity
        const config = createStandardPoolConfig(POOL_ID_DEFAULT);
        config.behavior.size = 10;
        genesisKeystone = await service.genesis({
            masterSecret: aliceSecret,
            configs: [config],
            metaspace: mockMetaspace,
            space: mockSpace,
        });
    });
    await respecfully(sir, 'Wrong Secret (Forgery)', async () => {
        await ifWe(sir, 'prevents creation of forged frames', async () => {
            const claim = { target: "comment 123^gib", verb: "post" };
            let errorCaught = false;
            let errorMsg = "";
            try {
                // Eve tries to sign Alice's keystone.
                // This MUST fail because sign() calls evolve(), which calls validate().
                await service.sign({
                    latestKeystone: genesisKeystone,
                    masterSecret: eveSecret,
                    claim,
                    poolId: POOL_ID_DEFAULT,
                    metaspace: mockMetaspace,
                    space: mockSpace,
                });
            }
            catch (e) {
                errorCaught = true;
                errorMsg = e.message;
            }
            iReckon(sir, errorCaught).asTo('service rejected forgery').isGonnaBeTrue();
            // Verify it was a crypto error, not something else
            iReckon(sir, errorMsg).asTo('error mentions crypto violation').includes('Crypto Violation');
        });
    });
    await respecfully(sir, 'Policy Violation (Restricted Verbs)', async () => {
        await ifWe(sir, 'throws error if signing forbidden verb with restricted pool', async () => {
            // Create a specific restricted pool config manually
            const restrictedPoolId = "read_only_pool";
            const restrictedConfig = createStandardPoolConfig(restrictedPoolId);
            // Manually restrict it (since Builder defaults to undefined/allow-all)
            restrictedConfig.allowedVerbs = ['read'];
            const restrictedGenesis = await service.genesis({
                masterSecret: aliceSecret,
                configs: [restrictedConfig],
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            // Try to sign "write" using "read_only_pool"
            const claim = { target: "data^gib", verb: "write" };
            let errorCaught = false;
            try {
                await service.sign({
                    latestKeystone: restrictedGenesis,
                    masterSecret: aliceSecret,
                    claim,
                    poolId: restrictedPoolId, // Force use of restricted pool
                    metaspace: mockMetaspace,
                    space: mockSpace,
                });
            }
            catch (e) {
                errorCaught = true;
                // Optional: Check error message contains "not authorized"
            }
            iReckon(sir, errorCaught).asTo('policy enforced').isGonnaBeTrue();
        });
    });
});
// ===========================================================================
// SUITE D: REVOCATION
// ===========================================================================
await respecfully(sir, 'Suite D: Revocation', async () => {
    const service = new KeystoneService_V1();
    const masterSecret = "AliceSecret_RevokeTest";
    let mockSpace;
    let mockMetaspace;
    let genesisKeystone;
    firstOfAll(sir, async () => {
        mockSpace = new MockIbGibSpace();
        mockMetaspace = new MockMetaspaceService(mockSpace);
        // Setup Identity WITH a Revocation Pool
        const stdConfig = createStandardPoolConfig(POOL_ID_DEFAULT);
        const revokeConfig = createRevocationPoolConfig(POOL_ID_REVOKE); // Special Config
        genesisKeystone = await service.genesis({
            masterSecret,
            configs: [stdConfig, revokeConfig],
            metaspace: mockMetaspace,
            space: mockSpace,
        });
    });
    await respecfully(sir, 'Revoke Lifecycle', async () => {
        let revokedKeystone;
        await ifWe(sir, 'successfully creates a revocation frame', async () => {
            revokedKeystone = await service.revoke({
                latestKeystone: genesisKeystone,
                masterSecret,
                reason: "Key compromised",
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            iReckon(sir, revokedKeystone).isGonnaBeTruthy();
            // Check Data
            const data = revokedKeystone.data;
            iReckon(sir, data.revocationInfo).asTo('revocation info present').isGonnaBeTruthy();
            iReckon(sir, data.revocationInfo.reason).willEqual("Key compromised");
            iReckon(sir, data.revocationInfo.proof.claim.verb).willEqual(KEYSTONE_VERB_REVOKE);
        });
        await ifWe(sir, 'validates the revocation frame', async () => {
            const errors = await service.validate({
                prevIbGib: genesisKeystone,
                currentIbGib: revokedKeystone,
                // metaspace: mockMetaspace,
                // space: mockSpace as any,
            });
            iReckon(sir, errors.length).asTo('no validation errors').willEqual(0);
        });
        await ifWe(sir, 'consumed the revocation pool (Scorched Earth)', async () => {
            const data = revokedKeystone.data;
            const revokePool = data.challengePools.find(p => p.id === POOL_ID_REVOKE);
            // The pool should exist...
            iReckon(sir, revokePool).isGonnaBeTruthy();
            // Should be empty (0 challenges)
            const remaining = Object.keys(revokePool.challenges);
            iReckon(sir, remaining.length).asTo('pool depleted').willEqual(0);
        });
    });
});
// ===========================================================================
// SUITE E: STRUCTURAL EVOLUTION (addPools)
// ===========================================================================
await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
    const service = new KeystoneService_V1();
    const aliceSecret = "Alice_Master_Key";
    const bobSecret = "Bob_Foreign_Key";
    let mockSpace;
    let mockMetaspace;
    let aliceKeystone;
    // Helper to generate a "Foreign" pool (e.g. from Bob)
    const createForeignPool = async (id, verbs = []) => {
        const config = createStandardPoolConfig(id);
        config.allowedVerbs = verbs;
        // We use the factory manually here to simulate Bob doing this offline
        const strategy = KeystoneStrategyFactory.create({ config });
        const poolSecret = await strategy.derivePoolSecret({ masterSecret: bobSecret });
        const challenges = {};
        const bindingMap = {};
        for (let i = 0; i < 10; i++) {
            // Manually replicate ID gen for test
            const raw = await hash({ s: `${config.salt}${Date.now()}${i}` });
            const challengeId = raw.substring(0, 16);
            const solution = await strategy.generateSolution({
                poolSecret, poolId: config.salt, challengeId,
            });
            const challenge = await strategy.generateChallenge({ solution });
            challenges[challengeId] = challenge;
            addToBindingMap(bindingMap, challengeId);
        }
        return {
            id,
            config,
            challenges,
            bindingMap,
            isForeign: true,
            metadata: { owner: 'Bob' }
        };
    };
    firstOfAll(sir, async () => {
        mockSpace = new MockIbGibSpace();
        mockMetaspace = new MockMetaspaceService(mockSpace);
        // Alice Genesis: Standard pool (allows all verbs, including 'manage')
        const config = createStandardPoolConfig(POOL_ID_DEFAULT);
        aliceKeystone = await service.genesis({
            masterSecret: aliceSecret,
            configs: [config],
            metaspace: mockMetaspace,
            space: mockSpace,
        });
    });
    await respecfully(sir, 'Happy Path', async () => {
        await ifWe(sir, 'authorizes and adds a foreign pool', async () => {
            const bobPool = await createForeignPool("pool_bob", ["post"]);
            const updatedKeystone = await service.addPools({
                latestKeystone: aliceKeystone,
                masterSecret: aliceSecret,
                newPools: [bobPool],
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            // 1. Verify new state
            iReckon(sir, updatedKeystone).isGonnaBeTruthy();
            const pools = updatedKeystone.data.challengePools;
            iReckon(sir, pools.length).asTo('pool count increased').willEqual(2);
            const foundBob = pools.find(p => p.id === "pool_bob");
            iReckon(sir, foundBob).asTo('bob pool exists').isGonnaBeTruthy();
            iReckon(sir, foundBob.isForeign).asTo('isForeign flag').isGonnaBeTrue();
            // 2. Verify Proof
            const proof = updatedKeystone.data.proofs[0];
            iReckon(sir, proof.claim.verb).asTo('proof verb').willEqual("manage");
            // Alice signed this using HER pool (default), not Bob's
            iReckon(sir, proof.solutions[0].poolId).willEqual(POOL_ID_DEFAULT);
            // 3. Verify Validity
            const errors = await service.validate({
                prevIbGib: aliceKeystone,
                currentIbGib: updatedKeystone,
            });
            iReckon(sir, errors.length).asTo('validation passed').willEqual(0);
            // Update local ref for next tests
            aliceKeystone = updatedKeystone;
        });
    });
    await respecfully(sir, 'Permissions & Logic', async () => {
        await ifWe(sir, 'fails if no pool allows "manage" verb', async () => {
            // 1. Create a restricted keystone
            const restrictedConfig = createStandardPoolConfig("read_only");
            restrictedConfig.allowedVerbs = ['read']; // No 'manage'
            const restrictedKeystone = await service.genesis({
                masterSecret: aliceSecret,
                configs: [restrictedConfig],
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            const newPool = await createForeignPool("pool_test");
            let errorCaught = false;
            try {
                await service.addPools({
                    latestKeystone: restrictedKeystone,
                    masterSecret: aliceSecret,
                    newPools: [newPool],
                    metaspace: mockMetaspace,
                    space: mockSpace,
                });
            }
            catch (e) {
                errorCaught = true;
                // Should fail in resolveTargetPool or addPools logic
                // console.log("Caught expected error:", e.message);
            }
            iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
        });
        await ifWe(sir, 'fails on ID collision', async () => {
            // Try to add "pool_bob" again (it was added in Happy Path)
            const duplicatePool = await createForeignPool("pool_bob");
            let errorCaught = false;
            try {
                await service.addPools({
                    latestKeystone: aliceKeystone, // This already has pool_bob
                    masterSecret: aliceSecret,
                    newPools: [duplicatePool],
                    metaspace: mockMetaspace,
                    space: mockSpace,
                });
            }
            catch (e) {
                errorCaught = true;
                iReckon(sir, e.message).includes("ID collision");
            }
            iReckon(sir, errorCaught).asTo('collision detected').isGonnaBeTrue();
        });
    });
});
// ===========================================================================
// SUITE E: STRUCTURAL EVOLUTION (addPools)
// ===========================================================================
await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
    const service = new KeystoneService_V1();
    const aliceSecret = "Alice_Master_Key";
    const bobSecret = "Bob_Foreign_Key";
    let mockSpace;
    let mockMetaspace;
    let aliceKeystone;
    // Helper to simulate Bob creating a pool "offline" to give to Alice
    const createForeignPool = async (id, verbs = []) => {
        const config = createStandardPoolConfig(id);
        config.allowedVerbs = verbs;
        // We use the factory manually here to simulate Bob doing this on his own machine
        const strategy = KeystoneStrategyFactory.create({ config });
        const poolSecret = await strategy.derivePoolSecret({ masterSecret: bobSecret });
        const challenges = {};
        const bindingMap = {};
        // Generate a small set of challenges
        for (let i = 0; i < 10; i++) {
            const raw = await hash({ s: `${config.salt}${Date.now()}${i}` });
            const challengeId = raw.substring(0, 16);
            const solution = await strategy.generateSolution({
                poolSecret, poolId: config.salt, challengeId,
            });
            const challenge = await strategy.generateChallenge({ solution });
            challenges[challengeId] = challenge;
            addToBindingMap(bindingMap, challengeId);
        }
        return {
            id,
            config,
            challenges,
            bindingMap,
            isForeign: true,
            metadata: { owner: 'Bob', role: 'Delegate' }
        };
    };
    firstOfAll(sir, async () => {
        mockSpace = new MockIbGibSpace();
        mockMetaspace = new MockMetaspaceService(mockSpace);
        // Alice Genesis: Standard pool (allows all verbs, including 'manage')
        const config = createStandardPoolConfig(POOL_ID_DEFAULT);
        aliceKeystone = await service.genesis({
            masterSecret: aliceSecret,
            configs: [config],
            metaspace: mockMetaspace,
            space: mockSpace,
        });
    });
    await respecfully(sir, 'Happy Path', async () => {
        await ifWe(sir, 'authorizes and adds a foreign pool', async () => {
            const bobPool = await createForeignPool("pool_bob", ["post"]);
            const updatedKeystone = await service.addPools({
                latestKeystone: aliceKeystone,
                masterSecret: aliceSecret,
                newPools: [bobPool],
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            // 1. Verify new state
            iReckon(sir, updatedKeystone).isGonnaBeTruthy();
            const pools = updatedKeystone.data.challengePools;
            iReckon(sir, pools.length).asTo('pool count increased').willEqual(2);
            const foundBob = pools.find(p => p.id === "pool_bob");
            iReckon(sir, foundBob).asTo('bob pool exists').isGonnaBeTruthy();
            iReckon(sir, foundBob.isForeign).asTo('isForeign flag').isGonnaBeTrue();
            // 2. Verify Proof
            const proof = updatedKeystone.data.proofs[0];
            iReckon(sir, proof.claim.verb).asTo('proof verb').willEqual(KEYSTONE_VERB_MANAGE);
            // Alice signed this using HER pool (default), not Bob's
            iReckon(sir, proof.solutions[0].poolId).willEqual(POOL_ID_DEFAULT);
            // 3. Verify Validity
            const errors = await service.validate({
                prevIbGib: aliceKeystone,
                currentIbGib: updatedKeystone,
            });
            iReckon(sir, errors.length).asTo('validation passed').willEqual(0);
            // Update local ref for next tests
            aliceKeystone = updatedKeystone;
        });
    });
    await respecfully(sir, 'Permissions & Logic', async () => {
        await ifWe(sir, 'fails if no pool allows "manage" verb', async () => {
            // 1. Create a restricted keystone (read-only)
            const restrictedConfig = createStandardPoolConfig("read_only");
            restrictedConfig.allowedVerbs = ['read']; // No 'manage'
            const restrictedKeystone = await service.genesis({
                masterSecret: aliceSecret,
                configs: [restrictedConfig],
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            const newPool = await createForeignPool("pool_test");
            let errorCaught = false;
            try {
                await service.addPools({
                    latestKeystone: restrictedKeystone,
                    masterSecret: aliceSecret,
                    newPools: [newPool],
                    metaspace: mockMetaspace,
                    space: mockSpace,
                });
            }
            catch (e) {
                errorCaught = true;
                // Optional: Check error message
                // iReckon(sir, e.message).includes("No local pool found with 'manage'");
            }
            iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
        });
        await ifWe(sir, 'fails on ID collision', async () => {
            // Try to add "pool_bob" again (it was added in Happy Path)
            const duplicatePool = await createForeignPool("pool_bob");
            let errorCaught = false;
            try {
                await service.addPools({
                    latestKeystone: aliceKeystone, // This already has pool_bob
                    masterSecret: aliceSecret,
                    newPools: [duplicatePool],
                    metaspace: mockMetaspace,
                    space: mockSpace,
                });
            }
            catch (e) {
                errorCaught = true;
                iReckon(sir, e.message).includes("collision");
            }
            iReckon(sir, errorCaught).asTo('collision detected').isGonnaBeTrue();
        });
    });
});
// ===========================================================================
// SUITE F: DEEP INSPECTION (Granularity & Serialization)
// ===========================================================================
await respecfully(sir, 'Suite F: Deep Inspection', async () => {
    const service = new KeystoneService_V1();
    const aliceSecret = "Alice_Deep_Inspect";
    const salt = "granularity_pool";
    let mockSpace;
    let mockMetaspace;
    let genesisKeystone;
    let signedKeystone;
    // We use a specific hybrid config to test exact selection logic
    const hybridConfig = createStandardPoolConfig(salt);
    // 2 FIFO + 2 Random = 4 Total per sign
    hybridConfig.behavior.selectSequentially = 2;
    hybridConfig.behavior.selectRandomly = 2;
    hybridConfig.behavior.size = 20; // Small enough to track, large enough to be random
    firstOfAll(sir, async () => {
        mockSpace = new MockIbGibSpace();
        mockMetaspace = new MockMetaspaceService(mockSpace);
        genesisKeystone = await service.genesis({
            masterSecret: aliceSecret,
            configs: [hybridConfig],
            metaspace: mockMetaspace,
            space: mockSpace,
        });
    });
    await respecfully(sir, 'Proof Granularity & Math', async () => {
        await ifWe(sir, 'generates exactly the expected number of solutions', async () => {
            signedKeystone = await service.sign({
                latestKeystone: genesisKeystone,
                masterSecret: aliceSecret,
                claim: { verb: "post", target: "data^gib" },
                metaspace: mockMetaspace,
                space: mockSpace,
            });
            const proofs = signedKeystone.data.proofs;
            iReckon(sir, proofs.length).asTo('proof count').willEqual(1);
            const solutions = proofs[0].solutions;
            // 2 Sequential + 2 Random = 4
            iReckon(sir, solutions.length).asTo('solution count').willEqual(4);
        });
        await ifWe(sir, 'verifies the math manually (White-box Crypto Check)', async () => {
            const proof = signedKeystone.data.proofs[0];
            const poolSnapshot = genesisKeystone.data.challengePools.find(p => p.id === salt);
            // We iterate every solution in the proof and MANUALLY verify the hash relationship
            // bypassing the Service's validation logic to ensure the raw math holds up.
            for (const solution of proof.solutions) {
                // 1. Find the challenge in the *Previous* frame (Genesis)
                const challenge = poolSnapshot.challenges[solution.challengeId];
                if (!challenge) {
                    throw new Error(`Test Failure: Solution references ID ${solution.challengeId} which was not in Genesis pool.`);
                }
                // 2. Re-implement HashReveal V1 verification logic locally in the test
                // Hash(Salt + Value + Salt)
                // Note: rounds=1 in standard config
                const indexSalt = solution.challengeId;
                const calculatedHash = await hash({
                    s: `${indexSalt}${solution.value}${indexSalt}`,
                    algorithm: 'SHA-256'
                });
                // 3. Assert
                iReckon(sir, calculatedHash).asTo(`Manual hash verification for ${solution.challengeId}`).willEqual(challenge.hash);
            }
        });
        await ifWe(sir, 'verifies FIFO logic (Deterministic Selection)', async () => {
            const proof = signedKeystone.data.proofs[0];
            const poolSnapshot = genesisKeystone.data.challengePools.find(p => p.id === salt);
            // The first N keys in the pool should be the FIFO targets.
            // Assumption: Object.keys returns insertion order (Standard in modern JS engines)
            const allIds = Object.keys(poolSnapshot.challenges);
            const expectedFifoIds = allIds.slice(0, 2);
            const solvedIds = proof.solutions.map(s => s.challengeId);
            // Check that our solution list *includes* the expected FIFO IDs
            const hasFirst = solvedIds.includes(expectedFifoIds[0]);
            const hasSecond = solvedIds.includes(expectedFifoIds[1]);
            iReckon(sir, hasFirst).asTo(`Solution includes 1st FIFO ID (${expectedFifoIds[0]})`).isGonnaBeTrue();
            iReckon(sir, hasSecond).asTo(`Solution includes 2nd FIFO ID (${expectedFifoIds[1]})`).isGonnaBeTrue();
        });
    });
    await respecfully(sir, 'DTO & Serialization', async () => {
        await ifWe(sir, 'survives a clone/JSON-cycle without corruption', async () => {
            // 1. Create a DTO (simulate network transmission/storage)
            // 'clone' does a JSON stringify/parse under the hood (usually) or structured clone.
            const dto = clone(signedKeystone);
            // 2. Structural checks
            iReckon(sir, dto).asTo('dto exists').isGonnaBeTruthy();
            iReckon(sir, dto.data).asTo('dto data').isGonnaBeTruthy();
            iReckon(sir, dto.data.proofs).asTo('dto proofs').isGonnaBeTruthy();
            // 3. Functional check: Can the service validate this DTO?
            // This ensures no prototypes or hidden properties were lost that the service depends on.
            const errors = await service.validate({
                prevIbGib: genesisKeystone,
                currentIbGib: dto, // Passing the DTO, not the original object
            });
            iReckon(sir, errors.length).asTo('DTO validation errors').willEqual(0);
        });
        await ifWe(sir, 'ensures data contains no functions or circular refs', async () => {
            // A crude but effective test: ensure JSON.stringify doesn't throw
            // and the result is equal to the object (if we parsed it back).
            const jsonStr = JSON.stringify(signedKeystone);
            const parsed = JSON.parse(jsonStr);
            // Compare specific deep fields
            const originalSolution = signedKeystone.data.proofs[0].solutions[0].value;
            const parsedSolution = parsed.data.proofs[0].solutions[0].value;
            iReckon(sir, parsedSolution).asTo('deep property survives stringify').willEqual(originalSolution);
            // Ensure no extra properties were lost
            // FIX: JSON.stringify removes keys with 'undefined' values. 
            // We must filter the original keys to match this behavior for a fair comparison.
            const origKeys = Object.keys(signedKeystone.data)
                .filter(k => signedKeystone.data[k] !== undefined);
            const parsedKeys = Object.keys(parsed.data);
            iReckon(sir, parsedKeys.length).asTo('key count matches').willEqual(origKeys.length);
        });
    });
});
//# sourceMappingURL=keystone-service-v1.respec.mjs.map