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:
1 parent
3cf359a0cd
commit
d5c9522ad1
24 files changed
+2937
-20
No files matched your search
@@ -410,8 +410,12 @@ pub fn build_package<B: PackageBooks + ?Sized>(
|
||||
let mut used: std::collections::HashSet<String> =
|
||||
files.iter().map(|(n, _)| n.to_lowercase()).collect();
|
||||
let mut unique = |name: String| -> String {
|
||||
let (stem, ext) = match name.rsplit_once('.') {
|
||||
Some((s, e)) if !s.ends_with('/') && !s.is_empty() => (s.to_string(), format!(".{e}")),
|
||||
// The extension is the last segment's own: a folder with a dot
|
||||
// ("a.b/notes") keeps its name, and a name that is all extension
|
||||
// (".env") has none.
|
||||
let cut = name.rfind('/').map_or(0, |i| i + 1);
|
||||
let (stem, ext) = match name[cut..].rfind('.') {
|
||||
Some(i) if i > 0 => (name[..cut + i].to_string(), name[cut + i..].to_string()),
|
||||
_ => (name.clone(), String::new()),
|
||||
};
|
||||
let mut n = 1;
|
||||
@@ -424,7 +428,8 @@ pub fn build_package<B: PackageBooks + ?Sized>(
|
||||
};
|
||||
if req.include_receipts {
|
||||
let clean = |s: &str| -> String {
|
||||
s.chars()
|
||||
let c: String = s
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_alphanumeric() || ".-_ ".contains(c) {
|
||||
c
|
||||
@@ -432,7 +437,14 @@ pub fn build_package<B: PackageBooks + ?Sized>(
|
||||
'_'
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
// A name of dots alone ("", ".", "..") would name no file, or
|
||||
// climb out of its folder when the zip is unpacked.
|
||||
if c.chars().all(|c| c == '.') {
|
||||
"_".repeat(c.len().max(1))
|
||||
} else {
|
||||
c
|
||||
}
|
||||
};
|
||||
let mut index = String::from("entry_id,date,description,amount,file,sha256\n");
|
||||
for a in books.all_attachments().map_err(|e| e.to_string())? {
|
||||
|
||||
Reference in new issue
Block a user