Skip to main content

memstead_cli/commands/
check.rs

1//! `memstead check` — record a check of one entity (agent-trust
2//! plan 14).
3//!
4//! Mirrors the MCP `memstead_check` tool 1:1. A check is the
5//! engine-recorded act of verification: verdict from the closed
6//! vocabulary (`ok` | `failed`), optional method note, plan-13
7//! provenance (actor, client, the session's `--role`), and the
8//! entity's `content_hash` at check time — appended to the
9//! workspace's append-only check ledger. Checking mutates nothing:
10//! no entity write, no mem commit. Derived check state is served by
11//! `memstead entity <id> --provenance`.
12
13use clap::Parser;
14use memstead_base::EntityId;
15use memstead_base::check::{VERDICTS, Verdict};
16use memstead_base::vcs::Actor;
17
18use crate::CliError;
19use crate::output::{ExitKind, print_json, print_markdown};
20use crate::setup::CliContext;
21
22#[derive(Parser, Debug)]
23pub struct Args {
24    /// Full entity id (`mem--slug`) of the entity that was checked.
25    pub id: String,
26
27    /// The verdict: `ok` | `failed`. The vocabulary is closed —
28    /// nuance goes in `--method` or in process-mem entities.
29    #[arg(long)]
30    pub verdict: String,
31
32    /// Free-text method note — how the check was performed.
33    #[arg(long)]
34    pub method: Option<String>,
35}
36
37pub fn run(ctx: &CliContext, args: Args) -> anyhow::Result<()> {
38    let Some(verdict) = Verdict::from_wire(&args.verdict) else {
39        return Err(CliError::new(
40            ExitKind::Validation,
41            "INVALID_VERDICT",
42            format!(
43                "unknown verdict {:?} — the vocabulary is: {}",
44                args.verdict,
45                VERDICTS.join(", ")
46            ),
47        )
48        .into());
49    };
50    let id = EntityId::canonical(&args.id);
51    let mut engine = ctx.cli_engine()?.into_base();
52    let client = crate::setup::cli_client_id();
53    let record = engine
54        .record_check(
55            id.mem(),
56            id.as_ref(),
57            verdict,
58            args.method.as_deref(),
59            Actor::Cli,
60            Some(&client),
61        )
62        .map_err(CliError::from_engine_op)?;
63    let (state, _) = engine
64        .entity_check_state(id.mem(), id.as_ref())
65        .map_err(CliError::from_engine_op)?;
66    if ctx.json {
67        print_json(&serde_json::json!({
68            "entity": record.entity,
69            "verdict": record.verdict,
70            "check_state": state.as_str(),
71            "role": record.role,
72            "ts": record.ts,
73            "method": record.method,
74        }))?;
75        return Ok(());
76    }
77    print_markdown(&format!(
78        "Check recorded: `{}` — verdict **{}**, state `{}` (role: {})",
79        record.entity,
80        record.verdict,
81        state.as_str(),
82        record.role
83    ));
84    Ok(())
85}