Skip to main content

Session

Struct Session 

Source
pub struct Session { /* private fields */ }

Implementations§

Source§

impl Session

Source

pub fn create(vault_path: &Path, pp: &Passphrase) -> Result<Session, CliError>

Create a brand-new encrypted vault, then initialize the core event schema and the CLI config table, and persist. (Vault::create already saved once; we re-save after the DDL.)

Source

pub fn repair(vault_path: &Path, pp: &Passphrase) -> Result<Session, CliError>

Like create, but first clears a half-created vault (orphan key, no pgp/bak) under explicit --repair consent. Delegates to Vault::repair which refuses if a real or recoverable vault is present (see Vault::repair safety invariant).

Source

pub fn open(vault_path: &Path, pp: &Passphrase) -> Result<Session, CliError>

Open an existing vault (acquires the store single-instance lock; NFR7).

Source

pub fn conn(&self) -> &Connection

Borrow the live in-memory SQLite handle (core appenders use interior mutability over &Connection).

Source

pub fn save(&mut self) -> Result<(), CliError>

Persist the current DB image (encrypted, atomic; NFR2/NFR3).

Source

pub fn snapshot(&self) -> Result<Vec<u8>, CliError>

Snapshot the in-memory DB image (no disk I/O) for a possible restore() after a failed save.

Source

pub fn restore(&mut self, image: &[u8]) -> Result<(), CliError>

Restore the in-memory DB from a prior snapshot() (no disk I/O). On Err, the in-memory DB is UNCHANGED and unsaved residue may still be live — the caller MUST latch, never swallow.

Source

pub fn vault(&self) -> &Vault

Borrow the vault for store-level operations (export_snapshot / backup_key).

Source

pub fn config(&self) -> Result<CliConfig, CliError>

The persisted projection config (TP8 treatment + lot method); default = (c)+FIFO if unset.

Source

pub fn tax_profile(&self, year: i32) -> Result<Option<TaxProfile>, CliError>

The stored per-year TaxProfile for year, or None if none has been set. Robust to older vaults (calls tax_profile::init_table as a defensive guard).

Source

pub fn all_tax_profiles(&self) -> Result<BTreeMap<i32, TaxProfile>, CliError>

All stored TaxProfiles, sorted by year ascending.

Source

pub fn optimize_attested_set(&self) -> Result<BTreeSet<EventId>, CliError>

All attested disposal EventIds (NFR4-stable BTreeSet; feeds compliance_overlay). Robust to older vaults (defensive init_table guard inside attested_set).

Source

pub fn donation_details( &self, ) -> Result<BTreeMap<EventId, DonationDetails>, CliError>

All stored DonationDetails, keyed by donation EventId (NFR4-stable BTreeMap). Robust to older vaults (defensive init_table guard inside donation_details::all).

Source

pub fn bulk_estimated(&self) -> Result<BTreeMap<EventId, String>, CliError>

All disposals flagged as estimated-FMV proceeds by the bulk-reclassify-outflow path, keyed by the transfer_out_event (== Disposal.event); value = the date_marked provenance stamp (NFR4-stable BTreeMap). Robust to older vaults (defensive init_table guard inside bulk_estimated::all). build_snapshot loads the [est] marker set via THIS accessor, NEVER conn() directly [R0-M1].

Source

pub fn project(&self) -> Result<(LedgerState, ProjectionConfig), CliError>

Load all events and run the pure deterministic projection (NFR4) over the bundled daily-close dataset (§9.2). Returns the resolved ProjectionConfig too (so verify can display it).

Source

pub fn load_events_and_project( &self, ) -> Result<(Vec<LedgerEvent>, LedgerState, ProjectionConfig), CliError>

Single-load variant: loads events ONCE and returns them alongside the projection. Callers that need both the raw event log and the projected state (e.g. verify, safe_harbor_attest) use this to avoid the double load_all call that the project() + separate load_all() pattern incurs.

Source

pub fn exchange_method_election_rows( &self, date: TaxDate, ) -> Result<Vec<(WalletId, LotMethod, bool)>, CliError>

§A.5(a): the distinct Exchange accounts in the vault, each with its currently-in-force cost-basis method and whether that method is an explicit per-account election (true) vs inherited from a global election / FIFO default (false), as of date. Feeds the btctax-tui-edit method-election flow’s account list. Uses the SHARED resolver via btctax_core::in_force_methods (the sole precedence path). Sorted by WalletId: Ord.

Source

pub fn optimize_proposal( &self, year: i32, now: OffsetDateTime, ) -> Result<OptimizeProposal, CliError>

