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§
- Pseudo
Approve Filter - The
--kind/--wallet/--yearfilter forreconcile pseudo approve.Nonefields = no restriction. - Pseudo
Approve Row - One row of a
reconcile pseudo approvepreview (sub-project 2, T5).targetis the imported event the synthetic governs;kindis the default TYPE;wallet/yearare the target’s provenance (for the filter + preview). Deterministic order (mirrorspseudo_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
SupersedeImportper conflict (adopt eachnew_payloadonto its target id), then a SINGLEsave. All-or-nothing: a mid-batchappend_decisionfailure returnsErrBEFORE the save, and the localSessionis dropped with nothing written — the exact one-session / N-append / one-save atomicity ofapply_bulk_link_transfer. Mirrors the shipped single-item splitaccept_conflict/reject_conflict[R0-I1 — NOResolveKindin 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_eventasIncome { kind, fmv, business }with a PER-ROW auto-FMV. Its OWN append-loop (mirrorsapply_bulk_self_transfer_in; NOT the tui-editpersist_bulk_decisions, which btctax-cli cannot reach — dependency cycle, R0-I1). OneClassifyInbound { transfer_in_event, Income{..} }per row, bare?-before-save(a mid-batch failure returns beforesave→ the in-memory session is discarded, nothing lands on disk = CLI atomicity), then a SINGLEsession.save(). - apply_
bulk_ link_ transfer - bulk-link-transfer D2 — Phase 2 (write): atomically link every
out_eventtodestas a self-transfer. Appends oneTransferLink { out_event, Wallet(dest) }per row, then a SINGLEsave. All-or-nothing: a mid-batchappend_decisionfailure returnsErrBEFORE the save, and the localSessionis dropped with nothing written — the exact one-session / N-append / one-save atomicity ofimport_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-editpersist_bulk_decisions— dependency cycle, Cargo.toml:19). Empty-guard (no matches ⇒ NO save); bare?-before-save(a mid-batch failure returns beforesave, discarding the in-memory session = CLI atomicity); a SINGLEsave. 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_eventas aDispose{kind}with a PER-ROW auto-FMV as ESTIMATED proceeds, AND mark each in thebulk_estimatedside-table, then a SINGLEsave. Its OWN append-loop (mirrorsapply_bulk_classify_inbound_income; the CLI CANNOT reach the tui-editpersist_bulk_decisions— dependency cycle, R0-I1 of Cycle 4). - apply_
bulk_ reject_ conflicts - bulk-resolve-conflict D2 — Phase 2 (write), REJECT: atomically append one
RejectImportper conflict (keep each target’s current payload; clear the blocker), then a SINGLEsave. Same all-or-nothing CLI atomicity asapply_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_eventas aSelfTransferMine { basis: None, acquired_at: None }($0 conservative basis, non-taxable). Appends oneClassifyInbound { transfer_in_event, SelfTransferMine{None, None} }per row, then a SINGLEsave. All-or-nothing: a mid-batchappend_decisionfailure returnsErrBEFORE the save, and the localSessionis dropped with nothing written — the exact one-session / N-append / one-save atomicity ofapply_bulk_link_transfer. Returns the number of inbounds classified. - apply_
bulk_ void - bulk-void D2 — Phase 2 (write): atomically append one
VoidDecisionEventpertargetAND, for eachLotSelectiontarget, clear its optimizer attestation (optimize_attest::clear), then a SINGLEsave. All-or-nothing: a mid-batchappend_decision/clearfailure returnsErrBEFORE the save, and the localSessionis dropped with nothing written — the exact one-session / N-append / one-save atomicity ofapply_bulk_accept_conflicts(the TUI path instead ROLLS BACK explicitly viapersist_bulk_void). - apply_
self_ transfer_ passthrough - self-transfer-passthrough C3 — Phase 2 (write), DROP: append one
SelfTransferPassthroughdecision mapping BOTH legs toOp::Skip(non-taxable passthrough). Mirrorslink_transfer(one append + save). The RELOCATE case is NOT here — it routes to the EXISTINGlink_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 helperSession::bulk_classify_income_plan— it excludes missing-price rows [#a tax-safety] and reports them asexcluded_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 helperSession::bulk_reclassify_outflow_plan— it excludes missing-price rows [#a tax-safety] and reports them asexcluded_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 interactivey/Nconfirmation stays a thin, untested shell in themain.rsdispatch. The plan is the shared read helperSession::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 interactivey/Nconfirmation stays a thin, untested shell in themain.rsdispatch. The plan is the shared read helperSession::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 interactivey/Nconfirmation stays a thin, untested shell in themain.rsdispatch. The plan is the shared read helperSession::bulk_void_plan(D1) — the SINGLEvoidable_decisionspredicate, which OMITS effective allocations (#7). The session (and its VaultLock) is dropped on return. - classify_
inbound - FR6: classify an externally-sourced inbound
TransferInas 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 Swandepositbasis GAP. - classify_
raw - FR2/§7.3: resolve an
Unclassifiedrow to a real imported payload (preserving the target EventId). The payload is supplied as JSON (EventPayloadisDeserialize) — e.g.{"Acquire":{…}}. - import_
selections - §A.4 / SPEC Task 5: batch-import
LotSelectiondecisions from a CSV file. - link_
transfer - FR6/TP7: confirm a self-transfer.
targetis a destinationTransferInevent (--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_reconcileflag incli_config(a projection input parameter, NOT ledger state — NFR6).on = true⇒ projection synthesizes non-persisted default decisions to clear the Hard classification blockers;offreverts 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 optionallykind) on an already-importedIncomeevent. Enables SE-tax treatment for professional miners / stakers whose River (and other adapter) income arrives withbusiness: falsehard-coded at ingest time. - reclassify_
outflow - FR6: reclassify a pending
TransferOutas a Sell/Spend disposition, a Gift out, or a Donation.principalis the gross proceeds (Dispose) or FMV-at-transfer (Gift/Donate);fee_usdis the optional disposition fee (TP8 / TP2). The engine applies the configured TP8 (c)/(b) fee treatment.doneeis the optional free-form donee identifier (Chunk 2);Nonefor 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
LotSelectiondecision for a specific disposal.disposal_refis the canonicalEventIdstring of the disposal event;picksis at least oneLotPick. 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 isSession::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_detailsside-table for the donation identified byevent_ref. - set_fmv
- FR3: set a manual FMV on an event (
ManualEntry), clearing itsfmv_missingblocker. - set_
forward_ method - M3 / SPEC A.1 / A.5(a): append a
MethodElectiondecision — the forward standing order. - show_
donation_ details - Chunk 3b D2: read back stored
DonationDetailsfor the donation identified byevent_ref. ReturnsNonewhen 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_conflictsin the projection (no effect) — the CLI only appends; the engine adjudicates.