use serde::{Deserialize, Serialize};
use crate::snapshot::ProjectSnapshot;
use crate::verify::VerifyReport;
pub const SCHEMA_VERSION: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, schemars::JsonSchema)]
#[serde(transparent)]
pub struct ExitCode(u8);
impl ExitCode {
pub const OK: Self = Self(0);
pub const INTERNAL: Self = Self(1);
pub const USAGE: Self = Self(2);
pub const NOT_A_PROJECT: Self = Self(10);
pub const DIAGNOSTICS_ERROR: Self = Self(11);
pub const SELECTION_NEEDED: Self = Self(12);
pub const CONFIG_ERROR: Self = Self(13);
pub const GIT_PRECONDITION: Self = Self(14);
pub const NOT_FOUND: Self = Self(15);
pub const TASK_NOT_READY: Self = Self(16);
pub const VERIFICATION_FAILED: Self = Self(20);
pub const NEEDS_HUMAN: Self = Self(23);
pub const EVAL_FAILED: Self = Self(24);
pub const NOT_REPLAYABLE: Self = Self(25);
pub const ATTESTATION_FAILED: Self = Self(26);
pub const RUN_FAILED: Self = Self(21);
pub const REPLAY_DIVERGED: Self = Self(22);
#[must_use]
pub const fn code(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CommandStatus {
Ok,
UsageError,
Error,
NotAProject,
DiagnosticsError,
SelectionNeeded,
ConfigError,
VerificationFailed,
GitPrecondition,
NotFound,
RunFailed,
ReplayDiverged,
TaskNotReady,
NeedsHuman,
EvalFailed,
NotReplayable,
AttestationFailed,
}
impl CommandStatus {
#[must_use]
pub const fn exit_code(self) -> ExitCode {
match self {
Self::Ok => ExitCode::OK,
Self::UsageError => ExitCode::USAGE,
Self::Error => ExitCode::INTERNAL,
Self::NotAProject => ExitCode::NOT_A_PROJECT,
Self::DiagnosticsError => ExitCode::DIAGNOSTICS_ERROR,
Self::SelectionNeeded => ExitCode::SELECTION_NEEDED,
Self::ConfigError => ExitCode::CONFIG_ERROR,
Self::VerificationFailed => ExitCode::VERIFICATION_FAILED,
Self::GitPrecondition => ExitCode::GIT_PRECONDITION,
Self::NotFound => ExitCode::NOT_FOUND,
Self::RunFailed => ExitCode::RUN_FAILED,
Self::ReplayDiverged => ExitCode::REPLAY_DIVERGED,
Self::TaskNotReady => ExitCode::TASK_NOT_READY,
Self::NeedsHuman => ExitCode::NEEDS_HUMAN,
Self::EvalFailed => ExitCode::EVAL_FAILED,
Self::NotReplayable => ExitCode::NOT_REPLAYABLE,
Self::AttestationFailed => ExitCode::ATTESTATION_FAILED,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
#[serde(untagged)]
pub enum CommandData {
Inspect(Box<ProjectSnapshot>),
Verify(Box<VerifyReport>),
Worktree(WorktreeReport),
Propose(Box<ProposeReport>),
Replay(ReplayReport),
Run(Box<RunReport>),
Eval(Box<EvalReport>),
Feature(Box<FeatureReport>),
Converge(Box<ConvergeReport>),
Report(Box<StatusReport>),
Approvals(Box<ApprovalsReport>),
Control(Box<ControlReport>),
Cost(Box<CostReport>),
Pr(Box<PrReport>),
Bench(Box<BenchReport>),
Init(Box<InitReport>),
Skills(Box<SkillsReport>),
Skill(Box<SkillRunReport>),
Checklists(Vec<ChecklistStatus>),
Archive(Box<ArchiveReport>),
InPlace(Box<InPlaceReport>),
Attest(Box<AttestReport>),
Explain(Box<ExplainReport>),
Schema(Box<SchemaReport>),
Doctor(Box<DoctorReport>),
Features(Box<FeaturesReport>),
Ui(Box<UiReport>),
Config(Box<ConfigReport>),
Issues(Box<IssuesReport>),
Inbox(Box<InboxReport>),
Review(Box<ReviewReport>),
Auth(Box<AuthReport>),
BranchVerify(Box<crate::verify_branch::BranchReport>),
EvidenceTour(Box<crate::tour::EvidenceTour>),
Actions(Box<ActionsReport>),
Bug(Box<crate::bug::BugReport>),
PolicyCoverage(Box<PolicyCoverage>),
Comments(Box<crate::oracle::CommentsView>),
Preset(Box<PresetReport>),
NewProject(Box<crate::new_project::NewProjectReport>),
Sample(Box<SampleReport>),
Similar(Box<crate::similar::SimilarReport>),
RungSuggestion(Box<crate::ladder::RungSuggestion>),
PlanningChain(Box<PlanningChainReport>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct PolicyCoverage {
pub entry: CoverageEntry,
pub complete: bool,
pub unreadable: Vec<String>,
pub rows: Vec<CoverageRow>,
pub digest: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct CoverageEntry {
pub key: String,
pub prefix: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct CoverageRow {
pub argv: Vec<String>,
pub count: u32,
pub last_at: Option<u64>,
pub decided: Vec<CoverageDecision>,
pub still_asks: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct CoverageDecision {
pub decision: String,
pub count: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ActionsReport {
pub actions: Vec<crate::actions::Action>,
pub journey: Vec<crate::actions::JourneyStep>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct AuthReport {
pub host: String,
pub login: Option<String>,
pub source: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ReviewReport {
pub feature: String,
pub file: String,
pub requirements: Vec<ReviewItem>,
pub totals: ReviewTotals,
pub revise_with: Option<String>,
pub revision: Option<String>,
pub questions: Vec<OpenQuestion>,
pub changes: Vec<ReviewChange>,
pub history: Vec<SpecVersion>,
#[serde(default)]
pub oracle: Vec<crate::oracle::OracleView>,
#[serde(default)]
pub tests_notice: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bug_tests: Vec<crate::oracle::BugTests>,
#[serde(default)]
pub approval: Option<crate::oracle::ReviewApproval>,
#[serde(default)]
pub sheet: Option<crate::oracle::ApproveSheet>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default)]
pub readiness: Option<Readiness>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ReviewChange {
pub id: String,
pub then: Option<String>,
pub now: Option<String>,
pub path: String,
pub then_line: Option<u32>,
pub now_line: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct OpenQuestion {
pub id: String,
pub text: String,
pub file: String,
pub line: usize,
pub near: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct SpecVersion {
pub commit: String,
pub date: String,
pub author: String,
pub subject: String,
pub added: Vec<String>,
pub reworded: Vec<String>,
pub removed: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ReviewItem {
pub id: String,
pub text: String,
pub state: String,
pub by: Option<String>,
pub at: Option<u64>,
pub comment: Option<String>,
pub decided_on: Option<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ReviewTotals {
pub approved: usize,
pub changes_requested: usize,
pub open: usize,
pub lapsed: usize,
#[serde(default)]
pub questions: usize,
#[serde(default)]
pub ambiguous: usize,
}
impl ReviewTotals {
#[must_use]
pub fn complete(&self) -> bool {
self.changes_requested == 0 && self.open == 0 && self.lapsed == 0 && self.questions == 0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct InboxReport {
pub items: Vec<InboxItem>,
pub features: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct InboxItem {
pub kind: String,
pub group: String,
pub feature: String,
pub task: Option<String>,
pub title: String,
pub detail: String,
pub since: Option<u64>,
pub actions: Vec<crate::actions::Action>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unverified: Option<Unverified>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct Unverified {
pub ticks: usize,
pub regression_only: usize,
pub counts: Vec<ReasonCount>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ReasonCount {
pub reason: String,
pub n: usize,
pub tasks: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub implemented_by: Vec<String>,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct IssuesReport {
pub repo: Option<String>,
pub issues: Vec<IssueEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct IssueEntry {
pub number: u64,
pub title: String,
pub url: String,
pub labels: Vec<String>,
pub assignees: Vec<String>,
pub mine: bool,
pub updated: Option<String>,
pub feature: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct ConfigReport {
pub file: String,
pub exists: bool,
pub committed: bool,
pub valid: bool,
pub error: Option<String>,
pub sections: Vec<ConfigSection>,
pub diff: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct ConfigSection {
pub name: String,
pub title: String,
pub about: String,
pub named: bool,
pub keys: Vec<ConfigKey>,
pub tables: Vec<ConfigTable>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct ConfigTable {
pub name: String,
pub keys: Vec<ConfigKey>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct ConfigKey {
pub key: String,
pub kind: String,
pub about: String,
pub default: Option<serde_json::Value>,
pub value: Option<serde_json::Value>,
pub choices: Vec<String>,
pub min: Option<f64>,
pub max: Option<f64>,
pub required: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ConfigChange {
pub key: String,
#[serde(default)]
pub value: Option<serde_json::Value>,
#[serde(default)]
pub append: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct UiReport {
pub url: String,
pub port: u16,
pub root: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct FeatureEntry {
pub number: usize,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub dir: String,
pub current: bool,
pub done: usize,
pub tasks: usize,
pub artifacts: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct FeaturesReport {
pub unit: String,
pub current: Option<String>,
pub reason: String,
pub reason_text: String,
pub action: String,
pub features: Vec<FeatureEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DoctorLevel {
Pass,
Info,
Warn,
Fail,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DoctorCheck {
pub check: String,
pub level: DoctorLevel,
pub detail: String,
pub code: Option<String>,
pub fix: Vec<String>,
pub remedy: Option<Remedy>,
pub tasks: Vec<String>,
pub commands: Vec<crate::step::Argvs>,
}
#[derive(Serialize, schemars::JsonSchema)]
#[schemars(rename = "DoctorCheck")]
struct ShownCheck<'a> {
check: &'a str,
level: DoctorLevel,
detail: &'a str,
code: Option<&'a str>,
fix: &'a [String],
fix_steps: Vec<crate::step::Step>,
#[serde(skip_serializing_if = "Option::is_none")]
remedy: Option<&'a Remedy>,
#[serde(skip_serializing_if = "<[String]>::is_empty")]
tasks: &'a [String],
}
impl Serialize for DoctorCheck {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
ShownCheck {
check: &self.check,
level: self.level,
detail: &self.detail,
code: self.code.as_deref(),
fix: &self.fix,
fix_steps: crate::step::steps_with(
&self.fix,
&self.commands,
crate::step::Context::default(),
),
remedy: self.remedy.as_ref(),
tasks: &self.tasks,
}
.serialize(serializer)
}
}
impl schemars::JsonSchema for DoctorCheck {
fn schema_name() -> std::borrow::Cow<'static, str> {
"DoctorCheck".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
ShownCheck::json_schema(generator)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct DoctorReport {
pub speccoder: String,
pub os: String,
pub checks: Vec<DoctorCheck>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub agents: Vec<AgentCard>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct AgentCard {
pub id: String,
pub name: String,
pub chosen: bool,
pub installed: AgentTick,
pub signed_in: AgentTick,
pub asks_first: AgentTick,
}
impl AgentCard {
#[must_use]
pub fn ready(&self) -> bool {
[&self.installed, &self.signed_in, &self.asks_first]
.iter()
.all(|t| t.state == TickState::Yes)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TickState {
Yes,
No,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentTick {
pub state: TickState,
pub detail: String,
pub fix: Vec<String>,
pub commands: Vec<crate::step::Argvs>,
}
impl AgentTick {
#[must_use]
pub fn new(state: TickState, detail: impl Into<String>) -> Self {
Self {
state,
detail: detail.into(),
fix: Vec::new(),
commands: Vec::new(),
}
}
#[must_use]
pub fn with_command(mut self, argvs: crate::step::Argvs) -> Self {
self.fix.push(crate::step::render(&argvs));
self.commands.push(argvs);
self
}
#[must_use]
pub fn with_words(mut self, words: impl Into<String>) -> Self {
self.fix.push(format!("→ {}", words.into()));
self
}
}
#[derive(Serialize, schemars::JsonSchema)]
#[schemars(rename = "AgentTick")]
struct ShownTick<'a> {
state: TickState,
detail: &'a str,
fix: &'a [String],
fix_steps: Vec<crate::step::Step>,
}
impl Serialize for AgentTick {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
ShownTick {
state: self.state,
detail: &self.detail,
fix: &self.fix,
fix_steps: crate::step::steps_with(
&self.fix,
&self.commands,
crate::step::Context::default(),
),
}
.serialize(serializer)
}
}
impl schemars::JsonSchema for AgentTick {
fn schema_name() -> std::borrow::Cow<'static, str> {
"AgentTick".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
ShownTick::json_schema(generator)
}
}
impl CommandData {
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::Inspect(_) => "inspect",
Self::Verify(_) => "verify",
Self::Worktree(_) => "worktree",
Self::Propose(_) => "propose",
Self::Replay(_) => "replay",
Self::Run(_) => "run",
Self::Eval(_) => "eval",
Self::Feature(_) => "feature",
Self::Converge(_) => "converge",
Self::Report(_) => "report",
Self::Approvals(_) => "approvals",
Self::Control(_) => "control",
Self::Cost(_) => "cost",
Self::Pr(_) => "pr",
Self::Bench(_) => "bench",
Self::Init(_) => "init",
Self::Skills(_) => "skills",
Self::Skill(_) => "skill",
Self::Checklists(_) => "checklists",
Self::Archive(_) => "archive",
Self::InPlace(_) => "in-place",
Self::Attest(_) => "attest",
Self::Explain(_) => "explain",
Self::Schema(_) => "schema",
Self::Doctor(_) => "doctor",
Self::Features(_) => "features",
Self::Ui(_) => "ui",
Self::Config(_) => "config",
Self::Issues(_) => "issues",
Self::Inbox(_) => "inbox",
Self::Review(_) => "review",
Self::Auth(_) => "auth",
Self::BranchVerify(_) => "branch_verify",
Self::EvidenceTour(_) => "evidence_tour",
Self::Actions(_) => "actions",
Self::Bug(_) => "bug",
Self::PolicyCoverage(_) => "policy_coverage",
Self::Comments(_) => "comments",
Self::Preset(_) => "preset",
Self::NewProject(_) => "new_project",
Self::Sample(_) => "sample",
Self::Similar(_) => "similar",
Self::RungSuggestion(_) => "rung_suggestion",
Self::PlanningChain(_) => "planning_chain",
}
}
}
pub const PAYLOADS: &[&str] = &[
"inspect",
"verify",
"worktree",
"propose",
"replay",
"run",
"eval",
"feature",
"converge",
"report",
"approvals",
"control",
"cost",
"pr",
"bench",
"init",
"skills",
"skill",
"checklists",
"archive",
"in-place",
"attest",
"explain",
"schema",
"doctor",
"features",
"ui",
"config",
"issues",
"inbox",
"review",
"auth",
"branch_verify",
"evidence_tour",
"actions",
"bug",
"policy_coverage",
"comments",
"preset",
"new_project",
"sample",
"similar",
"rung_suggestion",
"planning_chain",
];
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, schemars::JsonSchema)]
pub struct Readiness {
pub requirements: u32,
pub scenarios: u32,
pub open_questions: u32,
pub untestable: Vec<String>,
pub identifiers: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ChainStep {
pub skill: String,
pub status: CommandStatus,
pub message: String,
pub run_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PlanCheck {
pub code: String,
pub tasks: Vec<String>,
pub detail: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ChainStop {
Questions {
questions: Vec<String>,
},
Review {
requirements: Vec<String>,
},
Failed {
skill: String,
message: String,
code: Option<String>,
},
Done {
#[serde(default)]
plan_checks: Vec<PlanCheck>,
},
}
impl ChainStop {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Questions { .. } => "questions",
Self::Review { .. } => "review",
Self::Failed { .. } => "failed",
Self::Done { .. } => "done",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct PlanningChainReport {
pub unit: Option<String>,
pub through: crate::actions::Through,
pub ran: Vec<ChainStep>,
pub stop: ChainStop,
pub readiness: Option<Readiness>,
pub similar: Vec<crate::similar::SimilarMatch>,
pub source: crate::actions::FeatureSource,
pub next_skill: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct SampleRun {
pub id: String,
pub result: String,
pub finding: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct SampleRecording {
pub model: String,
pub recorded_at: String,
pub agentplane: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct SampleReport {
pub path: String,
pub fresh: bool,
pub recorded: SampleRecording,
pub runs: Vec<SampleRun>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct PresetReport {
pub preset: String,
pub version: String,
pub command: Vec<String>,
pub output: String,
pub state: PresetState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct PresetState {
pub installed: Option<String>,
pub active_for_tasks_template: bool,
pub bundled: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct InstructionFile {
pub path: String,
pub sha256: String,
pub bytes: usize,
pub cut: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct CodeInfo {
pub code: String,
pub kind: crate::codes::Kind,
pub status: CommandStatus,
pub exit_code: u8,
pub title: String,
pub explain: String,
pub fix: Vec<String>,
pub docs: String,
}
impl CodeInfo {
#[must_use]
pub fn of(e: &crate::codes::Entry) -> Self {
Self {
code: e.name.to_owned(),
kind: e.kind,
status: e.status,
exit_code: e.status.exit_code().code(),
title: e.title.to_owned(),
explain: e.explain.to_owned(),
fix: crate::codes::fixes(e, "<command>", None, None),
docs: e.docs(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ExplainReport {
pub codes: Vec<CodeInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct SchemaName {
pub name: String,
pub describes: String,
pub url: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct SchemaReport {
pub names: Vec<SchemaName>,
pub schema: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct InPlaceReport {
pub paths: Vec<RefusedPath>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ArchiveReport {
pub change: String,
pub stage: String,
pub openspec_status: Option<serde_json::Value>,
pub unverified_tasks: Vec<String>,
pub diff: Option<String>,
pub archived_as: Option<String>,
pub totals: Option<serde_json::Value>,
pub commit: Option<String>,
pub confirmed_by: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ChecklistStatus {
pub file: String,
pub total: usize,
pub done: usize,
pub open: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct EvalReport {
pub model: String,
pub cases: Vec<EvalCase>,
pub summary: EvalSummary,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct EvalCase {
pub name: String,
pub expect: String,
pub classification: String,
pub outcome: String,
pub run_id: Option<String>,
pub attempt_status: Option<String>,
pub verdict: Option<crate::verify::Verdict>,
pub oracle_passed: Option<bool>,
pub usage: Option<serde_json::Value>,
pub tool_calls: Vec<serde_json::Value>,
pub model_said: Option<String>,
pub attempts: usize,
pub round: usize,
pub monitor_flagged: Option<bool>,
pub replays: Option<bool>,
pub held_out: Option<serde_json::Value>,
pub gap: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strength: Option<f64>,
#[serde(default)]
pub over_strict: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, schemars::JsonSchema)]
pub struct EvalSummary {
pub cases: usize,
pub correct: usize,
pub false_completions: usize,
pub not_completed: usize,
pub honest_stops: usize,
pub cheats: usize,
pub invalid_or_error: usize,
pub precision: Option<f64>,
pub tokens_per_correct: Option<u64>,
pub monitor: Option<MonitorScore>,
pub replay_checked: usize,
pub replayed: usize,
pub caught: usize,
pub held_out_gap: Option<HeldOutGap>,
pub held_out_oracle: Option<HeldOutOracle>,
pub median_acceptance_strength: Option<f64>,
pub over_strict_stops: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct HeldOutGap {
pub visible_passed: usize,
pub held_out_failed: usize,
pub rate: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct HeldOutOracle {
pub agree: usize,
pub disagree: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct MonitorScore {
pub judged: usize,
pub true_positives: usize,
pub false_positives: usize,
pub false_negatives: usize,
pub precision: Option<f64>,
pub recall: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct AgentAttempt {
pub agent: String,
pub preset: String,
pub mode: Option<String>,
pub decides_alone: bool,
pub known: bool,
pub unasked: u32,
pub tally: PermissionTally,
pub plan: Vec<AgentPlanStep>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct PermissionTally {
pub asked: u32,
pub allowed: u32,
pub waited: u32,
pub refused: u32,
pub reported: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct AgentPlanStep {
pub content: String,
pub status: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct RunReport {
pub run_id: String,
pub feature: String,
pub task: String,
pub worktree: String,
pub attempt_status: String,
pub model_report: Option<serde_json::Value>,
pub reason: Option<String>,
pub pending: Option<serde_json::Value>,
pub tool_calls: Vec<serde_json::Value>,
pub changes: Vec<(String, String)>,
pub verification: Option<crate::verify::VerifyReport>,
pub verdict: Option<crate::verify::Verdict>,
pub usage: Option<serde_json::Value>,
pub integrity: Vec<String>,
pub ticked: bool,
pub commit: Option<String>,
pub acceptance: Option<serde_json::Value>,
pub earlier: Vec<EarlierAttempt>,
pub retry_stop: Option<String>,
pub saved_attempt: Option<String>,
pub set_aside: Option<String>,
pub notices: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<AgentAttempt>,
pub monitor: Option<serde_json::Value>,
pub mutation: Option<serde_json::Value>,
pub samples: Vec<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<crate::scope::ScopeFinding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attestation: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub instructions: Vec<InstructionFile>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strength: Option<crate::strength::AcceptanceStrength>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bug: Option<Box<crate::bug::BugReport>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub collection: Option<crate::collection::Collection>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub regression: Option<crate::regression::Regression>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context: Option<crate::context::ContextManifest>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct EarlierAttempt {
pub run_id: String,
pub attempt_status: String,
pub verdict: Option<crate::verify::Verdict>,
pub fingerprint: String,
pub digest: crate::retry::Digest,
pub usage: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct FeatureReport {
pub feature: String,
pub attempts: Vec<RunReport>,
pub stop: String,
pub converge: Vec<ConvergeReport>,
pub hooks: Vec<String>,
pub parallel: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub full_tier: Option<FullTier>,
#[serde(default)]
pub mode: crate::oracle::RunMode,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub autopilot_denied: Vec<crate::oracle::AutopilotDenial>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct FullTier {
pub ran: Vec<String>,
pub passed: bool,
pub failed: Vec<String>,
pub note: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct SkillsReport {
pub skills: Vec<SkillEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct SkillEntry {
pub name: String,
pub description: String,
pub path: String,
pub refused: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct SkillRunReport {
pub skill: String,
pub run_id: String,
pub status: String,
pub summary: Option<String>,
pub questions: Vec<String>,
pub changes: Vec<(String, String)>,
pub tool_calls: Vec<serde_json::Value>,
pub pending: Option<serde_json::Value>,
pub reason: Option<String>,
pub usage: Option<serde_json::Value>,
pub hooks: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub current: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct InitReport {
pub path: String,
pub checks: Vec<InitCheck>,
pub model: String,
pub wrote: Vec<String>,
pub ran: Vec<InitRun>,
pub instructions: Vec<String>,
pub key: InitKey,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
pub committed: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub include: Vec<String>,
#[serde(default)]
pub include_active: bool,
#[serde(default)]
pub worktreeinclude: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct InitRun {
pub name: String,
pub passed: bool,
pub status: String,
pub duration_ms: u64,
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, schemars::JsonSchema)]
pub struct InitKey {
pub variable: String,
pub set: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct InitCheck {
pub name: String,
pub argv: Vec<String>,
pub tier: String,
pub why: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ControlReport {
pub feature: String,
pub task: String,
pub action: String,
pub text: Option<String>,
pub record: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct CostReport {
pub feature: String,
pub tasks: Vec<TaskCost>,
pub convergence: Usage,
pub total: Usage,
pub priced_model: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct TaskCost {
pub task: String,
pub attempts: usize,
pub usage: Usage,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, schemars::JsonSchema)]
pub struct Usage {
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_read_tokens: u64,
pub cache_write_tokens: u64,
pub usd: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct ApprovalsReport {
pub feature: String,
pub pending: Vec<PendingCall>,
pub decided: Vec<DecidedCall>,
pub unreadable: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub batch: Vec<BatchView>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ObjectView {
pub class: String,
pub short: String,
pub describe: String,
pub lines: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct BatchView {
pub run: String,
pub digest: String,
pub groups: Vec<BatchGroup>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct BatchGroup {
pub class: String,
pub calls: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, schemars::JsonSchema)]
pub struct PendingCall {
pub task: String,
pub covers: String,
pub tool: String,
pub arguments: serde_json::Value,
pub reason: String,
pub reference: String,
pub presentation: Option<ApprovalView>,
pub call: String,
pub source: String,
pub kind: String,
pub question: Option<String>,
pub waiting_since: Option<u64>,
pub deadline: Option<u64>,
pub object: Option<ObjectView>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub sources: Vec<String>,
pub run: Option<String>,
pub batched: bool,
pub digest: String,
}
impl PendingCall {
#[must_use]
pub fn id(&self) -> String {
if self.call.is_empty() {
format!("attempt:{}", self.task)
} else {
self.call.clone()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct OperatorView {
pub actor: String,
pub basis: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ApprovalView {
pub kind: String,
pub title: String,
pub subject: String,
pub body_lines: Vec<String>,
pub sandbox: String,
pub claim: Option<String>,
pub risk: String,
pub scope: String,
pub object: Vec<String>,
pub sources: Vec<String>,
pub options: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct DecidedCall {
pub task: String,
pub decision: String,
pub covers: String,
pub reason: Option<String>,
pub by: Option<String>,
pub scope: Option<String>,
pub note: Option<String>,
pub call: Option<String>,
pub operator: Option<OperatorView>,
pub via: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client: Option<String>,
pub expired: bool,
pub object: Option<ObjectView>,
pub run: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum VerifyOutcome {
Ok,
Unsigned,
Missing,
BadSignature,
UntrustedSigner,
Mismatch,
Malformed,
ToolMissing,
}
impl VerifyOutcome {
#[must_use]
pub const fn rank(self) -> u8 {
match self {
Self::Ok => 0,
Self::Unsigned => 1,
Self::UntrustedSigner => 2,
Self::ToolMissing => 3,
Self::Mismatch => 4,
Self::BadSignature => 5,
Self::Malformed => 6,
Self::Missing => 7,
}
}
#[must_use]
pub const fn word(self) -> &'static str {
match self {
Self::Ok => "ok",
Self::Unsigned => "unsigned",
Self::Missing => "missing",
Self::BadSignature => "bad_signature",
Self::UntrustedSigner => "untrusted_signer",
Self::Mismatch => "mismatch",
Self::Malformed => "malformed",
Self::ToolMissing => "tool_missing",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct VerifyReason {
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expected: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub found: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct SignerView {
pub kind: String,
pub keyid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub principal: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ObjectVerification {
pub object: String,
pub kind: String,
pub task: String,
pub verdict: Option<String>,
pub outcome: VerifyOutcome,
pub reasons: Vec<VerifyReason>,
pub signer: Option<SignerView>,
pub statements: usize,
pub same_tree_as: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct AttestReport {
pub feature: String,
pub trust: serde_json::Value,
pub objects: Vec<ObjectVerification>,
pub totals: std::collections::BTreeMap<String, usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct TraceRow {
pub requirement: String,
pub scenario: Option<String>,
pub test: Option<String>,
pub task: Option<String>,
pub verdict: Option<String>,
#[serde(default)]
pub changed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct Drift {
pub requirements: Vec<RequirementDrift>,
pub files: Vec<String>,
}
impl Drift {
#[must_use]
pub fn stale(&self) -> bool {
!self.requirements.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct RequirementDrift {
pub id: String,
pub then: Option<String>,
pub now: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct StatusReport {
pub feature: String,
pub branch: Option<String>,
pub source: String,
#[serde(default)]
pub selected_by: String,
#[serde(default)]
pub artifacts: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub planning: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub uncommitted: Vec<String>,
#[serde(default)]
pub journey: Vec<crate::actions::JourneyStep>,
pub unmerged_commits: usize,
pub tasks: Vec<TaskStatus>,
pub convergence: Vec<ConvergenceRound>,
pub trace: Vec<TraceRow>,
pub totals: StatusTotals,
pub issues: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub review: Option<ReviewTotals>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct TaskStatus {
pub id: String,
pub title: String,
pub phase: String,
pub state: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub drift: Option<Drift>,
pub attestation: Option<String>,
pub blocked_by: Vec<String>,
pub commit: Option<String>,
pub run_id: Option<String>,
pub last_attempt: Option<LastAttempt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub set_aside: Option<SetAside>,
pub note: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strength: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bug: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub regression: Option<crate::regression::Regression>,
#[serde(default, skip_serializing_if = "crate::lane::Lane::is_code")]
pub lane: crate::lane::Lane,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct LastAttempt {
pub outcome: String,
pub reason: String,
pub run_id: Option<String>,
pub reference: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct SetAside {
pub reference: String,
pub files: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ConvergenceRound {
pub round: u32,
pub commit: String,
pub tasks: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct StatusTotals {
pub verified: usize,
pub regression_only: usize,
pub ticked_without_evidence: usize,
pub evidence_without_tick: usize,
pub without_attestation: usize,
pub attested: usize,
pub skipped: usize,
pub ready: usize,
pub blocked: usize,
#[serde(default)]
pub stale: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct BenchReport {
pub out: String,
pub imported: Vec<String>,
pub skipped: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct PrReport {
pub feature: String,
pub branch: String,
pub base: String,
pub remote: String,
pub title: String,
pub body: String,
pub commands: Vec<String>,
pub opened: bool,
pub url: Option<String>,
pub held_out_ref: Option<String>,
pub held_out_tasks: Vec<String>,
pub held_out_withheld: usize,
pub evidence_check: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct ConvergeReport {
pub feature: String,
pub run_id: Option<String>,
pub round: u32,
pub outcome: String,
pub findings: Vec<serde_json::Value>,
pub appended: Vec<String>,
pub unrequested: Vec<serde_json::Value>,
pub repeated: Vec<serde_json::Value>,
pub commit: Option<String>,
pub usage: Option<serde_json::Value>,
pub tool_calls: Vec<serde_json::Value>,
pub hooks: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
pub struct ProposeReport {
pub run_id: String,
pub feature: String,
pub task: String,
pub run_status: String,
pub proposal: Option<serde_json::Value>,
pub usage: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct ReplayReport {
pub run_id: String,
pub result: String,
pub detail: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct WorktreeReport {
pub feature: String,
pub path: Option<String>,
pub project_dir: Option<String>,
pub branch: Option<String>,
pub base: Option<String>,
pub created: bool,
pub refused_paths: Vec<RefusedPath>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct RefusedPath {
pub path: String,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CommandResult {
schema_version: u32,
command: String,
status: CommandStatus,
message: String,
exit_code: ExitCode,
data: Option<CommandData>,
next: Vec<crate::next::NextStep>,
code: Option<&'static crate::codes::Entry>,
fix: Vec<String>,
commands: Vec<crate::step::Argvs>,
remedy: Option<Box<Remedy>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct Remedy {
pub label: String,
pub effect: String,
pub command: String,
pub undo: Option<String>,
pub history: bool,
pub run: crate::actions::Runnable,
}
impl Serialize for CommandResult {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap as _;
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("schema_version", &self.schema_version)?;
map.serialize_entry("command", &self.command)?;
map.serialize_entry("status", &self.status)?;
map.serialize_entry("exit_code", &self.exit_code)?;
map.serialize_entry("message", &self.message)?;
if let Some(code) = self.code() {
let fix = self.fix();
map.serialize_entry("code", code.name)?;
map.serialize_entry("fix", &fix)?;
map.serialize_entry(
"fix_steps",
&crate::step::steps_with(&fix, &self.commands, crate::step::Context::default()),
)?;
map.serialize_entry("docs", &code.docs())?;
}
if let Some(remedy) = &self.remedy {
map.serialize_entry("remedy", remedy)?;
}
if !self.next.is_empty() {
map.serialize_entry("next", &self.next)?;
}
if let Some(data) = &self.data {
map.serialize_entry("payload", data.kind())?;
map.serialize_entry("data", data)?;
}
map.end()
}
}
#[derive(schemars::JsonSchema)]
#[allow(dead_code)] struct Envelope {
schema_version: u32,
command: String,
status: CommandStatus,
exit_code: ExitCode,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
fix: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
fix_steps: Option<Vec<crate::step::Step>>,
#[serde(skip_serializing_if = "Option::is_none")]
docs: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
remedy: Option<Remedy>,
#[serde(skip_serializing_if = "Option::is_none")]
next: Option<Vec<crate::next::NextStep>>,
#[serde(skip_serializing_if = "Option::is_none")]
payload: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<CommandData>,
}
impl schemars::JsonSchema for CommandResult {
fn schema_name() -> std::borrow::Cow<'static, str> {
"CommandResult".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
Envelope::json_schema(generator)
}
}
impl CommandResult {
#[must_use]
pub fn new(command: &str, status: CommandStatus, message: &str) -> Self {
Self {
schema_version: SCHEMA_VERSION,
command: command.to_owned(),
status,
message: message.to_owned(),
exit_code: status.exit_code(),
data: None,
next: Vec::new(),
code: None,
fix: Vec::new(),
commands: Vec::new(),
remedy: None,
}
}
#[must_use]
pub fn carried(command: &str, from: &Self) -> Self {
let mut out = Self::new(command, from.status, &from.message);
out.code = from.code;
out.fix.clone_from(&from.fix);
out.commands.clone_from(&from.commands);
out.remedy.clone_from(&from.remedy);
out
}
#[must_use]
pub fn caused_by(mut self, from: &Self) -> Self {
if self.status == from.status {
self.code = from.code;
self.fix.clone_from(&from.fix);
self.commands.clone_from(&from.commands);
self.remedy.clone_from(&from.remedy);
}
self
}
#[must_use]
pub fn reworded(mut self, status: CommandStatus, message: &str) -> Self {
if status != self.status {
self.code = None;
self.fix.clear();
self.commands.clear();
self.remedy = None;
}
self.status = status;
self.exit_code = status.exit_code();
message.clone_into(&mut self.message);
self
}
#[must_use]
pub fn with_code(mut self, name: &str) -> Self {
if let Some(entry) = crate::codes::lookup(name) {
self.code = Some(entry);
}
self
}
#[must_use]
pub fn with_fix(mut self, fix: Vec<String>) -> Self {
self.fix = fix;
self
}
#[must_use]
pub fn with_commands(mut self, commands: Vec<crate::step::Argvs>) -> Self {
self.commands = commands;
self
}
#[must_use]
pub fn with_command(mut self, argvs: crate::step::Argvs) -> Self {
if self.fix.is_empty() {
self.fix = self.fix();
}
self.fix.push(crate::step::render(&argvs));
self.commands.push(argvs);
self
}
#[must_use]
pub fn with_remedy(mut self, remedy: Option<Remedy>) -> Self {
self.remedy = remedy.map(Box::new);
self
}
#[must_use]
pub fn remedy(&self) -> Option<&Remedy> {
self.remedy.as_deref()
}
#[must_use]
pub fn code(&self) -> Option<&'static crate::codes::Entry> {
self.code.or_else(|| crate::codes::default_for(self.status))
}
#[must_use]
pub fn has_explicit_code(&self) -> bool {
self.code.is_some()
}
#[must_use]
pub fn fix(&self) -> Vec<String> {
match self.code() {
Some(_) if !self.fix.is_empty() => self.fix.clone(),
Some(entry) => crate::codes::fixes(entry, &self.command, None, None),
None => Vec::new(),
}
}
#[must_use]
pub fn with_next(mut self, next: Vec<crate::next::NextStep>) -> Self {
self.next = next;
self
}
#[must_use]
pub fn next(&self) -> &[crate::next::NextStep] {
&self.next
}
#[must_use]
pub fn with_data(mut self, data: CommandData) -> Self {
self.data = Some(data);
self
}
#[must_use]
pub fn data(&self) -> Option<&CommandData> {
self.data.as_ref()
}
#[must_use]
pub fn ok(command: &str, message: &str) -> Self {
Self::new(command, CommandStatus::Ok, message)
}
#[must_use]
pub fn command(&self) -> &str {
&self.command
}
#[must_use]
pub fn status(&self) -> CommandStatus {
self.status
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn exit_code(&self) -> ExitCode {
self.exit_code
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn payload_names_are_unique() {
let mut seen = std::collections::BTreeSet::new();
for p in PAYLOADS {
assert!(seen.insert(*p), "payload {p} is listed twice");
}
let branch = CommandData::BranchVerify(Box::new(crate::verify_branch::BranchReport {
head: String::new(),
base: None,
mode: crate::verify_branch::Mode::All,
features: Vec::new(),
ticks: Vec::new(),
skipped: Vec::new(),
totals: crate::verify_branch::BranchTotals::default(),
conclusion: crate::verify_branch::Conclusion::Neutral,
trust: serde_json::Value::Null,
accepted_unsigned: false,
require_verified: false,
trace: Vec::new(),
share: None,
open_no_claim: Vec::new(),
not_evaluated: Vec::new(),
newer_fields: Vec::new(),
}));
assert!(PAYLOADS.contains(&branch.kind()));
assert_ne!(branch.kind(), "verify");
assert_ne!(branch.kind(), "attest");
}
#[test]
fn every_status_maps_to_its_documented_exit_code() {
assert_eq!(CommandStatus::Ok.exit_code().code(), 0);
assert_eq!(CommandStatus::Error.exit_code().code(), 1);
assert_eq!(CommandStatus::UsageError.exit_code().code(), 2);
assert_eq!(CommandStatus::NotAProject.exit_code().code(), 10);
assert_eq!(CommandStatus::DiagnosticsError.exit_code().code(), 11);
assert_eq!(CommandStatus::SelectionNeeded.exit_code().code(), 12);
assert_eq!(CommandStatus::ConfigError.exit_code().code(), 13);
assert_eq!(CommandStatus::GitPrecondition.exit_code().code(), 14);
assert_eq!(CommandStatus::VerificationFailed.exit_code().code(), 20);
assert_eq!(CommandStatus::NotFound.exit_code().code(), 15);
assert_eq!(CommandStatus::RunFailed.exit_code().code(), 21);
assert_eq!(CommandStatus::ReplayDiverged.exit_code().code(), 22);
assert_eq!(CommandStatus::TaskNotReady.exit_code().code(), 16);
assert_eq!(CommandStatus::NeedsHuman.exit_code().code(), 23);
assert_eq!(CommandStatus::EvalFailed.exit_code().code(), 24);
assert_eq!(CommandStatus::NotReplayable.exit_code().code(), 25);
assert_eq!(CommandStatus::AttestationFailed.exit_code().code(), 26);
}
#[test]
fn exit_code_follows_status() {
let r = CommandResult::new("verify", CommandStatus::VerificationFailed, "x");
assert_eq!(r.exit_code(), r.status().exit_code());
assert_eq!(CommandResult::ok("x", "").exit_code(), ExitCode::OK);
}
#[test]
fn serializes_to_schema_version_2() {
let r = CommandResult::new("replay", CommandStatus::NotReplayable, "not replayable")
.with_code("NOT-REPLAYABLE")
.with_fix(vec!["x".to_owned()]);
let json = serde_json::to_value(&r).unwrap();
assert_eq!(
json,
serde_json::json!({
"schema_version": 2,
"command": "replay",
"status": "not_replayable",
"exit_code": 25,
"message": "not replayable",
"code": "NOT-REPLAYABLE",
"fix": ["x"],
"fix_steps": [{ "command": "x", "how": "words", "why": null }],
"docs": "https://hupe1980.github.io/speccoder/docs/codes/#not-replayable"
})
);
let with_next = CommandResult::new("run", CommandStatus::NeedsHuman, "x")
.with_next(crate::next::next(&crate::next::Outcome::Decided, None));
let json = serde_json::to_value(&with_next).unwrap();
assert_eq!(json["next"][0]["command"], "speccoder run");
assert_eq!(json["next"][0]["how"], "perform");
assert_eq!(json["next"][0]["run"]["kind"], "run");
assert!(json.get("data").is_none());
}
}