btctax_cli/cmd/defensive.rs
1//! Read-only Approach-B status: `btctax defensive status`.
2//!
3//! This is the SAME pure `btctax_core::defensive::journey_view` the (now-retired) TUI wizard dashboard
4//! rendered, driven over the CLI's own session-backed projection. No new computation lives here — this
5//! module only loads inputs, refuses on a pseudo-active projection (`journey_view`'s own DFW-D6
6//! precondition), and hands the composed view to `render::render_defensive_status`.
7
8use crate::{CliError, Session};
9use btctax_adapters::BundledTaxTables;
10use btctax_core::defensive::{journey_view, DefensiveFilingView};
11use btctax_core::experimental::uses_approach_b;
12use btctax_store::Passphrase;
13use std::path::Path;
14use time::OffsetDateTime;
15
16/// The `defensive status` read: the composed [`DefensiveFilingView`], plus whether Approach-B is
17/// currently IN USE — a live (non-voided) `DeclareTranche`/`PromoteTranche` on file. The SAME
18/// `uses_approach_b` predicate the CLI's `declare-tranche`/`promote-tranche`/`report`/`export-*` verbs
19/// already gate their stderr disclosure on (never the dashboard's old "being here at all is using it"
20/// rule — this is a plain read, not itself an act of filing).
21pub struct DefensiveStatusReport {
22 pub view: DefensiveFilingView,
23 pub experimental_notice_active: bool,
24}
25
26/// Load the ledger, refuse if pseudo-active, and compute `journey_view` over the projected state.
27///
28/// DFW-D6: `journey_view` opens with `debug_assert!(!state.pseudo_active())` — a defensive-filing
29/// journey over synthetic (pseudo-reconciled) estimates is incoherent, since a Phase-B
30/// `SelfTransferMine{$0}` default can silently clear a REAL shortfall this command exists to surface.
31/// This refuses gracefully BEFORE that assertion could ever fire, mirroring `plan_export`'s own
32/// pseudo-active refusal (the composed export step never prompts an attest-phrase override either).
33///
34/// `now` resolves the wizard's clock-free "current tax year" input (mirrors `plan_export`'s own
35/// `current_year` parameter) — never read from the wall clock anywhere else in this module.
36pub fn status(
37 vault_path: &Path,
38 pp: &Passphrase,
39 now: OffsetDateTime,
40) -> Result<DefensiveStatusReport, CliError> {
41 let session = Session::open(vault_path, pp)?;
42 let (events, state, cfg) = session.load_events_and_project()?;
43 if state.pseudo_active() {
44 return Err(CliError::Usage(
45 "cannot show defensive-filing status: the ledger is pseudo-reconciled (a synthetic, \
46 non-persisted default contributes to the projection), which could silently mask a real \
47 shortfall this command exists to surface. Run `btctax reconcile pseudo off` (or approve + \
48 attest the pending defaults through `reconcile pseudo approve`) first, then re-run."
49 .to_string(),
50 ));
51 }
52 let tables = BundledTaxTables::load();
53 let current = now.year();
54 let view = journey_view(&events, &state, session.prices(), &tables, &cfg, current);
55 Ok(DefensiveStatusReport {
56 experimental_notice_active: uses_approach_b(&events),
57 view,
58 })
59}