#!/usr/bin/env node

// src/cli.ts
import { readFile } from "fs/promises";
import { stdin as input, stdout as output } from "process";
import { Command } from "commander";

// src/canonical.ts
import { createHash } from "crypto";
import canonicalize from "canonicalize";

// src/errors.ts
var OasgError = class extends Error {
  code;
  details;
  constructor(code, message, details) {
    super(message);
    this.name = "OasgError";
    this.code = code;
    this.details = details;
  }
};
var ValidationError = class extends OasgError {
  constructor(subject, details) {
    super("VALIDATION_ERROR", `Invalid ${subject} input.`, details);
    this.name = "ValidationError";
  }
};

// src/canonical.ts
function fail(message) {
  throw new OasgError("CANONICALIZATION_ERROR", message);
}
function assertUnicode(value, path) {
  for (let index = 0; index < value.length; index += 1) {
    const code = value.charCodeAt(index);
    if (code >= 55296 && code <= 56319) {
      const next = value.charCodeAt(index + 1);
      if (!(next >= 56320 && next <= 57343)) fail(`Lone Unicode surrogate at ${path}.`);
      index += 1;
    } else if (code >= 56320 && code <= 57343) {
      fail(`Lone Unicode surrogate at ${path}.`);
    }
  }
}
function assertIJson(value, path = "$", seen = /* @__PURE__ */ new Set()) {
  if (value === null || typeof value === "boolean") return;
  if (typeof value === "string") return assertUnicode(value, path);
  if (typeof value === "number") {
    if (!Number.isFinite(value)) fail(`Non-finite number at ${path}.`);
    return;
  }
  if (typeof value !== "object") fail(`Non-JSON ${typeof value} value at ${path}.`);
  if (seen.has(value)) fail(`Circular reference at ${path}.`);
  seen.add(value);
  if (Array.isArray(value)) {
    for (let index = 0; index < value.length; index += 1) {
      if (!(index in value)) fail(`Sparse array entry at ${path}[${index}].`);
      assertIJson(value[index], `${path}[${index}]`, seen);
    }
  } else {
    const prototype = Object.getPrototypeOf(value);
    if (prototype !== Object.prototype && prototype !== null) fail(`Non-plain object at ${path}.`);
    for (const [key, entry] of Object.entries(value)) {
      assertUnicode(key, `${path} property name`);
      assertIJson(entry, `${path}.${key}`, seen);
    }
  }
  seen.delete(value);
}
function canonicalJson(value) {
  assertIJson(value);
  try {
    const serialized = canonicalize(value);
    if (serialized === void 0) fail("Value cannot be represented as canonical JSON.");
    return serialized;
  } catch (error) {
    if (error instanceof OasgError) throw error;
    return fail(error instanceof Error ? error.message : "Canonical JSON serialization failed.");
  }
}
function sha256(value) {
  return createHash("sha256").update(canonicalJson(value)).digest("hex");
}

// src/normalize.ts
var EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
function isEvmAddress(value) {
  return EVM_ADDRESS.test(value);
}
function normalizeIdentifier(value) {
  return EVM_ADDRESS.test(value) ? value.toLowerCase() : value;
}
function chainFromAssetId(assetId) {
  const slash = assetId.indexOf("/");
  return slash === -1 ? "" : assetId.slice(0, slash);
}
function x402AssetId(network, asset) {
  if (network.startsWith("eip155:")) {
    if (!EVM_ADDRESS.test(asset)) throw new TypeError("EVM x402 assets must be 20-byte addresses.");
    return `${network}/erc20:${asset.toLowerCase()}`;
  }
  if (!/^[A-Za-z0-9._%:-]{1,128}$/.test(asset)) {
    throw new TypeError("x402 asset cannot be represented as a portable asset identifier.");
  }
  return `${network}/token:${asset}`;
}

// src/schema.ts
import Ajv from "ajv/dist/ajv.js";
import addFormats from "ajv-formats/dist/index.js";

