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

+55
View File
@@ -404,6 +404,61 @@ mod tests {
);
}
#[test]
fn a_value_under_a_key_naming_a_tin_is_masked_whatever_it_looks_like() {
// Even a value that doesn't look like a TIN (a last four, a typo):
// the key says what it is.
for key in [
"tin",
"TIN",
"payee_tin",
"Member_TIN",
"ssn",
"ein",
"itin",
] {
assert_eq!(
digest(&serde_json::json!({ key: "6789" })),
digest(&serde_json::json!({ key: "1234" })),
"{key}"
);
assert_eq!(
digest(&serde_json::json!({ key: {"last4": 6789} })),
digest(&serde_json::json!({ key: ["anything"] })),
"{key}"
);
// A TIN not given isn't one: null stays null.
assert_ne!(
digest(&serde_json::json!({ key: null })),
digest(&serde_json::json!({ key: "6789" })),
"{key}"
);
}
// Only those keys: one that merely holds the letters counts.
assert_ne!(
digest(&serde_json::json!({"tint": "6789"})),
digest(&serde_json::json!({"tint": "1234"}))
);
}
#[test]
fn a_files_bytes_are_hashed_as_they_are_even_where_they_spell_digits() {
// Base64 can hold a run of nine digits; it is still a file's bytes,
// not a TIN, and two files differing there differ.
for key in FILE_BYTES {
assert_ne!(
digest(&serde_json::json!({ key: "QUJD123456789QUJD" })),
digest(&serde_json::json!({ key: "QUJD987814321QUJD" })),
"{key}"
);
}
// The same text anywhere else is masked.
assert_eq!(
digest(&serde_json::json!({"memo": "QUJD123456789QUJD"})),
digest(&serde_json::json!({"memo": "QUJD987814321QUJD"}))
);
}
#[test]
fn a_tin_at_the_cut_is_masked_whole() {
// The TIN straddles the 64-character limit.