/**
 * Helpers for the agent-memory bearer secret at `~/.olam/memory-secret`.
 *
 * Pattern mirrors `~/.olam/auth-secret`:
 *   - 0600 mode enforced on every write
 *   - atomic write via tmp + rename
 *   - generated on first call to `ensureMemorySecret()`
 *
 * Phase C's `cloud-memory-secret` reuses the path-arg form
 * (`ensureSecretAtPath` / `readSecretAtPath`) so local + cloud share
 * the same code path.
 *
 * Plan reference: docs/plans/olam-agent-memory-distributed/phase-a-tasks.md A3
 */
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, renameSync, unlinkSync, chmodSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, dirname } from 'node:path';
import { randomBytes } from 'node:crypto';
export const MEMORY_SECRET_PATH = join(homedir(), '.olam', 'memory-secret');
/**
 * Default cloud-mode secret path. Phase C C2.
 *
 * Mirrors the local pattern (0600, 64 hex chars). When `.olam/config.yaml`
 * carries `memory.cloud.secret_ref`, that wins over this default. The
 * config-resolver (C4) handles the precedence; this constant is the
 * fall-through when the operator doesn't override.
 */
export const CLOUD_MEMORY_SECRET_PATH = join(homedir(), '.olam', 'cloud-memory-secret');
export const SECRET_LEN_BYTES = 32; // 64 hex chars
/** Generate a 64-char hex secret. */
export function generateSecret() {
    return randomBytes(SECRET_LEN_BYTES).toString('hex');
}
/**
 * Write `value` to `path` atomically with mode 0600. Parent directory
 * created with default mode if absent.
 */
export function writeSecretAtPath(path, value) {
    mkdirSync(dirname(path), { recursive: true });
    const tmp = `${path}.tmp.${process.pid}`;
    writeFileSync(tmp, value, { mode: 0o600 });
    // writeFileSync's mode is umask-respecting; chmod explicitly to be safe.
    chmodSync(tmp, 0o600);
    renameSync(tmp, path);
}
/**
 * Read `path` as a string. Returns `null` if missing. Warns to stderr
 * if mode is not 0600 (caller decides whether to proceed).
 */
export function readSecretAtPathOrNull(path) {
    if (!existsSync(path))
        return null;
    const mode = statSync(path).mode & 0o777;
    if (mode !== 0o600) {
        process.stderr.write(`warn: ${path} has mode 0${mode.toString(8)}; expected 0600. Run 'olam memory secret rotate' to regenerate.\n`);
    }
    return readFileSync(path, 'utf8').trim();
}
/** Read `path` or throw if missing. */
export function readSecretAtPath(path) {
    const v = readSecretAtPathOrNull(path);
    if (v === null) {
        throw new Error(`Secret not found at ${path}. Run 'olam memory start' to generate it.`);
    }
    return v;
}
/**
 * Ensure `~/.olam/memory-secret` exists with mode 0600. Generates a
 * fresh 64-char hex secret on first call. Idempotent.
 */
export function ensureMemorySecret(path = MEMORY_SECRET_PATH) {
    const existing = readSecretAtPathOrNull(path);
    if (existing)
        return existing;
    const fresh = generateSecret();
    writeSecretAtPath(path, fresh);
    return fresh;
}
/** Read the local-mode memory secret (no-throw shape). */
export function readMemorySecretOrNull(path = MEMORY_SECRET_PATH) {
    return readSecretAtPathOrNull(path);
}
/** Read the local-mode memory secret (throw shape). */
export function readMemorySecret(path = MEMORY_SECRET_PATH) {
    return readSecretAtPath(path);
}
/**
 * Rotate the secret: generate fresh value, atomically replace the file,
 * return the new value. Caller is responsible for restarting any
 * running memory service so it picks up the new value.
 */
export function rotateMemorySecret(path = MEMORY_SECRET_PATH) {
    const fresh = generateSecret();
    writeSecretAtPath(path, fresh);
    return fresh;
}
/** Convenience: are we wired for local-mode memory? */
export function hasMemorySecret(path = MEMORY_SECRET_PATH) {
    return existsSync(path);
}
// ── Cloud-mode wrappers (Phase C C2) ────────────────────────────────────────
//
// Thin facades over the path-arg helpers so cloud + local share the same
// atomic-write, 0600-enforced, mode-warning code path. The split exists
// because the CLI surfaces them under different verbs (`olam memory secret`
// for local; cloud secrets are operator-prompted at `olam memory mode cloud`
// time per C3) and because Phase C's config resolver (C4) keeps the
// resolution logic in one place.
/**
 * Read the cloud-mode bearer secret. Returns `null` if the file is absent
 * (the common case when the operator hasn't run `olam memory mode cloud`
 * yet). Warns to stderr on mode != 0600 but still returns the value —
 * caller decides whether to proceed.
 */
export function readCloudMemorySecretOrNull(path = CLOUD_MEMORY_SECRET_PATH) {
    return readSecretAtPathOrNull(path);
}
/** Throws when the cloud secret is absent — use only when caller has verified mode is cloud. */
export function readCloudMemorySecret(path = CLOUD_MEMORY_SECRET_PATH) {
    return readSecretAtPath(path);
}
/** Atomic write 0600. Caller supplies the value (cloud secrets are operator-prompted, not generated). */
export function writeCloudMemorySecret(value, path = CLOUD_MEMORY_SECRET_PATH) {
    writeSecretAtPath(path, value);
}
/** Convenience: is a cloud secret on disk? */
export function hasCloudMemorySecret(path = CLOUD_MEMORY_SECRET_PATH) {
    return existsSync(path);
}
//# sourceMappingURL=memory-secret.js.map