P2: tests mutation found, three bugs fixed; plan P12 and P13

Mutation testing pauses until P12 (D-33, owner 2026-10-20), which reruns
the whole max-testing pass over everything since P1. Before the pause,
pass 1 on slices 3-6 and its triage gave tests in ledger-store,
ledger-package and ledger-mcp, and three real bugs, fixed:
- the command line took an admin answer longer than four MiB, or cut off
  before its newline, whenever the bytes that came still parsed;
- The CPA package put a twin's number on the last dot of the whole path,
  so in a folder with a dot the second file landed in another folder;
- a document or category named of dots alone ("..") could climb out of
  its folder when the CPA package is unpacked.
The plan gains P13, bookspec (D-34): a formal, sourced accounting
specification first (rules with symbolic forms and worked examples), then
its own formal validation and a conformance harness any engine can be
tested against, seeded by P3's regression tests. docs/testing.md records
slices 3-8 and what P12 must cover; plan/STATUS.md says where P2 stands.

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-20 11:14:22 -06:00
1 parent 3cf359a0cd
commit d5c9522ad1
24 files changed
+2937 -20

No files matched your search

+79
View File
@@ -256,6 +256,85 @@ mod tests {
const NOW: i64 = 1_800_000_000;
#[test]
fn an_export_is_kept_fifteen_minutes() {
// What its refusal tells the human: kept 15 minutes, then gone.
let dir = tempfile::tempdir().unwrap();
let x = Exports::open(dir.path(), NOW).unwrap();
let e = x.put("t", "a.csv", "text/csv", b"x", NOW).unwrap();
assert_eq!(e.expires, NOW + 15 * 60);
assert!(x.get(&e.id, "t", NOW + 15 * 60 - 1).is_ok());
let lapsed = x.get(&e.id, "t", NOW + 15 * 60).unwrap_err();
assert!(lapsed.to_string().contains("kept 15 minutes"), "{lapsed}");
}
#[test]
fn an_id_is_ex_and_thirty_two_letters_a_to_p_and_nothing_else() {
assert!(is_id(&format!("ex_{}", "a".repeat(32))));
assert!(is_id(&format!("ex_{}", "p".repeat(32))));
for bad in [
String::new(),
"ex_".into(),
format!("ex_{}", "a".repeat(31)),
format!("ex_{}", "a".repeat(33)),
format!("up_{}", "a".repeat(32)),
format!("exx{}", "a".repeat(32)),
format!("ex_{}", "q".repeat(32)),
format!("ex_{}0", "a".repeat(31)),
format!("ex_{}.", "a".repeat(31)),
] {
assert!(!is_id(&bad), "{bad}");
}
}
#[test]
fn a_fetch_never_reaches_past_the_exports_folder() {
// A whole export kept beside the books, not among their exports:
// no id names it.
let dir = tempfile::tempdir().unwrap();
let x = Exports::open(dir.path(), NOW).unwrap();
let bytes = b"not an export".to_vec();
for id in ["../outside", &format!("up_{}", "a".repeat(32))] {
let at = dir.path().join(EXPORTS_DIR).join(id);
let record = Export {
id: id.into(),
token: "t".into(),
file_name: "a.csv".into(),
mime: "text/csv".into(),
size: bytes.len() as u64,
sha256: sha256_hex(&bytes),
expires: NOW + EXPORT_TTL,
};
std::fs::write(
at.with_extension("json"),
serde_json::to_vec(&record).unwrap(),
)
.unwrap();
std::fs::write(at.with_extension("bin"), &bytes).unwrap();
assert!(
matches!(x.get(id, "t", NOW).unwrap_err(), ExportError::Unknown(_)),
"{id}"
);
}
// And a refusal quotes no more than 40 characters of what it got.
let long = format!("../{}", "x".repeat(100));
let ExportError::Unknown(quoted) = x.get(&long, "t", NOW).unwrap_err() else {
panic!("an unknown export")
};
assert_eq!(quoted.chars().count(), 40);
}
#[test]
fn an_export_counts_against_its_token_until_the_moment_it_lapses() {
let dir = tempfile::tempdir().unwrap();
let x = Exports::open(dir.path(), NOW).unwrap();
let e = x.put("t", "a.csv", "text/csv", b"x", NOW).unwrap();
assert_eq!(x.live_for("t", e.expires - 1), 1);
// Lapsed at its expiry, as a fetch then finds (not yet swept).
assert_eq!(x.live_for("t", e.expires), 0);
assert_eq!(x.live_for("u", NOW), 0);
}
#[test]
fn an_export_is_fetched_by_its_token_until_it_lapses() {
let dir = tempfile::tempdir().unwrap();