pub mod bulk_estimated;
pub mod cli;
pub mod cmd;
pub mod config;
pub mod donation_details;
pub mod eventref;
pub mod input_form_store;
pub mod optimize_attest;
pub mod price_cache;
pub mod render;
pub mod resolve;
pub mod return_inputs;
pub mod session;
pub mod tax_profile;
pub use cli::Cli;
pub use config::CliConfig;
pub use session::{
BulkFilter, BulkIncomeFilter, BulkIncomePlan, BulkIncomeRow, BulkLinkPlan, BulkLinkRow,
BulkReclassifyOutflowPlan, BulkReclassifyOutflowRow, BulkResolvePlan, BulkResolveRow,
BulkStiFilter, BulkStiPlan, BulkStiRow, BulkVoidPlan, BulkVoidRow, Frame, MatchAction,
MatchProposal, Session,
};
#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error(transparent)]
Store(#[from] btctax_store::StoreError),
#[error(transparent)]
Core(#[from] btctax_core::CoreError),
#[error(transparent)]
Adapter(#[from] btctax_adapters::AdapterError),
#[error("sqlite: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("csv: {0}")]
Csv(#[from] csv::Error),
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("IRS form fill: {0}")]
FormFill(#[from] btctax_forms::FormsError),
#[error("not a valid event reference: {0:?}")]
BadEventRef(String),
#[error("usage: {0}")]
Usage(String),
#[error("unrecognized stored config value: key={key:?} value={value:?}")]
BadConfigValue { key: String, value: String },
#[error(
"the stored inputs for {year} predate the form-question registry (schema v{found}; this build reads \
v{expected}). Run `btctax income clear {year}` — which DISCARDS any carryover this row's prior \
reports computed onto it — then `btctax income import` for {year}; then, if this row carried a \
computed carryover, `btctax report --tax-year {prior} --write-carryover` to rebuild it.",
prior = year - 1
)]
StaleReturnInputs { year: i32, found: i64, expected: i64 },
#[error(
"year {year}'s parked full return is schema v{found} but this build expects v{expected}; \
an upgrade changed the input format. Its data lives only in the draft — do not discard it. \
Re-run on the app version that wrote it, or export it there first."
)]
StaleParkedDraft { year: i32, found: i64, expected: i64 },
#[error(
"year {year} holds a parked full return — in the form, 'use full return' to re-commit it, or \
'discard parked draft' (a confirmed delete) to drop it; then re-run this command."
)]
ParkedDraftBlocksWrite { year: i32 },
#[error(
"export refused: the ledger is pseudo-reconciled (a synthetic default contributes to the \
projection). To export this draft ON PURPOSE, attest the exact phrase {:?} (pass --attest, or \
type it at the prompt). Otherwise run `reconcile pseudo off` (or approve + attest the defaults).",
ATTEST_PHRASE
)]
AttestationRequired,
#[error(
"export refused: the attestation phrase did not match. The ledger is pseudo-reconciled; type the \
phrase EXACTLY (trimmed, case-sensitive): {:?}.",
ATTEST_PHRASE
)]
AttestationFailed,
}
pub const ATTEST_PHRASE: &str = "I attest this is true";
pub fn require_attestation(attest: Option<&str>) -> Result<(), CliError> {
match attest.map(str::trim) {
Some(p) if p == ATTEST_PHRASE => Ok(()),
Some(_) => Err(CliError::AttestationFailed),
None => Err(CliError::AttestationRequired),
}
}