// schemas/policy.schema.json
var policy_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "https://onchainoncall.com/schemas/oasg.policy.v1.json",
  title: "Open Agent Spend Guard Policy v1",
  type: "object",
  additionalProperties: false,
  required: ["schemaVersion", "policyId", "version", "rules"],
  properties: {
    schemaVersion: { const: "oasg.policy.v1" },
    policyId: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]{2,63}$" },
    version: { type: "integer", minimum: 1 },
    name: { type: "string", minLength: 1, maxLength: 120 },
    rules: {
      type: "object",
      additionalProperties: false,
      required: ["allowedChains", "assets"],
      properties: {
        allowedChains: {
          type: "array",
          minItems: 1,
          uniqueItems: true,
          items: { $ref: "#/definitions/chainId" }
        },
        assets: {
          type: "array",
          minItems: 1,
          items: { $ref: "#/definitions/assetRule" }
        },
        allowedCounterparties: {
          type: "array",
          minItems: 1,
          uniqueItems: true,
          items: { type: "string", minLength: 1, maxLength: 256 }
        },
        allowedPurposes: {
          type: "array",
          minItems: 1,
          uniqueItems: true,
          items: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]{1,63}$" }
        }
      }
    }
  },
  definitions: {
    atomicAmount: { type: "string", pattern: "^(0|[1-9][0-9]*)$", maxLength: 78 },
    chainId: { type: "string", pattern: "^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,64}$" },
    assetId: {
      type: "string",
      pattern: "^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,64}/[-a-z0-9]{3,8}:[-._%a-zA-Z0-9]{1,128}$"
    },
    assetRule: {
      type: "object",
      additionalProperties: false,
      required: ["assetId", "maxPerTransaction"],
      properties: {
        assetId: { $ref: "#/definitions/assetId" },
        maxPerTransaction: { $ref: "#/definitions/atomicAmount" },
        approvalRequiredAbove: { $ref: "#/definitions/atomicAmount" },
        periodBudget: {
          type: "object",
          additionalProperties: false,
          required: ["period", "limit"],
          properties: {
            period: { const: "day" },
            limit: { $ref: "#/definitions/atomicAmount" }
          }
        }
      }
    }
  }
};

// schemas/request.schema.json
var request_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "https://onchainoncall.com/schemas/oasg.request.v1.json",
  title: "Open Agent Spend Guard Request v1",
  type: "object",
  additionalProperties: false,
  required: [
    "schemaVersion",
    "requestId",
    "agentId",
    "chainId",
    "assetId",
    "amount",
    "counterparty",
    "purpose",
    "observedAt"
  ],
  properties: {
    schemaVersion: { const: "oasg.request.v1" },
    requestId: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$" },
    agentId: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$" },
    chainId: { $ref: "#/definitions/chainId" },
    assetId: { $ref: "#/definitions/assetId" },
    amount: { $ref: "#/definitions/atomicAmount" },
    counterparty: { type: "string", minLength: 1, maxLength: 256 },
    purpose: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]{1,63}$" },
    observedAt: {
      type: "string",
      format: "date-time",
      description: "Caller-supplied source observation time. Normalize to UTC before hashing when cross-system byte-for-byte reproducibility is required."
    },
    spentInPeriod: { $ref: "#/definitions/atomicAmount" },
    source: {
      type: "object",
      additionalProperties: false,
      required: ["type"],
      properties: {
        type: { type: "string", minLength: 1, maxLength: 80 },
        reference: { type: "string", minLength: 1, maxLength: 512 },
        rawHash: { type: "string", pattern: "^[a-f0-9]{64}$" },
        metadata: {
          type: "object",
          additionalProperties: {
            anyOf: [
              { type: "string" },
              { type: "number" },
              { type: "boolean" },
              { type: "null" }
            ]
          }
        }
      }
    }
  },
  definitions: {
    atomicAmount: { type: "string", pattern: "^(0|[1-9][0-9]*)$", maxLength: 78 },
    chainId: { type: "string", pattern: "^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,64}$" },
    assetId: {
      type: "string",
      pattern: "^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,64}/[-a-z0-9]{3,8}:[-._%a-zA-Z0-9]{1,128}$"
    }
  }
};

