Skip to main content

Module reconcile

Module reconcile 

Source
Expand description

reconcile decision emitters (FR6/FR7/FR8, §7.2). Each fn builds exactly ONE EventPayload decision variant and appends it via append_decision (monotonic decision_seq), then saves. Decisions are append-only and re-projectable; the engine resolves precedence (latest-decision_seq, Void-first). now is the injected decision creation-time / safe-harbor made-date (§6.2) — deterministic in tests.

Also contains the set-donation-details / show-donation-details side-table commands (no decision append — these write to the donation_details side-table directly, like tax-profile set).

Structs§

PseudoApproveFilter
The --kind/--wallet/--year filter for reconcile pseudo approve. None fields = no restriction.
PseudoApproveRow
One row of a reconcile pseudo approve preview (sub-project 2, T5). target is the imported event the synthetic governs; kind is the default TYPE; wallet/year are the target’s provenance (for the filter + preview). Deterministic order (mirrors pseudo_plan).

Functions§

accept_conflict
FR1/FR8: accept an ImportConflict (apply the new payload to the target, keeping its EventId).
apply_bulk_accept_conflicts
bulk-resolve-conflict D2 — Phase 2 (write), ACCEPT: atomically append one SupersedeImport per conflict (adopt each new_payload onto its target id), then a SINGLE save. All-or-nothing: a mid-batch append_decision failure returns Err BEFORE the save, and the local Session is dropped with nothing written — the exact one-session / N-append / one-save atomicity of apply_bulk_link_transfer. Mirrors the shipped single-item split accept_conflict/reject_conflict [R0-I1 — NO ResolveKind in the CLI; it lives only in btctax-tui-edit]. Returns the number accepted.
apply_bulk_classify_inbound_income
bulk-classify-inbound-income (Cycle 4) — Phase 2 (write): atomically classify every in_event as Income { kind, fmv, business } with a PER-ROW auto-FMV. Its OWN append-loop (mirrors apply_bulk_self_transfer_in; NOT the tui-edit persist_bulk_decisions, which btctax-cli cannot reach — dependency cycle, R0-I1). One ClassifyInbound { transfer_in_event, Income{..} } per row, bare ?-before-save (a mid-batch failure returns before save → the in-memory session is discarded, nothing lands on disk = CLI atomicity), then a SINGLE session.save().
apply_bulk_link_transfer
bulk-link-transfer D2 — Phase 2 (write): atomically link every out_event to dest as a self-transfer. Appends one TransferLink { out_event, Wallet(dest) } per row, then a SINGLE save. All-or-nothing: a mid-batch append_decision failure returns Err BEFORE the save, and the local Session is dropped with nothing written — the exact one-session / N-append / one-save atomicity of import_selections. Returns the number of outflows linked.
apply_bulk_pseudo_approve
Pseudo-approve — Phase 2 (write): materialize the filtered pseudo defaults as REAL (attested) decisions via btctax-cli’s OWN append-loop [R0-M4] (mirrors apply_bulk_classify_inbound_income; the CLI CANNOT reach the tui-edit persist_bulk_decisions — dependency cycle, Cargo.toml:19). Empty-guard (no matches ⇒ NO save); bare ?-before-save (a mid-batch failure returns before save, discarding the in-memory session = CLI atomicity); a SINGLE save. Defense-in-depth: RE-derives the plan here (never trusts a threaded list). After approval these decisions are REAL → the next projection resolves them via the real decision path, so pseudo mode injects NO synthetic for them (no longer [PSEUDO]). Returns the count.
apply_bulk_reclassify_outflow
bulk-reclassify-outflow (Cycle 5) — Phase 2 (write): atomically reclassify every out_event as a Dispose{kind} with a PER-ROW auto-FMV as ESTIMATED proceeds, AND mark each in the bulk_estimated side-table, then a SINGLE save. Its OWN append-loop (mirrors apply_bulk_classify_inbound_income; the CLI CANNOT reach the tui-edit persist_bulk_decisions — dependency cycle, R0-I1 of Cycle 4).
apply_bulk_reject_conflicts
bulk-resolve-conflict D2 — Phase 2 (write), REJECT: atomically append one RejectImport per conflict (keep each target’s current payload; clear the blocker), then a SINGLE save. Same all-or-nothing CLI atomicity as apply_bulk_accept_conflicts. Returns the number rejected.
apply_bulk_self_transfer_in
bulk-classify-inbound-self-transfer D2 — Phase 2 (write): atomically classify every in_event as a SelfTransferMine { basis: None, acquired_at: None } ($0 conservative basis, non-taxable). Appends one ClassifyInbound { transfer_in_event, SelfTransferMine{None, None} } per row, then a SINGLE save. All-or-nothing: a mid-batch append_decision failure returns Err BEFORE the save, and the local Session is dropped with nothing written — the exact one-session / N-append / one-save atomicity of apply_bulk_link_transfer. Returns the number of inbounds classified.
apply_bulk_void
bulk-void D2 — Phase 2 (write): atomically append one VoidDecisionEvent per target AND, for each LotSelection target, clear its optimizer attestation (optimize_attest::clear), then a SINGLE save. All-or-nothing: a mid-batch append_decision / clear failure returns Err BEFORE the save, and the local Session is dropped with nothing written — the exact one-session / N-append / one-save atomicity of apply_bulk_accept_conflicts (the TUI path instead ROLLS BACK explicitly via persist_bulk_void).
apply_self_transfer_passthrough
self-transfer-passthrough C3 — Phase 2 (write), DROP: append one SelfTransferPassthrough decision mapping BOTH legs to Op::Skip (non-taxable passthrough). Mirrors link_transfer (one append + save). The RELOCATE case is NOT here — it routes to the EXISTING link_transfer(out, InEvent(in)) (G-RELOCATE-REUSE).
bulk_classify_income_plan
bulk-classify-inbound-income (Cycle 4) — Phase 1 (read): open the session and compute the bulk classify-income plan. Two-phase by design (mirrors bulk_self_transfer_in_plan): this read phase renders NOTHING to the vault. The plan is the shared read helper Session::bulk_classify_income_plan — it excludes missing-price rows [#a tax-safety] and reports them as excluded_missing_price.
bulk_link_plan
bulk-link-transfer D2 — Phase 1 (read): open the session and compute the bulk link-transfer plan.
bulk_reclassify_outflow_plan
bulk-reclassify-outflow (Cycle 5) — Phase 1 (read): open the session and compute the bulk reclassify-outflow plan. Two-phase by design (mirrors bulk_classify_income_plan): this read phase renders NOTHING to the vault. The plan is the shared read helper Session::bulk_reclassify_outflow_plan — it excludes missing-price rows [#a tax-safety] and reports them as excluded_missing_price.
bulk_resolve_conflict_plan
bulk-resolve-conflict D2 — Phase 1 (read): open the session and compute the bulk resolve-conflict plan. Two-phase by design (mirrors bulk_link_plan): this read phase renders NOTHING to the vault, so the interactive y/N confirmation stays a thin, untested shell in the main.rs dispatch. The plan is the shared read helper Session::bulk_resolve_conflict_plan (D1). The session (and its VaultLock) is dropped on return, before the confirmation prompt runs.
bulk_self_transfer_in_plan
bulk-classify-inbound-self-transfer D2 — Phase 1 (read): open the session and compute the bulk STI plan. Two-phase by design (mirrors bulk_link_plan): this read phase renders NOTHING to the vault, so the interactive y/N confirmation stays a thin, untested shell in the main.rs dispatch. The plan is the shared read helper Session::bulk_self_transfer_in_plan (D1). The session (and its VaultLock) is dropped on return, before the confirmation prompt runs.
bulk_void_plan
bulk-void D2 — Phase 1 (read): open the session and compute the bulk-void plan. Two-phase by design (mirrors bulk_resolve_conflict_plan): this read phase renders NOTHING to the vault, so the interactive y/N confirmation stays a thin, untested shell in the main.rs dispatch. The plan is the shared read helper Session::bulk_void_plan (D1) — the SINGLE voidable_decisions predicate, which OMITS effective allocations (#7). The session (and its VaultLock) is dropped on return.
classify_inbound
FR6: classify an externally-sourced inbound TransferIn as Income or a received Gift. For Income this supplies the FMV basis; for Gift it supplies donor basis/date + fmv_at_gift (TP11 dual-basis). This is the re-supply path for the §9.1 Swan deposit basis GAP.
classify_raw
FR2/§7.3: resolve an Unclassified row to a real imported payload (preserving the target EventId). The payload is supplied as JSON (EventPayload is Deserialize) — e.g. {"Acquire":{…}}.
import_selections
§A.4 / SPEC Task 5: batch-import LotSelection decisions from a CSV file.
link_transfer
FR6/TP7: confirm a self-transfer. target is a destination TransferIn event (--to-event) or a known wallet (--to-wallet); the engine relocates the lots carrying basis + acquired_at.
pseudo_approve_plan
Pseudo-approve — Phase 1 (read): compute the filtered plan of pseudo defaults that WOULD be promoted to real decisions. Renders NOTHING to the vault (the confirmation stays a thin shell in main.rs).
pseudo_set_mode
Pseudo-reconcile mode toggle (sub-project 2). Persists the pseudo_reconcile flag in cli_config (a projection input parameter, NOT ledger state — NFR6). on = true ⇒ projection synthesizes non-persisted default decisions to clear the Hard classification blockers; off reverts to real-only instantly (no fictional events were ever written). Returns the new flag value.
reclassify_income
SE-completion Chunk C (D3): flip business (and optionally kind) on an already-imported Income event. Enables SE-tax treatment for professional miners / stakers whose River (and other adapter) income arrives with business: false hard-coded at ingest time.
reclassify_outflow
FR6: reclassify a pending TransferOut as a Sell/Spend disposition, a Gift out, or a Donation. principal is the gross proceeds (Dispose) or FMV-at-transfer (Gift/Donate); fee_usd is the optional disposition fee (TP8 / TP2). The engine applies the configured TP8 (c)/(b) fee treatment. donee is the optional free-form donee identifier (Chunk 2); None for disposals and legacy records.
reject_conflict
FR1/FR8: reject an ImportConflict (keep the original; clear the blocker).
safe_harbor_allocate
FR7/§7.4: build a Path-B safe-harbor allocation that seeds from the pre-2025 residue (the 2025-01-01 Universal-pool position), so it conserves against the engine’s allocation-independent conservation guard.
safe_harbor_attest
FR7: attest an existing allocation. Events are immutable, so attestation = void the single live prior allocation and re-append it with timely_allocation_attested = true. Attestation only cures a §5.02(4) TIME-BAR; it is NOT valid on an already-effective allocation (which needs nothing) nor on one that fails CONSERVATION (which needs a corrected allocation, not an attestation).
select_lots
§A.4 / SPEC Task 5: emit a LotSelection decision for a specific disposal. disposal_ref is the canonical EventId string of the disposal event; picks is at least one LotPick. The engine validates completeness (Σsat == disposal principal) and lot existence in the fold; this function only appends the decision — it does NOT attempt to validate up-front (that would require a full projection, which the engine always does). Identification must exist by the time of sale (§1.1012-1(j)).
self_transfer_match_plan
self-transfer-passthrough C3 — Phase 1 (read): compute the self-transfer match proposals on the HELD session (READ-ONLY; appends/persists NOTHING). Mirrors bulk_link_plan: the shared read helper is Session::self_transfer_match_plan; the session (and its VaultLock) is dropped on return.
set_donation_details
Chunk 3b D2: store Form 8283 Section-B donation + appraiser details in the donation_details side-table for the donation identified by event_ref.
set_fmv
FR3: set a manual FMV on an event (ManualEntry), clearing its fmv_missing blocker.
set_forward_method
M3 / SPEC A.1 / A.5(a): append a MethodElection decision — the forward standing order.
show_donation_details
Chunk 3b D2: read back stored DonationDetails for the donation identified by event_ref. Returns None when no details have been stored yet. Read-only — no projection needed.
void
FR8: void a revocable decision. Voiding a non-revocable / effective-allocation target raises decision_conflicts in the projection (no effect) — the CLI only appends; the engine adjudicates.