Skip to main content

degenbot_cli_core/
lib.rs

1//! `degenbot-cli-core` — the clap-free semantics home for the degenbot console
2//! (ADR-051 D1/D2).
3//!
4//! The console has two first-class front ends: the pure-Rust argv facade
5//! (`degenbot-cli`, clap) and the Python passthrough (`src/degenbot/_cli.py`).
6//! Both map argv into the SAME command model declared here, so command
7//! semantics — prompt policy, dry-run text, exit-code mapping — exist exactly
8//! once. This crate is deliberately **pyo3-free** (so the pure-Rust consumer can
9//! depend on it) and **clap-free/indicatif-free** (argv spelling and progress
10//! rendering are the facade's job; asserted by `just check-cli-core-purity`).
11//!
12//! # Shape
13//!
14//! - [`Command`] + the per-group enums: the constructor-parsed command model.
15//! - [`Command::execute`] / [`run_with_cancel`]: the execution entries,
16//!   returning a typed [`CommandReport`].
17//! - [`CliError`] → [`ExitCode`]: declared at exactly one `From` site; the
18//!   workspace `exit = "deny"` lint stands, so `run` returns codes and never
19//!   aborts the process. The typed fleet boot refusal (FF-T1) maps to
20//!   `EX_CONFIG` 78 (lifted out of `DegenbotCLI.invoke`).
21//! - [`PromptPlan`] + [`Prompter`]: interactive policy is declared data, ported
22//!   verbatim from the click handlers and audited, never redesigned (D4).
23//! - [`CancelHandle`]: the cooperative cancel carrier the updater arms thread
24//!   into `run_pool_update` / `run_aave_update` (the facade owns the SIGINT
25//!   policy, ADR-051 D7).
26//!
27//! # Groups
28//!
29//! - `database` ([`DatabaseCommand`]): the template group; ports
30//!   `src/degenbot/cli/database.py` arm for arm.
31//! - `exchange` ([`ExchangeCommand`]): the 34 Python click verbs collapse to
32//!   one data-driven command resolving `(chain, name)` through the
33//!   `degenbot-uniswap` deployments registry (ADR-051 D5).
34//! - `pool` ([`PoolCommand`]): the `degenbot-pool-updater` chunk loop +
35//!   on-chain-truth verify.
36//! - `aave` ([`AaveCommand`]): the `degenbot-aave` market run + row flips.
37//! - `fleet` ([`FleetCommand`]): the live cordon posture over the
38//!   operator command channel (ADR-051 D6).
39//! - `path` ([`PathCommand`]): live add-path / bounded discovery over
40//!   the same operator command channel.
41
42pub mod aave;
43pub mod block;
44pub mod cancel;
45pub mod command;
46pub mod context;
47pub mod database;
48pub mod error;
49pub mod exchange;
50pub mod fleet;
51pub mod operator;
52pub mod path;
53pub mod pool;
54pub mod prompt;
55pub mod registrations;
56pub mod report;
57pub mod strategy;
58
59pub use aave::{resolve_aave_deployment, AaveCommand, AaveDeployment, AAVE_DEPLOYMENTS};
60pub use block::{
61    parse_to_block, resolve_chain_selector, resolve_to_block, BlockTag, ToBlockSpec,
62    DEFAULT_CHUNK_SIZE, DEFAULT_TO_BLOCK, DEFAULT_VERIFY_ALL_INTERVAL,
63};
64pub use cancel::CancelHandle;
65pub use command::{Command, DatabaseCommand};
66pub use context::CliContext;
67pub use database::database_backup_path;
68pub use error::{CliError, ExitCode};
69pub use exchange::{
70    resolve_deployment, ExchangeCommand, ExchangeDeployment, PoolManagerDeployment,
71    RETIRED_EXCHANGES,
72};
73pub use fleet::FleetCommand;
74pub use operator::{
75    parse_hop_token, parse_sim_intake_floor, render_json_sorted, resolve_socket,
76    validate_posture_patch, PathDirection, PathFamily, PathStep, PosturePatchEntry,
77    PosturePatchValue, WireRequest, WireResponse, FLEET_POSTURE_THRESHOLD_KEYS,
78    SIM_INTAKE_FLOOR_RESTORE, SOCKET_DEFAULT, SOCKET_ENV,
79};
80pub use path::PathCommand;
81pub use pool::{PoolCommand, PoolFamily};
82pub use prompt::{PromptPlan, Prompter};
83pub use registrations::{ensure_supported_registrations, RegistrationReport};
84pub use report::{
85    schema_state_label, AavePositionLine, AaveReport, AaveUpdateEntry, AaveUpdateOutcome,
86    ActivateOutcome, CommandOutcome, CommandReport, CutoverOutcome, DatabaseReport,
87    DeactivateOutcome, DryRunKind, ExchangeActiveState, ExchangeListRow, ExchangeReport,
88    FleetReport, PathReport, PoolReport, StrategyReport,
89};
90pub use strategy::{
91    descriptor, descriptors, EndpointSummary, MutationOutcome, StrategyCommand, StrategyFacet,
92    StrategyFacetDescriptor,
93};
94
95/// The ONE execution entry: run `command` against `ctx`, asking `prompter` when
96/// the command's [`PromptPlan`] requires it.
97///
98/// Returns a [`CommandOutcome`] carrying the typed [`CommandReport`] (on
99/// success) and the [`ExitCode`] derived at the single `From<&CliError>` site.
100/// A fresh [`CancelHandle`] is created (no SIGINT wiring); use
101/// [`run_with_cancel`] to share the facade's handle.
102#[must_use]
103pub fn run(command: &Command, ctx: &CliContext<'_>, prompter: &dyn Prompter) -> CommandOutcome {
104    run_with_cancel(command, ctx, prompter, &CancelHandle::new())
105}
106
107/// The cancellable execution entry (ADR-051 D7).
108#[must_use]
109pub fn run_with_cancel(
110    command: &Command,
111    ctx: &CliContext<'_>,
112    prompter: &dyn Prompter,
113    cancel: &CancelHandle,
114) -> CommandOutcome {
115    let result = command.execute_with_cancel(ctx, prompter, cancel);
116    let exit_code = match &result {
117        Ok(_) => ExitCode::Success,
118        Err(err) => ExitCode::from(err),
119    };
120    CommandOutcome::new(result, exit_code)
121}