// schemas/decision.schema.json
var decision_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "https://onchainoncall.com/schemas/oasg.decision.v1.json",
  title: "Open Agent Spend Guard Decision v1",
  type: "object",
  additionalProperties: false,
  required: [
    "schemaVersion",
    "decisionId",
    "outcome",
    "reasonCode",
    "explanation",
    "matchedRule",
    "policy",
    "request",
    "facts",
    "evaluatedAt"
  ],
  properties: {
    schemaVersion: { const: "oasg.decision.v1" },
    decisionId: { type: "string", pattern: "^oasg_[a-f0-9]{32}$" },
    outcome: { enum: ["allow", "block", "approval_required"] },
    reasonCode: {
      enum: [
        "chain.not_allowed",
        "asset.chain_mismatch",
        "asset.not_allowed",
        "counterparty.not_allowed",
        "purpose.not_allowed",
        "amount.max_transaction",
        "budget.snapshot_required",
        "budget.period_limit",
        "approval.threshold",
        "policy.allow"
      ]
    },
    explanation: { type: "string", minLength: 1, maxLength: 500 },
    matchedRule: { type: "string", pattern: "^/" },
    policy: {
      type: "object",
      additionalProperties: false,
      required: ["policyId", "version", "hash"],
      properties: {
        policyId: { type: "string" },
        version: { type: "integer", minimum: 1 },
        hash: { $ref: "#/definitions/hash" }
      }
    },
    request: {
      type: "object",
      additionalProperties: false,
      required: ["requestId", "hash"],
      properties: {
        requestId: { type: "string" },
        hash: { $ref: "#/definitions/hash" }
      }
    },
    facts: {
      type: "object",
      additionalProperties: false,
      required: ["chainId", "assetId", "amount", "counterparty", "purpose"],
      properties: {
        chainId: { type: "string" },
        assetId: { type: "string" },
        amount: { $ref: "#/definitions/atomicAmount" },
        counterparty: { type: "string" },
        purpose: { type: "string" },
        spentInPeriod: { $ref: "#/definitions/atomicAmount" },
        projectedPeriodSpend: { $ref: "#/definitions/atomicAmount" }
      }
    },
    evaluatedAt: {
      type: "string",
      format: "date-time",
      description: "Deterministic copy of request.observedAt, not a trusted wall-clock record of evaluator execution."
    }
  },
  definitions: {
    atomicAmount: { type: "string", pattern: "^(0|[1-9][0-9]*)$", maxLength: 78 },
    hash: { type: "string", pattern: "^[a-f0-9]{64}$" }
  }
};

// schemas/evidence.schema.json
var evidence_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "https://onchainoncall.com/schemas/oasg.evidence.v1.json",
  title: "Open Agent Spend Guard Evidence Package v1",
  type: "object",
  additionalProperties: false,
  required: ["schemaVersion", "packageId", "createdAt", "policy", "request", "decision"],
  properties: {
    schemaVersion: { const: "oasg.evidence.v1" },
    packageId: { type: "string", pattern: "^oasge_[a-f0-9]{32}$" },
    createdAt: { type: "string", format: "date-time" },
    policy: { $ref: "https://onchainoncall.com/schemas/oasg.policy.v1.json" },
    request: { $ref: "https://onchainoncall.com/schemas/oasg.request.v1.json" },
    decision: { $ref: "https://onchainoncall.com/schemas/oasg.decision.v1.json" }
  }
};

