noxid-cli 0.2.1

The Noxid compiler command line: check, build, test, adapt, and the agent surface
use std::fs;
use std::path::PathBuf;
use std::process::{Command, Output};
use std::sync::atomic::{AtomicU64, Ordering};

static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);

struct Fixture {
    root: PathBuf,
}

impl Fixture {
    fn new(label: &str) -> Self {
        let ordinal = NEXT_FIXTURE.fetch_add(1, Ordering::Relaxed);
        let root = std::env::temp_dir().join(format!(
            "noxid-wo24-qa5-{label}-{}-{ordinal}",
            std::process::id()
        ));
        fs::create_dir_all(root.join("server/queues")).expect("create queue fixture");
        fs::write(
            root.join("Noxid.toml"),
            "[app]\ntitle = \"WO-24 QA round 5\"\n",
        )
        .expect("write project manifest");
        fs::write(root.join("package.json"), "{\"type\":\"module\"}\n").expect("write ESM marker");
        fs::write(
            root.join("server/host.js"),
            "export const queues = Object.freeze({});\n",
        )
        .expect("write inert host");
        Self { root }
    }

    fn write(&self, relative: &str, contents: &str) {
        let path = self.root.join(relative);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).expect("create fixture parent");
        }
        fs::write(path, contents).expect("write fixture file");
    }

    fn build(&self) -> Output {
        Command::new(env!("CARGO_BIN_EXE_noxid"))
            .args(["build", ".", "--out-dir", "dist"])
            .current_dir(&self.root)
            .output()
            .expect("build queue fixture")
    }

    fn run_node(&self, name: &str, source: &str) -> Output {
        self.write(&format!("dist/{name}.mjs"), source);
        Command::new("node")
            .arg(format!("{name}.mjs"))
            .current_dir(self.root.join("dist"))
            .env_remove("DATABASE_URL")
            .output()
            .expect("execute generated queue runtime")
    }

    fn run_node_with_database(&self, name: &str, source: &str) -> Output {
        self.write(&format!("dist/{name}.mjs"), source);
        Command::new("node")
            .arg(format!("{name}.mjs"))
            .current_dir(self.root.join("dist"))
            .env("DATABASE_URL", "postgres://qa.invalid/noxid")
            .output()
            .expect("execute generated queue runtime with fake database")
    }
}

impl Drop for Fixture {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.root);
    }
}

