P3: depreciation straight-line by days through the final month, schedules by day (defects 22, 9, D-37)

The final month takes what the months before leave, spread over its days;
a disposed asset stops on its disposal day. A skipped month is caught up
in the month the run is made; a reversed run un-posts its month. New
schedules spread by day; equal-months schedules must start on the 1st.
Amounts round to the currency's own minor unit. Kani and Lean proofs; an
exhaustive search over runs, reversals and disposals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-21 14:04:11 -06:00
1 parent 1304b9c0ad
commit b0a9fd8ad4
69 files changed
+10529 -841

No files matched your search

+45
View File
@@ -47,6 +47,8 @@ pub const KNOWN_FEATURES: &[&str] = &[
PARTY_CREDITORS,
PAYMENT_INSTRUMENTS,
VENDOR_1099_DECISIONS,
MONTH_REPOSTING,
SCHEDULE_CONVENTION,
];
/// Feature: `period.closed` closes to members with `"rounding":
@@ -88,6 +90,17 @@ pub const PAYMENT_INSTRUMENTS: &str = "payment_instruments";
/// (`vendor.w9_flag_set`).
pub const VENDOR_1099_DECISIONS: &str = "vendor_1099_decisions";
/// Feature: a schedule month or a depreciation period posted again after a
/// reversal un-posted it. An older version would refuse the second
/// `schedule.posted` as posted twice, and its projection would ignore the
/// second `asset.depreciation_posted` (P3 depreciation review R3).
pub const MONTH_REPOSTING: &str = "month_reposting";
/// Feature: `schedule.registered` carries `convention` (`daily` or
/// `monthly_even`). An older version would read a daily schedule as equal
/// months (P3 defect 9, plan/served.md D-11).
pub const SCHEDULE_CONVENTION: &str = "schedule_convention";
/// The line type that declares the journal format.
pub const FORMAT_SET: &str = "journal.format_set";
@@ -525,6 +538,38 @@ mod tests {
}
}
/// P3's features (schedule_convention, month_reposting): this version
/// reads them; a version before them (KNOWN_FEATURES empty, as through
/// P2) stops at their declaration with NewerJournal, never misreading a
/// daily schedule as equal months or a second run of a month as none.
#[test]
fn an_older_reader_stops_at_p3s_features() {
for f in [SCHEDULE_CONVENTION, MONTH_REPOSTING] {
assert!(KNOWN_FEATURES.contains(&f));
let l = line(FORMAT_SET, json!({"format": 2, "features": [f]}));
assert_eq!(
declared(&l).unwrap(),
JournalFormat {
format: 2,
features: vec![f.to_string()]
}
);
match declared_knowing(&l, &[]) {
Err(StoreError::NewerJournal { need, .. }) => {
assert_eq!(need, format!("journal features {f}"))
}
other => panic!("expected NewerJournal, got {other:?}"),
}
}
// Declared together, sorted.
assert_eq!(
JournalFormat::default()
.declaration(2, &[SCHEDULE_CONVENTION, MONTH_REPOSTING])
.unwrap(),
json!({"format": 2, "features": ["month_reposting", "schedule_convention"]})
);
}
/// A format newer than this version reads is newer whatever shape the
/// rest of its declaration takes, and however large its number: a
/// newer format may define its features differently, so they are