// src/schema.ts
var ajv = new Ajv({ allErrors: true, strict: true });
addFormats(ajv);
var validatePolicySchema = ajv.compile(policy_schema_default);
var validateRequestSchema = ajv.compile(request_schema_default);
var validateDecisionSchema = ajv.compile(decision_schema_default);
var validateEvidenceSchema = ajv.compile(evidence_schema_default);
function copyErrors(errors) {
  return errors ? structuredClone(errors) : [];
}
function semanticError(instancePath, message) {
  return {
    instancePath,
    schemaPath: "#/semantic",
    keyword: "semantic",
    params: {},
    message
  };
}
function policySemanticErrors(value) {
  const seenAssets = /* @__PURE__ */ new Set();
  const errors = [];
  for (const [index, asset] of value.rules.assets.entries()) {
    const assetPath2 = `/rules/assets/${index}/assetId`;
    if (seenAssets.has(asset.assetId)) {
      errors.push(semanticError(assetPath2, "must not duplicate another asset rule"));
    }
    seenAssets.add(asset.assetId);
    if (!value.rules.allowedChains.includes(chainFromAssetId(asset.assetId))) {
      errors.push(semanticError(assetPath2, "must belong to a chain in allowedChains"));
    }
    if (asset.approvalRequiredAbove !== void 0 && BigInt(asset.approvalRequiredAbove) > BigInt(asset.maxPerTransaction)) {
      errors.push(semanticError(`/rules/assets/${index}/approvalRequiredAbove`, "must not exceed maxPerTransaction"));
    }
  }
  return errors;
}
function assertPolicy(value) {
  if (!validatePolicySchema(value)) throw new ValidationError("policy", copyErrors(validatePolicySchema.errors));
  const semanticErrors = policySemanticErrors(value);
  if (semanticErrors.length > 0) throw new ValidationError("policy", semanticErrors);
}
function assertRequest(value) {
  if (!validateRequestSchema(value)) throw new ValidationError("request", copyErrors(validateRequestSchema.errors));
}
function assertDecision(value) {
  if (!validateDecisionSchema(value)) throw new ValidationError("decision", copyErrors(validateDecisionSchema.errors));
}
function assertEvidence(value) {
  if (!validateEvidenceSchema(value)) throw new ValidationError("evidence", copyErrors(validateEvidenceSchema.errors));
}
function validationResult(kind, value) {
  const validator = kind === "policy" ? validatePolicySchema : kind === "request" ? validateRequestSchema : kind === "decision" ? validateDecisionSchema : validateEvidenceSchema;
  if (!validator(value)) return { valid: false, errors: copyErrors(validator.errors) };
  const errors = kind === "policy" ? policySemanticErrors(value) : [];
  return { valid: errors.length === 0, errors };
}

// src/types.ts
var REQUEST_SCHEMA_VERSION = "oasg.request.v1";
var DECISION_SCHEMA_VERSION = "oasg.decision.v1";
var EVIDENCE_SCHEMA_VERSION = "oasg.evidence.v1";