Recompute the Mode-1 optimizer proposal for year on the HELD session. READ-ONLY: appends and persists NOTHING (a clone-fold-discard recompute).

The TUI editor’s optimize-accept opener calls this to obtain a FRESH proposal (NFR4 — never trusts a stale one) WITHOUT opening a second Session (a second open would deadlock on the held VaultLock, and cmd::optimize::accept is forbidden to the editor for the same reason). Assembles optimize_year’s inputs exactly as cmd::optimize::run/accept do — events + config from this conn, bundled prices + tables, a FRESH tax_profile(year) read (not the cached snap), the attested set, and proposal_made = tax_date(now, UtcOffset::UTC) — and maps OptimizeError through the crate-internal map_opt_err (which is pub(crate) and not TUI-reachable). now is injected by the caller for determinism.

Source

pub fn safe_harbor_residue( &self, ) -> Result<(Vec<AllocLot>, LotMethod), CliError>

READ-ONLY: the 2025-01-01 pre-2025 Universal residue as AllocLots, plus the pre2025_method (LotMethod) it was computed under. Appends/persists NOTHING. The single source of the pre-2025 subset, shared by cmd::reconcile::safe_harbor_allocate and the TUI allocate opener.

Reads the config ONCE: cfg.pre2025_method is the recorded method returned to the caller, and cfg.to_projection() is the projection the residue is computed under — the two are STRUCTURALLY the same config read, so the returned method can never diverge from the residue’s [R0-M1]. The pre-2025 subset keeps only imports whose tax-date < 2025-01-01 plus ALL reconciliation decisions (which shape the residue), and DROPs any prior SafeHarborAllocation so the residue stays allocation-INDEPENDENT (matches transition::universal_snapshot).

READ-ONLY: compute the bulk link-transfer plan (bulk-link-transfer D1). Selects over the PROJECTED pending_reconciliation (which already excludes already-decided / already-linked outs), enriches each with date / source wallet / principal / advisory USD value / carried basis, applies the frame + from_wallet filters, and routes source == dest rows to skipped_same_wallet. Appends and persists NOTHING; mirrors safe_harbor_residue.

The USD value is btctax_core::price::fmv_of(&prices, date, principal_sat) [R0-M1] — the vetted checked helper (round_cents + overflow→None), NOT a hand-rolled principal × price. The total is the HONEST FLOOR [R0-I2]: total_usd_value_floor is Σ of the PRICED rows only, and missing_price_count records how many rows lacked a price, so the caller renders exact $X (when 0) or ≥ $X (N unavailable).

Source

pub fn bulk_self_transfer_in_plan( &self, filter: BulkStiFilter, ) -> Result<BulkStiPlan, CliError>

READ-ONLY: compute the bulk classify-inbound-self-transfer plan (bulk-classify-inbound-self-transfer D1). A close MIRROR of bulk_link_transfer_plan applied to Cycle A’s inbound SelfTransferMine ($0 conservative basis, non-taxable). Appends/persists NOTHING (clone-fold-discard recompute); KAT-G1-clean at the TUI call site.

Selection (structural false-classify safety) [R0-I1]: candidates are TransferIn events still flagged UnknownBasisInbound (blocker set joined to the raw event via the index, as self_transfer_match_plan does) MINUS any already targeted by a NON-VOIDED ClassifyInbound (mirror open_classify_inbound_flow’s filter 3 — appending a second fires a return-blocking Hard DecisionConflict; UnknownBasisInbound is RE-EMITTED for gift-basis-unknown states, so “flagged” ≠ “unclassified”) MINUS wallet-less inbounds [R0-M2] (create no lot). The USD is fmv_of [G4]; the total is the HONEST FLOOR (total_usd_fmv_floor + missing_price_count).

Source

pub fn bulk_classify_income_plan( &self, filter: BulkIncomeFilter, ) -> Result<BulkIncomePlan, CliError>

