use crate::event::EventPayload;
use crate::LedgerEvent;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[non_exhaustive]
pub struct ExperimentalNotice {
pub title: &'static str,
pub summary: &'static str,
pub defects: &'static [&'static str],
pub action: &'static str,
}
pub const NOTICE: ExperimentalNotice = ExperimentalNotice {
title: "EXPERIMENTAL — DEFENSIVE FILING (declare/promote tranche, Form 8275, estimated basis)",
summary: "This feature is newer and less proven than the rest of btctax, and it was developed with \
heavy AI assistance. Defects that affect what gets FILED have shipped and were found only by \
later review; all known ones are fixed.",
defects: &[
"the Form 8275 disclosure was silently truncated to its first ~137 characters",
"a change the editor said had been rolled back could still be written to your vault, silently \
changing the figures on your forms",
"a filed basis figure could be derived from a stale in-editor ledger image after a failed \
re-projection",
],
action: "Check what this feature actually produced: open the Form 8275 PDF and confirm the Part II \
narrative renders whole, continuing onto Part IV on page 2, rather than stopping mid-sentence \
after about one line; confirm the basis in Form 8949 column (e) for each promoted lot equals \
the floor you consented to at promote time; and confirm the tranche quantity and acquisition \
window on the 8275 match what you declared.",
};
impl ExperimentalNotice {
pub fn plain_text(&self) -> String {
let mut s = String::new();
s.push_str(self.title);
s.push_str("\n\n");
s.push_str(self.summary);
s.push('\n');
for d in self.defects {
s.push_str(" - ");
s.push_str(d);
s.push('\n');
}
s.push('\n');
s.push_str(self.action);
s.push('\n');
s
}
pub fn one_line(&self) -> String {
format!("{} — {} {}", self.title, self.summary, self.action)
}
}
#[doc(hidden)]
pub mod testonly {
use super::NOTICE;
pub fn leak_guard_needles() -> Vec<&'static str> {
let mut v = vec![NOTICE.title, NOTICE.summary, NOTICE.action];
v.extend(NOTICE.defects.iter().copied());
v
}
}
pub fn uses_approach_b(events: &[LedgerEvent]) -> bool {
let voided = crate::tranche_guard::void_targets(events);
events.iter().any(|e| {
!voided.contains(&e.id)
&& matches!(
e.payload,
EventPayload::DeclareTranche(_) | EventPayload::PromoteTranche(_)
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::{
Acknowledgment, DeclareTranche, FloorMethod, PromoteTranche, VoidDecisionEvent,
};
use crate::identity::{EventId, WalletId};
use rust_decimal_macros::dec;
use time::macros::{date, datetime, offset};
fn wallet() -> WalletId {
WalletId::SelfCustody {
label: "cold".into(),
}
}
fn dec_ev(seq: u64, payload: EventPayload) -> LedgerEvent {
LedgerEvent {
id: EventId::decision(seq),
utc_timestamp: datetime!(2026-01-01 00:00 UTC),
original_tz: offset!(+00:00),
wallet: None,
payload,
}
}
fn tranche_ev(seq: u64) -> LedgerEvent {
dec_ev(
seq,
EventPayload::DeclareTranche(DeclareTranche {
sat: 100_000_000,
wallet: wallet(),
window_start: date!(2018 - 01 - 01),
window_end: date!(2018 - 12 - 31),
}),
)
}
fn promote_ev(seq: u64, target: EventId) -> LedgerEvent {
dec_ev(
seq,
EventPayload::PromoteTranche(PromoteTranche {
target,
method: FloorMethod::WindowLowClose,
filed_basis: dec!(1000),
coverage: crate::conservative::Coverage::Full,
provenance_attested: true,
acknowledgment: Acknowledgment {
phrase: "ack".into(),
shown_terms: vec![],
provenance_text: "provenance".into(),
provenance_version: "v1".into(),
},
part_ii_narrative: "narrative".into(),
}),
)
}
fn void_ev(seq: u64, target: EventId) -> LedgerEvent {
dec_ev(
seq,
EventPayload::VoidDecisionEvent(VoidDecisionEvent {
target_event_id: target,
}),
)
}
#[test]
fn no_tranche_is_false() {
let evs: Vec<LedgerEvent> = vec![];
assert!(!uses_approach_b(&evs));
}
#[test]
fn live_declare_tranche_is_true() {
let evs = vec![tranche_ev(1)];
assert!(uses_approach_b(&evs));
}
#[test]
fn live_promote_tranche_is_true() {
let target = EventId::decision(1);
let evs = vec![tranche_ev(1), promote_ev(2, target)];
assert!(uses_approach_b(&evs));
}
#[test]
fn voided_only_tranche_is_false() {
let tranche_id = EventId::decision(1);
let evs = vec![tranche_ev(1), void_ev(2, tranche_id)];
assert!(!uses_approach_b(&evs));
}
#[test]
fn voided_declare_and_voided_promote_is_false() {
let tranche_id = EventId::decision(1);
let promote_id = EventId::decision(2);
let evs = vec![
tranche_ev(1),
promote_ev(2, tranche_id.clone()),
void_ev(3, tranche_id),
void_ev(4, promote_id),
];
assert!(!uses_approach_b(&evs));
}
#[test]
fn declare_voided_but_promote_still_live_is_true() {
let tranche_id = EventId::decision(1);
let evs = vec![
tranche_ev(1),
promote_ev(2, tranche_id.clone()),
void_ev(3, tranche_id), ];
assert!(uses_approach_b(&evs));
}
#[test]
fn unrelated_events_are_false() {
use crate::event::Acquire;
use crate::identity::{Source, SourceRef};
use crate::BasisSource;
let evs = vec![LedgerEvent {
id: EventId::import(Source::Coinbase, SourceRef::new("x")),
utc_timestamp: datetime!(2026-01-01 00:00 UTC),
original_tz: offset!(+00:00),
wallet: Some(wallet()),
payload: EventPayload::Acquire(Acquire {
sat: 1,
usd_cost: dec!(1),
fee_usd: dec!(0),
basis_source: BasisSource::ExchangeProvided,
}),
}];
assert!(!uses_approach_b(&evs));
}
#[test]
fn plain_text_preserves_every_fact() {
let t = NOTICE.plain_text();
assert!(t.contains("newer"), "{t}");
assert!(t.contains("less proven"), "{t}");
assert!(t.contains("AI assistance"), "{t}");
assert!(t.contains("found only by later review"), "{t}");
assert!(t.contains("137 characters"), "{t}");
assert!(
t.contains("could still be written to your vault, silently changing the figures"),
"{t}"
);
assert!(
t.contains("a stale in-editor ledger image after a failed re-projection"),
"{t}"
);
assert!(t.contains("all known ones are fixed"), "{t}");
assert!(t.contains("Form 8949 column (e)"), "{t}");
assert!(t.contains("Part IV on page 2"), "{t}");
assert!(t.ends_with('\n'), "{t:?}");
assert!(
!t.contains('\u{1b}'),
"plain_text must carry no ANSI escapes: {t:?}"
);
}
#[test]
fn one_line_is_derived_from_title_summary_and_action() {
assert_eq!(
NOTICE.one_line(),
format!("{} — {} {}", NOTICE.title, NOTICE.summary, NOTICE.action)
);
let l = NOTICE.one_line();
assert!(l.contains(NOTICE.title), "{l}");
assert!(l.contains(NOTICE.summary), "{l}");
assert!(l.contains(NOTICE.action), "{l}");
}
#[test]
fn notice_fields_are_presentation_neutral() {
let mut fields: Vec<&str> = vec![NOTICE.title, NOTICE.summary, NOTICE.action];
fields.extend(NOTICE.defects.iter().copied());
for f in fields {
assert!(!f.contains('\n'), "no embedded newline: {f:?}");
assert!(!f.contains('\r'), "no embedded CR: {f:?}");
assert!(!f.contains('\u{1b}'), "no ANSI escape: {f:?}");
assert!(!f.contains('\t'), "no tab: {f:?}");
assert!(
!f.contains(" "),
"no run of 2+ spaces (a hard-wrap/indent tell): {f:?}"
);
}
}
#[test]
fn leak_guard_needles_covers_every_field() {
let needles = testonly::leak_guard_needles();
assert!(needles.contains(&NOTICE.title));
assert!(needles.contains(&NOTICE.summary));
assert!(needles.contains(&NOTICE.action));
for d in NOTICE.defects {
assert!(needles.contains(d), "missing defect: {d:?}");
}
}
}