// src/adapters/safe.ts
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
function assertSafe(value) {
  const candidate = value;
  const valid = candidate !== null && typeof candidate === "object" && typeof candidate.safe === "string" && isEvmAddress(candidate.safe) && typeof candidate.to === "string" && isEvmAddress(candidate.to) && typeof candidate.value === "string" && /^(0|[1-9][0-9]*)$/.test(candidate.value) && Number.isInteger(candidate.operation) && typeof candidate.safeTxGas === "string" && /^(0|[1-9][0-9]*)$/.test(candidate.safeTxGas) && typeof candidate.baseGas === "string" && /^(0|[1-9][0-9]*)$/.test(candidate.baseGas) && typeof candidate.gasPrice === "string" && /^(0|[1-9][0-9]*)$/.test(candidate.gasPrice) && typeof candidate.gasToken === "string" && isEvmAddress(candidate.gasToken) && (candidate.refundReceiver === void 0 || candidate.refundReceiver === null || isEvmAddress(candidate.refundReceiver)) && (typeof candidate.nonce === "number" && Number.isInteger(candidate.nonce) && candidate.nonce >= 0 || typeof candidate.nonce === "string" && /^(0|[1-9][0-9]*)$/.test(candidate.nonce));
  if (!valid) throw new ValidationError("safe", "Expected a Safe Transaction Service multisig transaction.");
}
function normalizeSafeMultisigTransaction(value, options) {
  assertSafe(value);
  if (value.operation !== 0) {
    throw new OasgError("UNSUPPORTED_SAFE_OPERATION", "v0.1 rejects Safe delegate calls and supports CALL only.");
  }
  if (value.data && value.data !== "0x") {
    throw new OasgError(
      "UNSUPPORTED_SAFE_CALLDATA",
      "v0.1 rejects Safe calldata because token transfers and arbitrary contract effects require decoding."
    );
  }
  const refundReceiver = value.refundReceiver ? normalizeIdentifier(value.refundReceiver) : ZERO_ADDRESS;
  if (value.gasPrice !== "0" || normalizeIdentifier(value.gasToken) !== ZERO_ADDRESS || refundReceiver !== ZERO_ADDRESS) {
    throw new OasgError(
      "UNSUPPORTED_SAFE_GAS_REFUND",
      "v0.1 rejects Safe gas-refund configuration because it can move value outside the normalized transfer amount."
    );
  }
  const observedAt = options.observedAt ?? value.submissionDate;
  if (!observedAt) throw new OasgError("SAFE_OBSERVED_AT", "Provide observedAt when the Safe response has no submissionDate.");
  if (!options.assetId.startsWith(`${options.chainId}/`)) {
    throw new OasgError("SAFE_ASSET_CHAIN_MISMATCH", "Safe assetId must belong to the supplied chainId.");
  }
  const nativeAssetPrefix = `${options.chainId}/slip44:`;
  const nativeAssetReference = options.assetId.slice(nativeAssetPrefix.length);
  if (!options.assetId.startsWith(nativeAssetPrefix) || !/^(0|[1-9][0-9]*)$/.test(nativeAssetReference)) {
    throw new OasgError(
      "SAFE_NATIVE_ASSET_REQUIRED",
      "Safe native-value transfers require a CAIP-19 native assetId in the slip44 namespace."
    );
  }
  const rawHash = sha256(value);
  const reference = value.safeTxHash ?? value.transactionHash ?? `safe:${value.safe}:nonce:${value.nonce}`;
  const request = {
    schemaVersion: REQUEST_SCHEMA_VERSION,
    requestId: options.requestId ?? `safe_${rawHash.slice(0, 24)}`,
    agentId: options.agentId,
    chainId: options.chainId,
    assetId: options.assetId,
    amount: value.value,
    counterparty: normalizeIdentifier(value.to),
    purpose: options.purpose ?? "safe_native_transfer",
    observedAt,
    source: {
      type: "safe.multisig_transaction",
      reference,
      rawHash,
      metadata: {
        safe: normalizeIdentifier(value.safe),
        nonce: String(value.nonce),
        isExecuted: value.isExecuted ?? false,
        confirmationsRequired: value.confirmationsRequired ?? 0,
        confirmationsObserved: value.confirmations?.length ?? 0
      }
    }
  };
  if (options.spentInPeriod !== void 0) request.spentInPeriod = options.spentInPeriod;
  assertRequest(request);
  return request;
}

// src/adapters/x402.ts
function assertX402Envelope(value) {
  const candidate = value;
  const valid = candidate !== null && typeof candidate === "object" && candidate.x402Version === 2 && typeof candidate.resource?.url === "string" && Array.isArray(candidate.accepts) && candidate.accepts.length > 0 && candidate.accepts.every(
    (entry) => entry && typeof entry.scheme === "string" && typeof entry.network === "string" && typeof entry.asset === "string" && typeof entry.amount === "string" && typeof entry.payTo === "string" && Number.isFinite(entry.maxTimeoutSeconds) && entry.maxTimeoutSeconds > 0
  );
  if (!valid) throw new ValidationError("x402", "Expected an x402 v2 PaymentRequired object.");
}
function normalizeX402PaymentRequired(value, options) {
  assertX402Envelope(value);
  const acceptIndex = options.acceptIndex ?? 0;
  const accepted = value.accepts[acceptIndex];
  if (!accepted) throw new OasgError("X402_ACCEPT_INDEX", `No x402 payment option exists at index ${acceptIndex}.`);
  if (accepted.scheme !== "exact") {
    throw new OasgError("UNSUPPORTED_X402_SCHEME", `v0.1 supports the x402 exact scheme, not ${accepted.scheme}.`);
  }
  const invalidAmount = !/^(0|[1-9][0-9]*)$/.test(accepted.amount);
  const invalidPayTo = accepted.network.startsWith("eip155:") && !isEvmAddress(accepted.payTo);
  if (invalidAmount || invalidPayTo) {
    throw new ValidationError("x402", "The selected exact payment option has an invalid atomic amount or payTo address.");
  }
  const rawHash = sha256(value);
  let assetId;
  try {
    assetId = x402AssetId(accepted.network, accepted.asset);
  } catch (error) {
    throw new OasgError("X402_ASSET_IDENTIFIER", error instanceof Error ? error.message : String(error));
  }
  const request = {
    schemaVersion: REQUEST_SCHEMA_VERSION,
    requestId: options.requestId ?? `x402_${rawHash.slice(0, 24)}_${acceptIndex}`,
    agentId: options.agentId,
    chainId: accepted.network,
    assetId,
    amount: accepted.amount,
    counterparty: normalizeIdentifier(accepted.payTo),
    purpose: options.purpose ?? "x402_resource_payment",
    observedAt: options.observedAt,
    source: {
      type: "x402.payment_required.v2",
      reference: value.resource.url,
      rawHash,
      metadata: {
        scheme: accepted.scheme,
        acceptIndex,
        maxTimeoutSeconds: accepted.maxTimeoutSeconds
      }
    }
  };
  if (options.spentInPeriod !== void 0) request.spentInPeriod = options.spentInPeriod;
  assertRequest(request);
  return request;
}