READ-ONLY: compute the bulk classify-inbound-income plan (bulk-classify-inbound-income, Cycle 4). A NEAR-CLONE of bulk_self_transfer_in_plan: candidates are TransferIns still flagged UnknownBasisInbound MINUS any already targeted by a NON-VOIDED ClassifyInbound (filter-3; a second ClassifyInbound fires a Hard DecisionConflict) MINUS wallet-less inbounds (create no lot; also a Hard-FmvMissing vector). Then the Cycle-4 tax-safety difference [#a]: any candidate whose fmv_of(date, sat) is None (missing daily-close price OR overflow) is EXCLUDED from included and counted in excluded_missing_price — a persisted Income{fmv:None} projects to a Hard FmvMissing year-gate (NOT clearable by ManualFmv on the inbound path). included therefore carries a RESOLVED fmv: Usd (non-Option). Appends/persists NOTHING.

Source

pub fn bulk_reclassify_outflow_plan( &self, filter: BulkFilter, ) -> Result<BulkReclassifyOutflowPlan, CliError>

READ-ONLY: compute the bulk reclassify-outflow plan (bulk-reclassify-outflow, Cycle 5). Selects over the PROJECTED pending_reconciliation (which already excludes already-reclassified / linked outs, and never contains wallet-less outflows), enriches each with date / source wallet / principal / RESOLVED auto-FMV / carried basis / estimated gain, and applies the frame + from_wallet filters. Appends/persists NOTHING (clone-fold-discard recompute); mirrors bulk_link_transfer_plan + the bulk_classify_income_plan #a missing-price exclusion.

[#a tax-safety — the whole cycle] a candidate with no price is EXCLUDED (counted in excluded_missing_price), NEVER reclassified: ReclassifyOutflow.principal_proceeds_or_fmv is Usd (NOT Option), so the row could only be built by FABRICATING a 0/bogus proceeds — a SILENT misreport (unlike bulk-income’s LOUD FmvMissing). included therefore carries a RESOLVED fmv: Usd (non-Option), making a fabricated-proceeds Sell structurally unrepresentable here.

Estimated gain [Q3]: basis_usd = Σ pt.legs.usd_basis — computed by the fold’s SINGLE chronological pass with ALL candidate PendingOuts pending, so Σ over multiple rows is NEVER double-counted (an earlier-dated out drew the pool down before a later one folded). estimated_gain = round_cents(fmv − basis_usd) per row. The persisted Form-8949 numbers always run the ordinary fold (exact); only this preview carries the FIFO-vs-method / fee-treatment residual (label it).

Source

pub fn bulk_resolve_conflict_plan(&self) -> Result<BulkResolvePlan, CliError>

READ-ONLY: compute the bulk resolve-conflict plan (bulk-resolve-conflict D1). Candidate set = live ImportConflict blockers only (engine post-filtered — an accepted/rejected conflict is no longer flagged, so re-running never double-resolves; structural idempotence). Joins each blocker (whose .event is the ImportConflict event id) to the event index to build a STRUCTURED row: the conflict’s target + new_payload + new_fingerprint, plus the current_payload read from the TARGET event (a SEPARATE event — accept adopts new_payload, reject keeps current_payload). Appends/persists NOTHING; mirrors bulk_self_transfer_in_plan.

Source

pub fn bulk_void_plan(&self) -> Result<BulkVoidPlan, CliError>

READ-ONLY: compute the bulk-void plan (bulk-void D1). Candidate set = the SINGLE shared predicate btctax_core::voidable_decisions over the projected events + blockers (Decision-id ∧ not-voided ∧ is_revocable_payload ∧ #7 !effective_alloc) — the ONLY defense against sweeping an EFFECTIVE SafeHarborAllocation into a Hard DecisionConflict. Each row precomputes its disposal_to_clear ONCE from the same event set (a LotSelection target → ls.disposal_event) so apply_bulk_void/persist_bulk_void never re-load the log per row. Appends/persists NOTHING; mirrors bulk_resolve_conflict_plan. Sorted by seq for deterministic display.

Source

pub fn self_transfer_match_plan(&self) -> Result<Vec<MatchProposal>, CliError>

READ-ONLY: propose self-transfer matches (self-transfer-passthrough C2). Appends/persists NOTHING (a clone-fold-discard recompute, like bulk_link_transfer_plan). Pairs ONLY unreconciled legs — candidate ins are TransferIns flagged UnknownBasisInbound [R0-M2] (enumerated from the blocker set + joined to the raw event via the event index), candidate outs are pending_reconciliation.

A pair is proposed iff ALL criteria pass: amount within tol = max(out.fee_sat, ceil(0.005 × out.principal)) (a txid EXACT match relaxes the amount check), a ±2-day window consistent with the direction (passthrough: in on/before out; relocate: in on/after out), and BOTH wallets present. The suggested action is wallet topology (same-wallet ⇒ Drop, cross-tracked-wallet ⇒ Relocate). A leg matching >1 counterpart is flagged ambiguous (surfaced, NEVER auto-picked — G-FALSE-MATCH).

Trait Implementations§

Source§

impl Debug for Session

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V