qq-cli 0.1.0

A modular Rust QQ task interface for coding agents
use crate::{
    core::error::{AppError, Result},
    storage::{
        references::References,
        state::{Instance, Store, atomic_json, private_dir, reject_links},
    },
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum Outcome {
    Pending,
    Completed,
}
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(super) struct Record {
    pub schema_version: u8,
    pub instance: Instance,
    pub installation_digest: String,
    pub outcome: Outcome,
}
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct Pointer {
    schema_version: u8,
    reference: String,
}
fn path(store: &Store, id: &str) -> Result<std::path::PathBuf> {
    crate::core::domain::request_key(id).map_err(|_| AppError::invalid())?;
    let path = store
        .root()
        .join("instance-removals")
        .join(format!("{:x}.json", Sha256::digest(id.as_bytes())));
    reject_links(&path)?;
    Ok(path)
}
pub(super) fn load(store: &Store, refs: &References, id: &str) -> Result<Option<Record>> {
    let pointer: Pointer = match crate::storage::config::read_json(&path(store, id)?, 4096) {
        Ok(value) => value,
        Err(error) if error.code == "FILE_NOT_FOUND" => return Ok(None),
        Err(error) => return Err(error),
    };
    if pointer.schema_version != 1 {
        return Err(AppError::storage());
    }
    let record: Record = refs.get("x1", &pointer.reference)?;
    if record.schema_version != 1
        || record.instance.id != id
        || record.instance.state != "stopped"
        || record.installation_digest.len() != 64
        || !record
            .installation_digest
            .bytes()
            .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
    {
        return Err(AppError::storage());
    }
    for key in [&record.instance.run_id, &record.instance.installation_id] {
        crate::core::domain::request_key(key).map_err(|_| AppError::storage())?;
    }
    Ok(Some(record))
}
pub(super) fn has_record(store: &Store, id: &str) -> Result<bool> {
    path(store, id)?
        .try_exists()
        .map_err(|_| AppError::storage())
}
pub(super) fn save(store: &Store, refs: &References, record: &Record) -> Result<()> {
    let path = path(store, &record.instance.id)?;
    private_dir(path.parent().ok_or_else(AppError::storage)?)?;
    atomic_json(
        &path,
        &Pointer {
            schema_version: 1,
            reference: refs.issue("x1", record)?,
        },
    )
}