// src/evaluator.ts
function result(outcome, reasonCode, explanation, matchedRule, projectedPeriodSpend) {
  return projectedPeriodSpend === void 0 ? { outcome, reasonCode, explanation, matchedRule } : { outcome, reasonCode, explanation, matchedRule, projectedPeriodSpend };
}
function evaluateRules(policy, request) {
  if (!policy.rules.allowedChains.includes(request.chainId)) {
    return result("block", "chain.not_allowed", `Chain ${request.chainId} is not allowed by this policy.`, "/rules/allowedChains");
  }
  if (chainFromAssetId(request.assetId) !== request.chainId) {
    return result("block", "asset.chain_mismatch", "The asset identifier does not belong to the requested chain.", "/assetId");
  }
  const asset = policy.rules.assets.find((candidate) => candidate.assetId === request.assetId);
  if (!asset) {
    return result("block", "asset.not_allowed", `Asset ${request.assetId} is not allowed by this policy.`, "/rules/assets");
  }
  const counterparty = normalizeIdentifier(request.counterparty);
  if (policy.rules.allowedCounterparties && !policy.rules.allowedCounterparties.map(normalizeIdentifier).includes(counterparty)) {
    return result(
      "block",
      "counterparty.not_allowed",
      `Counterparty ${request.counterparty} is not allowed by this policy.`,
      "/rules/allowedCounterparties"
    );
  }
  if (policy.rules.allowedPurposes && !policy.rules.allowedPurposes.includes(request.purpose)) {
    return result("block", "purpose.not_allowed", `Purpose ${request.purpose} is not allowed by this policy.`, "/rules/allowedPurposes");
  }
  const amount = BigInt(request.amount);
  if (amount > BigInt(asset.maxPerTransaction)) {
    return result(
      "block",
      "amount.max_transaction",
      `Amount ${request.amount} exceeds the per-transaction limit ${asset.maxPerTransaction}.`,
      assetPath(policy, asset, "maxPerTransaction")
    );
  }
  let projectedPeriodSpend;
  if (asset.periodBudget) {
    if (request.spentInPeriod === void 0) {
      return result(
        "block",
        "budget.snapshot_required",
        `A current ${asset.periodBudget.period} spend snapshot is required for this asset.`,
        assetPath(policy, asset, "periodBudget")
      );
    }
    const spent = BigInt(request.spentInPeriod);
    projectedPeriodSpend = (spent + amount).toString();
    if (BigInt(projectedPeriodSpend) > BigInt(asset.periodBudget.limit)) {
      return result(
        "block",
        "budget.period_limit",
        `Projected ${asset.periodBudget.period} spend ${projectedPeriodSpend} exceeds the limit ${asset.periodBudget.limit}.`,
        assetPath(policy, asset, "periodBudget/limit"),
        projectedPeriodSpend
      );
    }
  }
  if (asset.approvalRequiredAbove !== void 0 && amount > BigInt(asset.approvalRequiredAbove)) {
    return result(
      "approval_required",
      "approval.threshold",
      `Amount ${request.amount} requires approval above ${asset.approvalRequiredAbove}.`,
      assetPath(policy, asset, "approvalRequiredAbove"),
      projectedPeriodSpend
    );
  }
  return result("allow", "policy.allow", "The request satisfies every active policy rule.", "/rules", projectedPeriodSpend);
}
function assetPath(policy, asset, suffix) {
  return `/rules/assets/${policy.rules.assets.indexOf(asset)}/${suffix}`;
}
function evaluate(policyInput, requestInput) {
  assertPolicy(policyInput);
  assertRequest(requestInput);
  const policy = policyInput;
  const request = requestInput;
  const evaluated = evaluateRules(policy, request);
  const policyHash = sha256(policy);
  const requestHash = sha256(request);
  const decisionSeed = {
    schemaVersion: DECISION_SCHEMA_VERSION,
    policyHash,
    requestHash,
    outcome: evaluated.outcome,
    reasonCode: evaluated.reasonCode
  };
  const facts = {
    chainId: request.chainId,
    assetId: request.assetId,
    amount: request.amount,
    counterparty: request.counterparty,
    purpose: request.purpose
  };
  if (request.spentInPeriod !== void 0) facts.spentInPeriod = request.spentInPeriod;
  if (evaluated.projectedPeriodSpend !== void 0) facts.projectedPeriodSpend = evaluated.projectedPeriodSpend;
  const decision = {
    schemaVersion: DECISION_SCHEMA_VERSION,
    decisionId: `oasg_${sha256(decisionSeed).slice(0, 32)}`,
    outcome: evaluated.outcome,
    reasonCode: evaluated.reasonCode,
    explanation: evaluated.explanation,
    matchedRule: evaluated.matchedRule,
    policy: { policyId: policy.policyId, version: policy.version, hash: policyHash },
    request: { requestId: request.requestId, hash: requestHash },
    facts,
    evaluatedAt: request.observedAt
  };
  assertDecision(decision);
  return decision;
}

