1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! `memstead check` — record a check of one entity (agent-trust
//! plan 14).
//!
//! Mirrors the MCP `memstead_check` tool 1:1. A check is the
//! engine-recorded act of verification: verdict from the closed
//! vocabulary (`ok` | `failed`), optional method note, plan-13
//! provenance (actor, client, the session's `--role`), and the
//! entity's `content_hash` at check time — appended to the
//! workspace's append-only check ledger. Checking mutates nothing:
//! no entity write, no mem commit. Derived check state is served by
//! `memstead entity <id> --provenance`.
use clap::Parser;
use memstead_base::EntityId;
use memstead_base::check::{CHECK_KINDS, CheckKind, VERDICTS, Verdict};
use memstead_base::vcs::Actor;
use crate::CliError;
use crate::output::{ExitKind, print_json, print_markdown};
use crate::setup::CliContext;
#[derive(Parser, Debug)]
pub struct Args {
/// Full entity id (`mem--slug`) of the entity that was checked.
pub id: String,
/// The verdict: `ok` | `failed`. The vocabulary is closed —
/// nuance goes in `--method` or in process-mem entities.
#[arg(long)]
pub verdict: String,
/// Free-text method note — how the check was performed. For a
/// conformance check, name the judging model here.
#[arg(long)]
pub method: Option<String>,
/// The check kind: `verification` (default — "I checked this
/// entity's content") | `conformance` (a semantic judgment
/// against the type's schema prose; the engine stamps the mem's
/// schema pin into the record, and the verdict goes stale when
/// the content hash moves OR the pin changes). The vocabulary is
/// closed.
#[arg(long)]
pub kind: Option<String>,
}
pub fn run(ctx: &CliContext, args: Args) -> anyhow::Result<()> {
let Some(verdict) = Verdict::from_wire(&args.verdict) else {
return Err(CliError::new(
ExitKind::Validation,
"INVALID_VERDICT",
format!(
"unknown verdict {:?} — the vocabulary is: {}",
args.verdict,
VERDICTS.join(", ")
),
)
.into());
};
let kind = match args.kind.as_deref() {
None => CheckKind::Verification,
Some(s) => CheckKind::from_wire(s).ok_or_else(|| {
CliError::new(
ExitKind::Validation,
"INVALID_CHECK_KIND",
format!(
"unknown check kind {s:?} — the vocabulary is: {}",
CHECK_KINDS.join(", ")
),
)
})?,
};
let id = EntityId::canonical(&args.id);
let mut engine = ctx.cli_engine()?.into_base();
let client = crate::setup::cli_client_id();
let record = engine
.record_check(
id.mem(),
id.as_ref(),
verdict,
kind,
args.method.as_deref(),
Actor::Cli,
Some(&client),
)
.map_err(CliError::from_engine_op)?;
let (state, _) = match kind {
CheckKind::Verification => engine.entity_check_state(id.mem(), id.as_ref()),
CheckKind::Conformance => engine.entity_conformance_state(id.mem(), id.as_ref()),
}
.map_err(CliError::from_engine_op)?;
if ctx.json {
print_json(&serde_json::json!({
"entity": record.entity,
"verdict": record.verdict,
"check_state": state.as_str(),
"kind": record.kind.as_deref().unwrap_or("verification"),
"schema_ref": record.schema_ref,
"role": record.role,
"ts": record.ts,
"method": record.method,
}))?;
return Ok(());
}
print_markdown(&format!(
"Check recorded: `{}` — kind `{}`, verdict **{}**, state `{}` (role: {})",
record.entity,
record.kind.as_deref().unwrap_or("verification"),
record.verdict,
state.as_str(),
record.role
));
Ok(())
}