fn output_text(output: &Output) -> String {
    format!(
        "stdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    )
}

fn assert_success(output: &Output, context: &str) {
    assert!(
        output.status.success(),
        "{context}:\n{}",
        output_text(output)
    );
}

fn write_probe_queue(fixture: &Fixture) {
    fixture.write(
        "server/queues/Probe.nox",
        "queue Probe { payload {} retry: 0 backoff: 1s }\n",
    );
}

#[test]
fn qa_round5_work_once_rejects_undeclared_options_and_non_string_identities() {
    let fixture = Fixture::new("work-once-options");
    write_probe_queue(&fixture);
    assert_success(&fixture.build(), "build work-once option fixture");

    let node = fixture.run_node(
        "work-once-options",
        r#"import { workQueueOnce } from "./server/handler.js";
async function code(options) {
  try { await workQueueOnce(options); } catch (error) { return error?.code ?? error?.name; }
  return null;
}
const cases = [
  ["valid declared options", { queue: "Probe", worker: "qa", now: "2028-02-29T12:00:00Z" }, "QUEUE_DATABASE_URL_REQUIRED"],
  ["numeric queue", { queue: 7, worker: "qa", now: "2028-02-29T12:00:00Z" }, "QUEUE_CLOCK_INVALID"],
  ["object worker", { queue: "Probe", worker: {}, now: "2028-02-29T12:00:00Z" }, "QUEUE_CLOCK_INVALID"],
  ["timer option belongs to loop", { queue: "Probe", setTimeout() {} }, "QUEUE_CLOCK_INVALID"],
  ["poll option belongs to loop", { queue: "Probe", pollIntervalMs: 10 }, "QUEUE_CLOCK_INVALID"],
  ["error hook belongs to loop", { queue: "Probe", onError() {} }, "QUEUE_CLOCK_INVALID"],
  ["undeclared field", { queue: "Probe", authority: true }, "QUEUE_CLOCK_INVALID"],
];
const failures = [];
for (const [label, options, expected] of cases) {
  const observed = await code(options);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(
        &node,
        "enforce workQueueOnce's declared typed option surface",
    );
}

#[test]
fn qa_round5_start_worker_validates_identity_and_clock_before_polling() {
    let fixture = Fixture::new("start-worker-scalars");
    write_probe_queue(&fixture);
    assert_success(&fixture.build(), "build start-worker scalar fixture");

    let node = fixture.run_node(
        "start-worker-scalars",
        r#"import { startQueueWorker } from "./server/handler.js";
async function code(options) {
  let worker;
  try { worker = startQueueWorker(options); } catch (error) { return error?.code ?? error?.name; }
  try { await worker.stop(); } catch (error) { return `late:${error?.code ?? error?.name}`; }
  return "accepted";
}
const quiet = () => {};
const cases = [
  ["numeric queue", { queue: 7, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["object worker", { queue: "Probe", worker: {}, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["invalid fixed clock", { queue: "Probe", now: "2026-02-31T12:00:00Z", onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["object inheriting from Date is not genuine", { queue: "Probe", now: Object.create(new Date("2028-02-29T12:00:00Z")), onError: quiet }, "QUEUE_CLOCK_INVALID"],
];
const failures = [];
for (const [label, options, expected] of cases) {
  const observed = await code(options);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(
        &node,
        "validate startQueueWorker identity and clock before its first poll",
    );
}

#[test]
fn qa_round5_start_worker_rejects_invalid_timer_hooks_and_poll_intervals() {
    let fixture = Fixture::new("worker-loop-options");
    write_probe_queue(&fixture);
    assert_success(&fixture.build(), "build worker-loop option fixture");

    let node = fixture.run_node(
        "worker-loop-options",
        r#"import { startQueueWorker } from "./server/handler.js";
async function code(options) {
  let worker;
  try { worker = startQueueWorker(options); } catch (error) { return error?.code ?? error?.name; }
  try { await worker.stop(); } catch (error) { return `late:${error?.code ?? error?.name}`; }
  return "accepted";
}
const quiet = () => {};
const timer = () => 1;
const clear = () => {};
const cases = [
  ["non-callable setTimeout", { setTimeout: 1, clearTimeout: clear, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["non-callable clearTimeout", { setTimeout: timer, clearTimeout: {}, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["non-callable onError", { onError: "ignore" }, "QUEUE_CLOCK_INVALID"],
  ["zero interval", { pollIntervalMs: 0, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["negative interval", { pollIntervalMs: -1, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["fractional interval", { pollIntervalMs: 1.5, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["infinite interval", { pollIntervalMs: Infinity, onError: quiet }, "QUEUE_CLOCK_INVALID"],
  ["string interval", { pollIntervalMs: "17", onError: quiet }, "QUEUE_CLOCK_INVALID"],
];
const failures = [];
for (const [label, options, expected] of cases) {
  const observed = await code(options);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(
        &node,
        "validate worker timers, callback hooks, and positive integer polling",
    );
}

#[test]
fn qa_round5_on_error_can_stop_its_worker_without_deadlock_or_timer_leak() {
    let fixture = Fixture::new("on-error-stop");
    write_probe_queue(&fixture);
    assert_success(&fixture.build(), "build onError disposal fixture");

    let node = fixture.run_node(
        "on-error-stop",
        r#"import { startQueueWorker } from "./server/handler.js";
const timers = new Map();
let nextTimer = 0;
let errorCode = null;
let stoppedFromError = false;
let worker;
worker = startQueueWorker({
  pollIntervalMs: 11,
  setTimeout(callback, delay) { const id = ++nextTimer; timers.set(id, { callback, delay }); return id; },
  clearTimeout(id) { timers.delete(id); },
  async onError(error) {
    errorCode = error?.code;
    await worker.stop();
    stoppedFromError = true;
  },
});
for (let turn = 0; turn < 8 && !stoppedFromError; turn += 1) {
  await new Promise((resolve) => setImmediate(resolve));
}
if (errorCode !== "QUEUE_DATABASE_URL_REQUIRED") throw new Error(`unexpected worker error ${errorCode}`);
if (!stoppedFromError) throw new Error("onError awaiting worker.stop() deadlocked");
if (timers.size !== 0) throw new Error(`onError stop leaked ${timers.size} timer(s)`);
await worker.stop();
"#,
    );
    assert_success(
        &node,
        "allow an error callback to dispose the worker that invoked it",
    );
}

#[test]
fn qa_round5_recursive_map_result_sparse_arrays_and_dates_replay_at_enqueue() {
    let fixture = Fixture::new("recursive-payload-replay");
    fixture.write(
        "server/queues/Probe.nox",
        r#"type Envelope {
    at: Date
    values: Array<Optional<Int>>
    next: Optional<Envelope>
}
queue Probe {
    payload { entries: Map<String, Result<Envelope, Array<Date>>> }
    retry: 1
    backoff: 1s
}
"#,
    );
    assert_success(&fixture.build(), "build recursive payload replay fixture");

    let node = fixture.run_node(
        "recursive-payload-replay",
        r#"import { enqueue } from "./server/handler.js";
async function code(entries) {
  try { await enqueue("Probe", { entries }); } catch (error) { return error?.code ?? error?.name; }
  return null;
}
const sparseOptional = new Array(3);
sparseOptional[1] = 7;
const validEnvelope = { at: "2000-02-29T23:59:59.999Z", values: sparseOptional, next: null };
const cyclicEnvelope = { at: "2000-02-29T23:59:59Z", values: [], next: null };
cyclicEnvelope.next = cyclicEnvelope;
const sparseDates = new Array(1);
const cases = [
  ["valid recursive wrappers", { safe: { tag: "Ok", value: validEnvelope }, dates: { tag: "Err", value: ["2028-02-29T12:00:00Z"] } }, "QUEUE_DATABASE_URL_REQUIRED"],
  ["reserved proto", JSON.parse('{"__proto__":{"tag":"Ok","value":{"at":"2000-02-29T00:00:00Z","values":[],"next":null}}}'), "QUEUE_PAYLOAD_TYPE"],
  ["reserved constructor", { constructor: { tag: "Ok", value: validEnvelope } }, "QUEUE_PAYLOAD_TYPE"],
  ["reserved prototype", { prototype: { tag: "Ok", value: validEnvelope } }, "QUEUE_PAYLOAD_TYPE"],
  ["invalid named Date", { bad: { tag: "Ok", value: { at: "2100-02-29T00:00:00Z", values: [], next: null } } }, "QUEUE_PAYLOAD_TYPE"],
  ["invalid Result Date", { bad: { tag: "Err", value: ["2028-02-29T12:00:00+00:00"] } }, "QUEUE_PAYLOAD_TYPE"],
  ["non-optional sparse Date", { bad: { tag: "Err", value: sparseDates } }, "QUEUE_PAYLOAD_TYPE"],
  ["recursive named cycle", { bad: { tag: "Ok", value: cyclicEnvelope } }, "QUEUE_PAYLOAD_TYPE"],
  ["genuine Date payload", { bad: { tag: "Ok", value: { at: new Date("2000-02-29T00:00:00Z"), values: [], next: null } } }, "QUEUE_PAYLOAD_TYPE"],
];
const failures = [];
for (const [label, entries, expected] of cases) {
  const observed = await code(entries);
  if (observed !== expected) failures.push(`${label}: expected ${expected}, saw ${observed}`);
}
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(
        &node,
        "replay recursive Map/Result, sparse-array, and Date payload boundaries",
    );
}

#[test]
fn qa_round5_fake_claim_refuses_reserved_maps_and_recursive_date_drift() {
    let fixture = Fixture::new("claim-payload-replay");
    fixture.write(
        "server/queues/Probe.nox",
        r#"type Envelope { at: Date values: Array<Optional<Int>> }
queue Probe {
    payload { entries: Map<String, Result<Envelope, Array<Date>>> }
    retry: 0
    backoff: 1s
}
"#,
    );
    fixture.write(
        "server/host.js",
        r#"export const queues = Object.freeze({
  "queue:Probe": async () => { globalThis.__qaHandlerCalls += 1; return "handled"; },
});
"#,
    );
    assert_success(&fixture.build(), "build fake-claim payload replay fixture");
    fixture.write(
        "dist/node_modules/postgres/package.json",
        "{\"type\":\"module\",\"exports\":\"./index.js\"}\n",
    );
    fixture.write(
        "dist/node_modules/postgres/index.js",
        r#"export default function postgres() {
  const sql = async (strings) => {
    const query = strings.join("?");
    globalThis.__qaQueries.push(query);
    if (query.includes("SELECT id, queue, payload")) return [globalThis.__qaRows.shift()];
    return [];
  };
  sql.unsafe = async (query) => { globalThis.__qaQueries.push(query); return []; };
  sql.begin = async (callback) => callback(sql);
  sql.end = async () => {};
  sql.json = (value) => value;
  return sql;
}
"#,
    );

    let node = fixture.run_node_with_database(
        "claim-payload-replay",
        r#"globalThis.__qaQueries = [];
globalThis.__qaHandlerCalls = 0;
const envelope = { at: "2000-02-29T00:00:00Z", values: [] };
globalThis.__qaRows = [
  { id: "reserved-proto", queue: "Probe", principal: null, attempts: 0, run_at: new Date("2026-08-30T12:00:00Z"), payload: { entries: JSON.parse('{"__proto__":{"tag":"Ok","value":{"at":"2000-02-29T00:00:00Z","values":[]}}}') } },
  { id: "reserved-constructor", queue: "Probe", principal: null, attempts: 0, run_at: new Date("2026-08-30T12:00:00Z"), payload: { entries: { constructor: { tag: "Ok", value: envelope } } } },
  { id: "reserved-prototype", queue: "Probe", principal: null, attempts: 0, run_at: new Date("2026-08-30T12:00:00Z"), payload: { entries: { prototype: { tag: "Ok", value: envelope } } } },
  { id: "nested-date", queue: "Probe", principal: null, attempts: 0, run_at: new Date("2026-08-30T12:00:00Z"), payload: { entries: { unsafe: { tag: "Err", value: ["2100-02-29T00:00:00Z"] } } } },
];
const { workQueueOnce, closeQueueDatabase } = await import("./server/handler.js");
const failures = [];
for (const expectedId of ["reserved-proto", "reserved-constructor", "reserved-prototype", "nested-date"]) {
  let failure;
  try { await workQueueOnce({ queue: "Probe", worker: "qa", now: "2026-08-30T12:00:00Z" }); }
  catch (error) { failure = error; }
  if (failure?.code !== "QUEUE_PAYLOAD_DRIFT" || failure?.jobId !== expectedId) failures.push(`${expectedId}: saw ${failure?.code}/${failure?.jobId}`);
}
if (globalThis.__qaHandlerCalls !== 0) failures.push(`drift reached handler ${globalThis.__qaHandlerCalls} time(s)`);
const driftUpdates = globalThis.__qaQueries.filter((query) => query.includes("last_error = 'QUEUE_PAYLOAD_DRIFT'"));
if (driftUpdates.length !== 4) failures.push(`expected four drift updates, saw ${driftUpdates.length}`);
if (globalThis.__qaQueries.some((query) => query.includes("state = 'running'"))) failures.push("drifted claim entered running state");
await closeQueueDatabase();
if (failures.length > 0) throw new Error(failures.join("; "));
"#,
    );
    assert_success(
        &node,
        "refuse reserved Map keys and recursive Date drift on fake claims",
    );
}