// src/evidence.ts
function createEvidencePackage(policyInput, requestInput) {
  assertPolicy(policyInput);
  assertRequest(requestInput);
  const decision = evaluate(policyInput, requestInput);
  const packageId = `oasge_${sha256({
    policyHash: decision.policy.hash,
    requestHash: decision.request.hash,
    decisionId: decision.decisionId
  }).slice(0, 32)}`;
  const evidence = {
    schemaVersion: EVIDENCE_SCHEMA_VERSION,
    packageId,
    createdAt: requestInput.observedAt,
    policy: structuredClone(policyInput),
    request: structuredClone(requestInput),
    decision
  };
  assertEvidence(evidence);
  return evidence;
}

// src/cli.ts
async function readStdin() {
  const chunks = [];
  for await (const chunk of input) chunks.push(Buffer.from(chunk));
  return Buffer.concat(chunks).toString("utf8");
}
async function readJson(path) {
  const source = path === "-" ? await readStdin() : await readFile(path, "utf8");
  try {
    return JSON.parse(source);
  } catch (error) {
    throw new OasgError("INVALID_JSON", `Could not parse JSON from ${path}.`, String(error));
  }
}
function writeJson(value, compact = false) {
  output.write(`${compact ? canonicalJson(value) : JSON.stringify(value, null, 2)}
`);
}
function parseIndex(value) {
  const parsed = Number(value);
  if (!Number.isInteger(parsed) || parsed < 0) throw new OasgError("INVALID_INDEX", "Accept index must be a non-negative integer.");
  return parsed;
}
function x402Options(options) {
  const result2 = {
    agentId: String(options.agent),
    observedAt: String(options.observedAt)
  };
  if (options.purpose !== void 0) result2.purpose = String(options.purpose);
  if (options.requestId !== void 0) result2.requestId = String(options.requestId);
  if (options.acceptIndex !== void 0) result2.acceptIndex = Number(options.acceptIndex);
  if (options.spentInPeriod !== void 0) result2.spentInPeriod = String(options.spentInPeriod);
  return result2;
}
function safeOptions(options) {
  const result2 = {
    agentId: String(options.agent),
    chainId: String(options.chain),
    assetId: String(options.asset)
  };
  if (options.observedAt !== void 0) result2.observedAt = options.observedAt;
  if (options.purpose !== void 0) result2.purpose = options.purpose;
  if (options.requestId !== void 0) result2.requestId = options.requestId;
  if (options.spentInPeriod !== void 0) result2.spentInPeriod = options.spentInPeriod;
  return result2;
}
async function main(argv = process.argv) {
  const program = new Command();
  program.name("oasg").description("Deterministic spend-policy decisions for agent-initiated onchain payments.").version("0.1.2");
  program.command("evaluate").description("Evaluate a normalized request against a policy.").requiredOption("-p, --policy <file>", "policy JSON file").requiredOption("-r, --request <file>", "request JSON file, or - for stdin").option("--evidence", "emit a complete reproducible evidence package").option("--compact", "emit canonical single-line JSON").action(async (options) => {
    const policy = await readJson(options.policy);
    const request = await readJson(options.request);
    writeJson(options.evidence ? createEvidencePackage(policy, request) : evaluate(policy, request), options.compact);
  });
  program.command("normalize-x402").description("Normalize one x402 v2 PaymentRequired option into an OASG request.").requiredOption("-i, --input <file>", "x402 PaymentRequired JSON, or - for stdin").requiredOption("--agent <id>", "agent identifier").requiredOption("--observed-at <iso>", "observation time in ISO 8601 format").option("--purpose <slug>", "payment purpose").option("--request-id <id>", "request identifier").option("--accept-index <n>", "zero-based accepts index", parseIndex, 0).option("--spent-in-period <atomic>", "already-spent atomic amount").option("--compact", "emit canonical single-line JSON").action(async (options) => writeJson(normalizeX402PaymentRequired(await readJson(options.input), x402Options(options)), options.compact));
  program.command("normalize-safe").description("Normalize a Safe native-transfer multisig transaction into an OASG request.").requiredOption("-i, --input <file>", "Safe transaction JSON, or - for stdin").requiredOption("--agent <id>", "agent identifier").requiredOption("--chain <caip2>", "CAIP-2 chain identifier").requiredOption("--asset <caip19>", "native asset identifier").option("--observed-at <iso>", "observation time; falls back to submissionDate").option("--purpose <slug>", "payment purpose").option("--request-id <id>", "request identifier").option("--spent-in-period <atomic>", "already-spent atomic amount").option("--compact", "emit canonical single-line JSON").action(async (options) => writeJson(normalizeSafeMultisigTransaction(await readJson(options.input), safeOptions(options)), options.compact));
  program.command("validate").description("Validate a policy, request, decision, or evidence document.").argument("<kind>", "policy, request, decision, or evidence").argument("<file>", "JSON file, or - for stdin").action(async (kind, file) => {
    if (!["policy", "request", "decision", "evidence"].includes(kind)) {
      throw new OasgError("VALIDATION_KIND", "Kind must be policy, request, decision, or evidence.");
    }
    const checked = validationResult(kind, await readJson(file));
    writeJson(checked);
    if (!checked.valid) process.exitCode = 1;
  });
  program.command("hash").description("Compute the canonical SHA-256 hash of a JSON document.").argument("<file>", "JSON file, or - for stdin").action(async (file) => writeJson({ algorithm: "sha256", hash: sha256(await readJson(file)) }));
  await program.parseAsync(argv);
}
main().catch((error) => {
  const known = error instanceof OasgError;
  process.stderr.write(
    `${JSON.stringify({
      error: known ? error.code : "UNEXPECTED_ERROR",
      message: error instanceof Error ? error.message : String(error),
      ...known && error.details !== void 0 ? { details: error.details } : {}
    })}
`
  );
  process.exitCode = 1;
});
export {
  main
};
//# sourceMappingURL=cli.js.map