import {
  generateSalt,
  open,
  seal
} from "./chunk-A6XUJE5D.js";

// src/secure-store/secure-fs.ts
import { lstat, mkdir, readFile, readdir, rename, unlink, writeFile } from "fs/promises";
import path from "path";
var SecureStoreLockedError = class extends Error {
  constructor(message = "secure-store is locked \u2014 run `remnic secure-store unlock` to decrypt") {
    super(message);
    this.name = "SecureStoreLockedError";
  }
};
var SecureStoreDecryptError = class extends Error {
  constructor(message = "secure-store decryption failed \u2014 wrong key or tampered ciphertext") {
    super(message);
    this.name = "SecureStoreDecryptError";
  }
};
var MAGIC_BYTES = Buffer.from("REMNIC-ENC", "ascii");
var FILE_FORMAT_VERSION = 1;
var FILE_FORMAT_FLAGS = 0;
var MAGIC_HEADER_SIZE = MAGIC_BYTES.length + 2;
function isEncryptedFile(buf) {
  if (buf.length < MAGIC_HEADER_SIZE) return false;
  const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
  return b.subarray(0, MAGIC_BYTES.length).equals(MAGIC_BYTES);
}
function encryptFileBody(plain, key, aad) {
  const plainBuf = typeof plain === "string" ? Buffer.from(plain, "utf8") : plain;
  const salt = generateSalt();
  const envelope = seal(key, salt, plainBuf, aad ? { aad } : {});
  const header = Buffer.alloc(MAGIC_HEADER_SIZE);
  MAGIC_BYTES.copy(header, 0);
  header.writeUInt8(FILE_FORMAT_VERSION, MAGIC_BYTES.length);
  header.writeUInt8(FILE_FORMAT_FLAGS, MAGIC_BYTES.length + 1);
  return Buffer.concat([header, envelope]);
}
function decryptFileBody(buf, key, aad) {
  if (!isEncryptedFile(buf)) {
    throw new Error("decryptFileBody: buffer does not start with REMNIC-ENC magic header");
  }
  const version = buf.readUInt8(MAGIC_BYTES.length);
  if (version !== FILE_FORMAT_VERSION) {
    throw new Error(
      `decryptFileBody: unsupported file format version ${version} (this build supports ${FILE_FORMAT_VERSION})`
    );
  }
  const flags = buf.readUInt8(MAGIC_BYTES.length + 1);
  if (flags !== FILE_FORMAT_FLAGS) {
    throw new Error(`decryptFileBody: unknown flags byte 0x${flags.toString(16).padStart(2, "0")}`);
  }
  const envelope = buf.subarray(MAGIC_HEADER_SIZE);
  try {
    return open(key, envelope, aad ? { aad } : {});
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    throw new SecureStoreDecryptError(
      `secure-store decryption failed: ${msg}`
    );
  }
}
function filePathAad(filePath, memoryDir) {
  let rel = filePath;
  if (memoryDir && path.isAbsolute(filePath)) {
    rel = path.relative(memoryDir, filePath);
  }
  return Buffer.from(rel, "utf8");
}
async function readMaybeEncryptedFile(filePath, key, memoryDir) {
  const buf = await readFile(filePath);
  if (!isEncryptedFile(buf)) {
    return buf.toString("utf8");
  }
  if (key === null) {
    throw new SecureStoreLockedError(
      `secure-store is locked \u2014 cannot read encrypted file at ${filePath}. Run \`remnic secure-store unlock\` to decrypt.`
    );
  }
  const aad = filePathAad(filePath, memoryDir);
  const plain = decryptFileBody(buf, key, aad);
  return plain.toString("utf8");
}
async function writeMaybeEncryptedFile(filePath, content, key, options = {}, memoryDir) {
  const { mode = 384, atomic = true } = options;
  await mkdir(path.dirname(filePath), { recursive: true });
  let data;
  if (key !== null) {
    const aad = filePathAad(filePath, memoryDir);
    data = encryptFileBody(content, key, aad);
  } else {
    data = content;
  }
  if (atomic) {
    const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
    try {
      await writeFile(tempPath, data, { mode });
      await rename(tempPath, filePath);
    } catch (err) {
      try {
        await unlink(tempPath);
      } catch {
      }
      throw err;
    }
  } else {
    await writeFile(filePath, data, { mode });
  }
}
async function migrateMemoryDirToEncrypted(dir, key, onBeforeEncrypt) {
  const result = { encrypted: 0, skipped: 0, errors: [] };
  const files = await collectEncryptableStorageFiles(dir);
  for (const filePath of files) {
    try {
      const buf = await readFile(filePath);
      if (isEncryptedFile(buf)) {
        result.skipped++;
        continue;
      }
      if (onBeforeEncrypt) {
        try {
          await onBeforeEncrypt(filePath);
        } catch {
        }
      }
      const content = buf.toString("utf8");
      const aad = filePathAad(filePath, storageAadRootForFile(filePath, dir));
      const encrypted = encryptFileBody(content, key, aad);
      const tempPath = `${filePath}.enc-tmp-${process.pid}-${Date.now()}`;
      try {
        await writeFile(tempPath, encrypted, { mode: 384 });
        await rename(tempPath, filePath);
        result.encrypted++;
      } catch (writeErr) {
        try {
          const { unlink: unlink2 } = await import("fs/promises");
          await unlink2(tempPath);
        } catch {
        }
        throw writeErr;
      }
    } catch (err) {
      result.errors.push({
        filePath,
        error: err instanceof Error ? err.message : String(err)
      });
    }
  }
  return result;
}
async function decryptMemoryDirToPlaintext(dir, key) {
  const result = { decrypted: 0, skipped: 0, errors: [] };
  const files = await collectStorageManagedFiles(dir, isDecryptableStoragePath);
  for (const filePath of files) {
    try {
      const buf = await readFile(filePath);
      if (!isEncryptedFile(buf)) {
        result.skipped++;
        continue;
      }
      const aad = filePathAad(filePath, storageAadRootForFile(filePath, dir));
      const plaintext = decryptFileBody(buf, key, aad);
      const tempPath = `${filePath}.dec-tmp-${process.pid}-${Date.now()}`;
      try {
        await writeFile(tempPath, plaintext, { mode: 384 });
        await rename(tempPath, filePath);
        result.decrypted++;
      } catch (writeErr) {
        try {
          await unlink(tempPath);
        } catch {
        }
        throw writeErr;
      }
    } catch (err) {
      result.errors.push({
        filePath,
        error: err instanceof Error ? err.message : String(err)
      });
    }
  }
  return result;
}
async function collectEncryptableStorageFiles(dir, rootDir = dir) {
  return collectStorageManagedFiles(dir, isEncryptableStoragePath, rootDir);
}
async function collectStorageManagedFiles(dir, includeFile, rootDir = dir) {
  const results = [];
  let names;
  try {
    names = await readdir(dir, { encoding: "utf8" });
  } catch {
    return results;
  }
  for (const name of names) {
    if (name.startsWith(".secure-store")) continue;
    const full = path.join(dir, name);
    let isDir = false;
    let isFile = false;
    try {
      const s = await lstat(full);
      if (s.isSymbolicLink()) continue;
      isDir = s.isDirectory();
      isFile = s.isFile();
    } catch {
      continue;
    }
    if (isDir) {
      const sub = await collectStorageManagedFiles(full, includeFile, rootDir);
      results.push(...sub);
    } else if (isFile && includeFile(full, rootDir)) {
      results.push(full);
    }
  }
  return results;
}
function isEncryptableStoragePath(filePath, rootDir) {
  const rel = path.relative(rootDir, filePath);
  if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return false;
  const normalized = normalizeStorageRelativePath(rel);
  if (normalized === "profile.md") return true;
  if (isEncryptableStateSidecar(normalized)) return true;
  if (isEncryptableSummarySidecar(normalized)) return true;
  const firstSegment = normalized.split("/", 1)[0];
  return ENCRYPTABLE_MARKDOWN_STORAGE_ROOTS.has(firstSegment) && normalized.endsWith(".md");
}
function isDecryptableStoragePath(filePath, rootDir) {
  if (isEncryptableStoragePath(filePath, rootDir)) return true;
  const rel = path.relative(rootDir, filePath);
  if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return false;
  const normalized = normalizeStorageRelativePath(rel);
  const firstSegment = normalized.split("/", 1)[0];
  return DECRYPTABLE_SIDECAR_ROOTS.has(firstSegment);
}
function normalizeStorageRelativePath(rel) {
  const normalized = rel.split(path.sep).join("/");
  const parts = normalized.split("/");
  if (parts[0] === "namespaces" && parts.length >= 3) {
    return parts.slice(2).join("/");
  }
  return normalized;
}
function storageAadRootForFile(filePath, rootDir) {
  const rel = path.relative(rootDir, filePath);
  if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return rootDir;
  const parts = rel.split(path.sep);
  if (parts[0] === "namespaces" && parts.length >= 3 && parts[1]) {
    return path.join(rootDir, "namespaces", parts[1]);
  }
  return rootDir;
}
var ENCRYPTABLE_MARKDOWN_STORAGE_ROOTS = /* @__PURE__ */ new Set([
  "facts",
  "corrections",
  "procedures",
  "reasoning-traces",
  "artifacts",
  "archive",
  "entities",
  "identity"
]);
var ENCRYPTABLE_STATE_SIDECARS = /* @__PURE__ */ new Set([
  "state/behavior-signals.jsonl",
  "state/buffer-surprise-ledger.jsonl",
  "state/buffer.json",
  "state/compression-guideline-draft-state.json",
  "state/compression-guideline-state.json",
  "state/compression-guidelines.draft.md",
  "state/compression-guidelines.md",
  "state/entity-synthesis-queue.json",
  "state/fact-hashes.txt",
  "state/memory-actions.jsonl",
  "state/memory-lifecycle-ledger.jsonl",
  "state/meta.json",
  "state/reextract-jobs.jsonl",
  "state/topics.json"
]);
function isEncryptableStateSidecar(normalized) {
  return ENCRYPTABLE_STATE_SIDECARS.has(normalized);
}
function isEncryptableSummarySidecar(normalized) {
  return normalized.startsWith("summaries/") && normalized.endsWith(".json");
}
var DECRYPTABLE_SIDECAR_ROOTS = /* @__PURE__ */ new Set([
  "state",
  "indexes",
  "index",
  "provenance"
]);

export {
  SecureStoreLockedError,
  SecureStoreDecryptError,
  MAGIC_BYTES,
  FILE_FORMAT_VERSION,
  FILE_FORMAT_FLAGS,
  MAGIC_HEADER_SIZE,
  isEncryptedFile,
  encryptFileBody,
  decryptFileBody,
  filePathAad,
  readMaybeEncryptedFile,
  writeMaybeEncryptedFile,
  migrateMemoryDirToEncrypted,
  decryptMemoryDirToPlaintext
};
//# sourceMappingURL=chunk-YNJHCGDT.js.map