"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PolicyViolation = exports.PolicyEngine = void 0;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const DEFAULT_POLICY = {
    // 1 KAS per tx, 5 KAS per hour: deliberately conservative defaults
    // suitable for testnet experimentation.
    maxSompiPerTx: 100000000n,
    maxSompiPerHour: 500000000n,
    allowlist: [],
    requireApprovalAboveSompi: 0n,
};
class PolicyEngine {
    policyPath;
    spendLogPath;
    spendLog = [];
    cachedPolicy;
    cachedMtimeMs = -1;
    loadError;
    constructor(dataDir, policyPath) {
        this.policyPath = policyPath;
        this.cachedPolicy = loadPolicy(policyPath);
        if (policyPath)
            this.cachedMtimeMs = fs.statSync(policyPath).mtimeMs;
        this.spendLogPath = path.join(dataDir, "spend-log.json");
        if (fs.existsSync(this.spendLogPath)) {
            try {
                this.spendLog = JSON.parse(fs.readFileSync(this.spendLogPath, "utf8"));
            }
            catch {
                this.spendLog = [];
            }
        }
    }
    /**
     * The active policy, hot-reloaded: edits to the policy file take effect on
     * the next call, no restart needed. A malformed file fails closed — sends
     * are denied until it parses again.
     */
    get policy() {
        if (this.policyPath) {
            try {
                const mtimeMs = fs.statSync(this.policyPath).mtimeMs;
                if (mtimeMs !== this.cachedMtimeMs) {
                    this.cachedPolicy = loadPolicy(this.policyPath);
                    this.cachedMtimeMs = mtimeMs;
                    this.loadError = undefined;
                    console.error(`sompi: policy reloaded from ${this.policyPath}`);
                }
            }
            catch (e) {
                this.loadError = e instanceof Error ? e.message : String(e);
            }
        }
        if (this.loadError) {
            throw new PolicyViolation(`policy file ${this.policyPath} is unreadable or malformed (${this.loadError}); ` +
                `all sends are denied until it is fixed`);
        }
        return this.cachedPolicy;
    }
    /** Sum of sends in the trailing hour, in sompi. */
    spentLastHour(now = Date.now()) {
        const cutoff = now - 60 * 60 * 1000;
        return this.spendLog
            .filter((r) => r.timestampMs >= cutoff)
            .reduce((acc, r) => acc + BigInt(r.amountSompi), 0n);
    }
    /** Throws with an agent-readable reason if the send is not permitted. */
    authorize(destination, amountSompi) {
        const p = this.policy;
        if (amountSompi <= 0n) {
            throw new PolicyViolation("amount must be positive");
        }
        if (amountSompi > p.maxSompiPerTx) {
            throw new PolicyViolation(`amount ${amountSompi} sompi exceeds per-transaction limit of ${p.maxSompiPerTx} sompi` + OPERATOR_BOUNDARY);
        }
        if (p.requireApprovalAboveSompi > 0n && amountSompi > p.requireApprovalAboveSompi) {
            throw new PolicyViolation(`amount ${amountSompi} sompi exceeds the approval threshold of ${p.requireApprovalAboveSompi} sompi. ` +
                `Relay this to your human operator and wait for them to either perform the payment themselves or ` +
                `adjust the policy — do not edit the policy file or work around this tool yourself`);
        }
        if (p.allowlist.length > 0 && !p.allowlist.includes(destination)) {
            throw new PolicyViolation(`destination ${destination} is not on the policy allowlist` + OPERATOR_BOUNDARY);
        }
        const spent = this.spentLastHour();
        if (spent + amountSompi > p.maxSompiPerHour) {
            throw new PolicyViolation(`send of ${amountSompi} sompi would exceed the rolling hourly limit ` +
                `(${spent} of ${p.maxSompiPerHour} sompi already spent in the last hour)` + OPERATOR_BOUNDARY);
        }
    }
    /** Record a completed send against the rolling-hour budget. */
    record(amountSompi, now = Date.now()) {
        const cutoff = now - 60 * 60 * 1000;
        this.spendLog = this.spendLog.filter((r) => r.timestampMs >= cutoff);
        this.spendLog.push({ timestampMs: now, amountSompi: amountSompi.toString() });
        fs.writeFileSync(this.spendLogPath, JSON.stringify(this.spendLog), { mode: 0o600 });
    }
    describe() {
        return {
            maxSompiPerTx: this.policy.maxSompiPerTx.toString(),
            maxSompiPerHour: this.policy.maxSompiPerHour.toString(),
            spentLastHourSompi: this.spentLastHour().toString(),
            allowlist: this.policy.allowlist,
            requireApprovalAboveSompi: this.policy.requireApprovalAboveSompi.toString(),
        };
    }
}
exports.PolicyEngine = PolicyEngine;
/**
 * Appended to policy denials so agents treat them as boundaries to report,
 * not obstacles to engineer around. This is the agent-facing contract: the
 * policy belongs to the human operator.
 */
const OPERATOR_BOUNDARY = ". This limit was set deliberately by your human operator. Do not edit the policy file, restart processes, " +
    "or bypass these tools with direct scripts to get around it — report this message to your operator and let " +
    "them decide";
class PolicyViolation extends Error {
    constructor(reason) {
        super(`policy violation: ${reason}`);
        this.name = "PolicyViolation";
    }
}
exports.PolicyViolation = PolicyViolation;
function loadPolicy(policyPath) {
    if (!policyPath)
        return { ...DEFAULT_POLICY };
    const raw = JSON.parse(fs.readFileSync(policyPath, "utf8"));
    return {
        maxSompiPerTx: toBigInt(raw.maxSompiPerTx, DEFAULT_POLICY.maxSompiPerTx),
        maxSompiPerHour: toBigInt(raw.maxSompiPerHour, DEFAULT_POLICY.maxSompiPerHour),
        allowlist: raw.allowlist ?? [],
        requireApprovalAboveSompi: toBigInt(raw.requireApprovalAboveSompi, DEFAULT_POLICY.requireApprovalAboveSompi),
    };
}
function toBigInt(v, fallback) {
    if (v === undefined)
        return fallback;
    return BigInt(v);
}
