// ══════════════════════════════════════════════════════════════════
// VolatileBackend — In-memory StorageBackend for testing
// ══════════════════════════════════════════════════════════════════
// WARNING: All state is lost on process restart.
// This is NOT suitable for production. Use @aeoess/storage-sqlite.
// This backend exists for testing and development ONLY.
// ══════════════════════════════════════════════════════════════════
import { canonicalize } from '../core/canonical.js';
import { sign } from '../crypto/keys.js';
export class VolatileBackend {
    agents = new Map();
    delegations = new Map();
    revocations = new Map();
    receipts = [];
    reputations = new Map(); // key: agentId:scope
    demotions = [];
    keyRotations = [];
    nonces = new Map(); // requestId → expiresAt
    spendReservations = new Map();
    committedSpend = new Map(); // delegationId → total spent
    checkpoints = [];
    checkpointCallbacks = [];
    _initialized = false;
    async initialize() { this._initialized = true; }
    async close() { this._initialized = false; }
    async transaction(fn) {
        // In-memory: no real transaction needed, but we snapshot state
        // so we can roll back on error
        const snapshot = this._snapshot();
        try {
            return await fn(this);
        }
        catch (e) {
            this._restore(snapshot);
            throw e;
        }
    }
    // ── Agents ──
    async putAgent(agent) { this.agents.set(agent.agentId, agent); }
    async getAgent(agentId) { return this.agents.get(agentId) ?? null; }
    async listAgents() { return [...this.agents.values()]; }
    // ── Delegations ──
    async putDelegation(d) { this.delegations.set(d.delegationId, d); }
    async getDelegation(id) { return this.delegations.get(id) ?? null; }
    async getDelegationsForAgent(pubKey) {
        return [...this.delegations.values()].filter(d => d.delegatedTo === pubKey);
    }
    // ── Spend (reserve/commit/release) ──
    async reserveSpend(delegationId, amount, currency, ttlSeconds = 30) {
        const d = this.delegations.get(delegationId);
        if (!d)
            return { success: false, reason: 'Delegation not found' };
        const committed = this.committedSpend.get(delegationId) ?? 0;
        const reserved = this._pendingReserved(delegationId);
        const total = committed + reserved + amount;
        if (d.spendLimit !== undefined && total > d.spendLimit) {
            return { success: false, currentSpent: committed, limit: d.spendLimit, reason: `Spend $${total} exceeds limit $${d.spendLimit}` };
        }
        const rid = `res_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
        this.spendReservations.set(rid, {
            reservationId: rid, delegationId, amount, currency,
            expiresAt: Date.now() + ttlSeconds * 1000, status: 'reserved'
        });
        return { success: true, reservationId: rid, currentSpent: committed, limit: d.spendLimit };
    }
    async commitSpend(reservationId) {
        const r = this.spendReservations.get(reservationId);
        if (!r || r.status !== 'reserved')
            return false;
        if (Date.now() > r.expiresAt) {
            r.status = 'released';
            return false;
        }
        r.status = 'committed';
        const prev = this.committedSpend.get(r.delegationId) ?? 0;
        this.committedSpend.set(r.delegationId, prev + r.amount);
        return true;
    }
    async releaseSpend(reservationId) {
        const r = this.spendReservations.get(reservationId);
        if (!r || r.status !== 'reserved')
            return false;
        r.status = 'released';
        return true;
    }
    async getSpentAmount(delegationId) {
        return this.committedSpend.get(delegationId) ?? 0;
    }
    _pendingReserved(delegationId) {
        const now = Date.now();
        let total = 0;
        for (const r of this.spendReservations.values()) {
            if (r.delegationId === delegationId && r.status === 'reserved' && now < r.expiresAt)
                total += r.amount;
        }
        return total;
    }
    // ── Revocations ──
    async appendRevocation(rev) { this.revocations.set(rev.delegationId, rev); }
    async isRevoked(delegationId) { return this.revocations.has(delegationId); }
    async getRevocationsBy(revokedBy) {
        return [...this.revocations.values()].filter(r => r.revokedBy === revokedBy);
    }
    // ── Receipts (append-only) ──
    async appendReceipt(receipt) { this.receipts.push(receipt); }
    async getReceipt(receiptId) {
        return this.receipts.find(r => r.receiptId === receiptId) ?? null;
    }
    async queryReceipts(filter, limit = 50, cursor) {
        let items = this.receipts;
        if (filter.agentId)
            items = items.filter(r => r.agentId === filter.agentId);
        if (filter.delegationId)
            items = items.filter(r => r.delegationId === filter.delegationId);
        if (filter.after)
            items = items.filter(r => r.timestamp > filter.after);
        if (filter.before)
            items = items.filter(r => r.timestamp < filter.before);
        const startIdx = cursor ? parseInt(cursor, 10) : 0;
        const page = items.slice(startIdx, startIdx + limit);
        const hasMore = startIdx + limit < items.length;
        return { items: page, hasMore, nextCursor: hasMore ? String(startIdx + limit) : undefined };
    }
    async getReceiptCount(agentId, scope) {
        let items = this.receipts;
        if (agentId)
            items = items.filter(r => r.agentId === agentId);
        if (scope)
            items = items.filter(r => r.action?.scopeUsed === scope);
        return items.length;
    }
    async tombstoneReceipt(receiptId, reason) {
        const r = this.receipts.find(r => r.receiptId === receiptId);
        if (!r)
            return false;
        r.tombstoned = true;
        r.tombstoneReason = reason;
        r.action = { type: '[REDACTED]', target: '[REDACTED]', scopeUsed: r.action.scopeUsed };
        r.result = { status: r.result.status, summary: '[REDACTED]' };
        return true;
    }
    // ── Reputation (derived cache) ──
    async getReputation(agentId, scope) {
        return this.reputations.get(`${agentId}:${scope}`) ?? null;
    }
    async putReputation(rep) {
        this.reputations.set(`${rep.agentId}:${rep.scope}`, rep);
    }
    // ── Demotions (permanent, append-only) ──
    async appendDemotion(d) { this.demotions.push(d); }
    async getDemotionCount(agentId) {
        return this.demotions.filter(d => d.agentId === agentId).length;
    }
    async getDemotions(agentId) {
        return this.demotions.filter(d => d.agentId === agentId);
    }
    // ── Key Rotations (append-only) ──
    async appendKeyRotation(entry) { this.keyRotations.push(entry); }
    async getKeyRotations(publicKey) {
        return this.keyRotations.filter(k => k.oldPublicKey === publicKey || k.newPublicKey === publicKey);
    }
    // ── Replay Protection ──
    async checkAndStoreNonce(requestId, ttlSeconds) {
        const now = Date.now();
        // Prune expired on access (lazy)
        if (this.nonces.size > 1000) {
            for (const [id, exp] of this.nonces) {
                if (now > exp)
                    this.nonces.delete(id);
            }
        }
        if (this.nonces.has(requestId))
            return false; // replay detected
        this.nonces.set(requestId, now + ttlSeconds * 1000);
        return true; // fresh nonce
    }
    // ── Integrity Verification ──
    async verifyIntegrity() {
        const brokenLinks = [];
        // Check receipt chain continuity
        for (let i = 1; i < this.receipts.length; i++) {
            if (this.receipts[i].previousReceiptHash && this.receipts[i - 1].receiptId) {
                // In volatile backend, chain hashes aren't set by default — skip deep verification
            }
        }
        const latest = this.checkpoints.length > 0 ? this.checkpoints[this.checkpoints.length - 1] : null;
        return {
            schemaVersion: 1,
            receiptChainValid: brokenLinks.length === 0,
            receiptCount: this.receipts.length,
            brokenLinks,
            delegationCount: this.delegations.size,
            revocationCount: this.revocations.size,
            checkpointSequence: latest?.sequence ?? 0,
            checkpointValid: true,
            errors: []
        };
    }
    async rebuildDerivedState() {
        // In volatile backend: reputation is already in sync (no persistence drift)
        // Real backends would replay receipts to recompute reputation + spend
    }
    async pruneExpired() {
        const now = Date.now();
        let nonces = 0, reservations = 0;
        for (const [id, exp] of this.nonces) {
            if (now > exp) {
                this.nonces.delete(id);
                nonces++;
            }
        }
        for (const [id, r] of this.spendReservations) {
            if (r.status === 'reserved' && now > r.expiresAt) {
                r.status = 'released';
                reservations++;
            }
        }
        return { nonces, reservations };
    }
    // ── Checkpoints ──
    async createCheckpoint(gatewayId, gatewayPrivateKey) {
        const prev = this.checkpoints.length > 0 ? this.checkpoints[this.checkpoints.length - 1] : null;
        const sequence = (prev?.sequence ?? 0) + 1;
        const receiptHead = this.receipts.length > 0 ? this.receipts[this.receipts.length - 1].receiptId : '0';
        const stateRoot = canonicalize({ d: this.delegations.size, r: this.revocations.size, rc: this.receipts.length });
        const previousHash = prev ? canonicalize(prev) : '0'.repeat(64);
        const checkpoint = {
            checkpointId: `chk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
            gatewayId, sequence,
            receiptHeadHash: receiptHead,
            stateRootHash: stateRoot,
            delegationCount: this.delegations.size,
            revocationCount: this.revocations.size,
            receiptCount: this.receipts.length,
            protocolVersion: '1.0',
            createdAt: new Date().toISOString(),
            previousCheckpointHash: previousHash,
            signature: sign(canonicalize({ sequence, stateRoot, previousHash }), gatewayPrivateKey)
        };
        this.checkpoints.push(checkpoint);
        // Emit to external anchoring callbacks
        for (const cb of this.checkpointCallbacks) {
            try {
                await cb(checkpoint.stateRootHash, checkpoint.sequence);
            }
            catch { /* best effort */ }
        }
        return checkpoint;
    }
    async getLatestCheckpoint() {
        return this.checkpoints.length > 0 ? this.checkpoints[this.checkpoints.length - 1] : null;
    }
    onCheckpoint(callback) {
        this.checkpointCallbacks.push(callback);
    }
    // ── Export ──
    async exportReceipts(filter) {
        const result = await this.queryReceipts(filter, 100000);
        return { receipts: result.items, chainValid: true };
    }
    // ── Transaction snapshot/restore (for rollback on error) ──
    _snapshot() {
        return {
            agents: new Map(this.agents),
            delegations: new Map(this.delegations),
            revocations: new Map(this.revocations),
            receipts: [...this.receipts],
            reputations: new Map(this.reputations),
            demotions: [...this.demotions],
            keyRotations: [...this.keyRotations],
            nonces: new Map(this.nonces),
            spendReservations: new Map(this.spendReservations),
            committedSpend: new Map(this.committedSpend),
            checkpoints: [...this.checkpoints]
        };
    }
    _restore(snap) {
        this.agents = snap.agents;
        this.delegations = snap.delegations;
        this.revocations = snap.revocations;
        this.receipts = snap.receipts;
        this.reputations = snap.reputations;
        this.demotions = snap.demotions;
        this.keyRotations = snap.keyRotations;
        this.nonces = snap.nonces;
        this.spendReservations = snap.spendReservations;
        this.committedSpend = snap.committedSpend;
        this.checkpoints = snap.checkpoints;
    }
}
//# sourceMappingURL=volatile-backend.js.map