test: the tests P6's gate never reached, under one-record dedup
P6's branch paused with its gate interrupted, so five tests that predate plan P6 step 4 (the same bytes filed twice are one attachment, linked to each subject) still encoded the invariant it replaced, and one golden carried counts from before extraction records existed. - store: `blob_dedup_and_hash_verification` expects one attachment row under the first filing's name, not two. The `attach_receipt` spec block's INVARIANT 2/3 now say what P6 built; the stale "each filing is its own attachment" was the one wording left. - store: the power-loss replay copies the blob store too. The books on disk were always journal, database and blobs; verify now reads an extraction's text blob (P6 step 2), so a copy without `blobs/` cannot verify. - store: the refusals property allows `AttachReceipt` and `FileDocument` to journal nothing when the bytes are already on file: the record they return is the record they were. - viewer: each statement the reconciliation helper files has its own bytes (a wrong statement is voided and a new one comes); identical bytes would be one record, and a record is one statement. - viewer: the tall sidebar's shapes golden is re-blessed. The fixture books now carry extraction records, so the Overview attention count, the event count and the verified-file count the sidebar prints moved; no layout changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
6c26160e26
commit
bfd453b679
5 files changed
+38
-21
No files matched your search
@@ -224,12 +224,16 @@ fn reconcile(
|
|||||||
};
|
};
|
||||||
let opening = report(s, month_start.pred_opt().unwrap());
|
let opening = report(s, month_start.pred_opt().unwrap());
|
||||||
let closing = report(s, through);
|
let closing = report(s, through);
|
||||||
|
// Each call files a new statement (a wrong one is voided and another
|
||||||
|
// comes), so each has its own bytes: identical bytes would be one
|
||||||
|
// record (plan P6 step 4), and a record is one statement.
|
||||||
|
let filing = s.statements(None).unwrap().len() + 1;
|
||||||
let (att, _, _) = s
|
let (att, _, _) = s
|
||||||
.file_document(
|
.file_document(
|
||||||
"statement",
|
"statement",
|
||||||
&format!("statement-{through}.pdf"),
|
&format!("statement-{through}.pdf"),
|
||||||
"application/pdf",
|
"application/pdf",
|
||||||
format!("statement {through}").as_bytes(),
|
format!("statement {filing}: {through} for account {account}").as_bytes(),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
@@ -1 +1 @@
|
|||||||
5ccd68030ec4a54b
|
b4c03adfe9dc03e6
|
||||||
@@ -4488,9 +4488,11 @@ impl Store {
|
|||||||
/// INVARIANTS:
|
/// INVARIANTS:
|
||||||
/// 1. The bytes MUST be stored under their SHA-256 and read back
|
/// 1. The bytes MUST be stored under their SHA-256 and read back
|
||||||
/// identical, verified against it.
|
/// identical, verified against it.
|
||||||
/// 2. The same bytes filed twice MUST share one stored file, and the
|
/// 2. The same bytes filed twice MUST be one record (plan P6 step 4):
|
||||||
/// second filing MUST say deduplicated.
|
/// the second filing returns the first's id, says deduplicated,
|
||||||
/// 3. Each filing MUST be its own attachment, journaled
|
/// and links that record to the new subject (`attachment.linked`)
|
||||||
|
/// when it isn't linked there yet.
|
||||||
|
/// 3. The first filing of bytes MUST be journaled
|
||||||
/// (`receipt.attached`), with its name, type, and description.
|
/// (`receipt.attached`), with its name, type, and description.
|
||||||
/// SCENARIOS:
|
/// SCENARIOS:
|
||||||
/// 1. IF `entry_id` names no entry: SHOULD refuse
|
/// 1. IF `entry_id` names no entry: SHOULD refuse
|
||||||
|
|||||||
@@ -235,6 +235,19 @@ fn damage_before_the_last_newline_still_refuses_to_open() {
|
|||||||
assert_eq!(std::fs::read(&journal).unwrap(), raw);
|
assert_eq!(std::fs::read(&journal).unwrap(), raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every file and directory under `from`, under `to`.
|
||||||
|
fn copy_tree(from: &std::path::Path, to: &std::path::Path) {
|
||||||
|
std::fs::create_dir_all(to).unwrap();
|
||||||
|
for e in std::fs::read_dir(from).unwrap() {
|
||||||
|
let e = e.unwrap();
|
||||||
|
if e.file_type().unwrap().is_dir() {
|
||||||
|
copy_tree(&e.path(), &to.join(e.file_name()));
|
||||||
|
} else {
|
||||||
|
std::fs::copy(e.path(), to.join(e.file_name())).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Power loss at any byte of the last append: whatever prefix reached the
|
/// Power loss at any byte of the last append: whatever prefix reached the
|
||||||
/// disk, the books open with exactly what was acknowledged before it.
|
/// disk, the books open with exactly what was acknowledged before it.
|
||||||
#[test]
|
#[test]
|
||||||
@@ -246,14 +259,11 @@ fn power_loss_at_any_byte_of_the_last_append_is_recovered() {
|
|||||||
let before = ledger_core::reports::snapshot(store.ledger()).unwrap();
|
let before = ledger_core::reports::snapshot(store.ledger()).unwrap();
|
||||||
drop(store);
|
drop(store);
|
||||||
// The books as they were on disk before the last append (the database
|
// The books as they were on disk before the last append (the database
|
||||||
// is never ahead of the journal: the line is written first).
|
// is never ahead of the journal: the line is written first), blobs
|
||||||
|
// included: verify reads the receipt's extracted text from them (plan
|
||||||
|
// P6 step 2).
|
||||||
let pre = test_dir("tornevery-pre");
|
let pre = test_dir("tornevery-pre");
|
||||||
for e in std::fs::read_dir(&dir).unwrap() {
|
copy_tree(&dir, &pre);
|
||||||
let e = e.unwrap();
|
|
||||||
if e.file_type().unwrap().is_file() {
|
|
||||||
std::fs::copy(e.path(), pre.join(e.file_name())).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut store = Store::open(&dir).unwrap();
|
let mut store = Store::open(&dir).unwrap();
|
||||||
store.add_customer("Acme").unwrap();
|
store.add_customer("Acme").unwrap();
|
||||||
drop(store);
|
drop(store);
|
||||||
@@ -262,12 +272,7 @@ fn power_loss_at_any_byte_of_the_last_append_is_recovered() {
|
|||||||
assert_eq!(last.last(), Some(&b'\n'));
|
assert_eq!(last.last(), Some(&b'\n'));
|
||||||
for cut in 1..last.len() {
|
for cut in 1..last.len() {
|
||||||
let case = test_dir(&format!("tornevery-{cut}"));
|
let case = test_dir(&format!("tornevery-{cut}"));
|
||||||
for e in std::fs::read_dir(&pre).unwrap() {
|
copy_tree(&pre, &case);
|
||||||
let e = e.unwrap();
|
|
||||||
if e.file_type().unwrap().is_file() {
|
|
||||||
std::fs::copy(e.path(), case.join(e.file_name())).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut torn = before_bytes.clone();
|
let mut torn = before_bytes.clone();
|
||||||
torn.extend_from_slice(&last[..cut]);
|
torn.extend_from_slice(&last[..cut]);
|
||||||
std::fs::write(case.join(ledger_store::JOURNAL_FILE), &torn).unwrap();
|
std::fs::write(case.join(ledger_store::JOURNAL_FILE), &torn).unwrap();
|
||||||
@@ -326,9 +331,12 @@ fn blob_dedup_and_hash_verification() {
|
|||||||
assert!(!dedup1);
|
assert!(!dedup1);
|
||||||
assert!(dedup2);
|
assert!(dedup2);
|
||||||
assert_eq!(sha1, sha2);
|
assert_eq!(sha1, sha2);
|
||||||
// Only one blob row; two attachment rows.
|
// One blob row and one attachment row: the same bytes filed twice are
|
||||||
|
// one record (plan P6 step 4), under the first filing's name.
|
||||||
assert_eq!(store.verify_blobs().unwrap(), 1);
|
assert_eq!(store.verify_blobs().unwrap(), 1);
|
||||||
assert_eq!(store.list_attachments(None).unwrap().len(), 2);
|
let records = store.list_attachments(None).unwrap();
|
||||||
|
assert_eq!(records.len(), 1);
|
||||||
|
assert_eq!(records[0].file_name, "w2.pdf");
|
||||||
|
|
||||||
// Corrupt the blob file on disk → the next read detects it.
|
// Corrupt the blob file on disk → the next read detects it.
|
||||||
let shard = dir
|
let shard = dir
|
||||||
@@ -1691,7 +1699,8 @@ mod refusals {
|
|||||||
// The converse law: an accepted command is on the record, or
|
// The converse law: an accepted command is on the record, or
|
||||||
// changed nothing at all (a batch run with nothing due; a
|
// changed nothing at all (a batch run with nothing due; a
|
||||||
// setting changed to the value it has; a 1099 decision, W-9
|
// setting changed to the value it has; a 1099 decision, W-9
|
||||||
// flag or payment method already so).
|
// flag or payment method already so; bytes already on file,
|
||||||
|
// which are the record they were, plan P6 step 4).
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
let after = std::fs::read_to_string(&journal).unwrap();
|
let after = std::fs::read_to_string(&journal).unwrap();
|
||||||
if after.len() == before.len() {
|
if after.len() == before.len() {
|
||||||
@@ -1711,6 +1720,8 @@ mod refusals {
|
|||||||
| Op::SetVendor1099(..)
|
| Op::SetVendor1099(..)
|
||||||
| Op::SetW9Flag(..)
|
| Op::SetW9Flag(..)
|
||||||
| Op::SetEntryInstrument(..)
|
| Op::SetEntryInstrument(..)
|
||||||
|
| Op::AttachReceipt(..)
|
||||||
|
| Op::FileDocument(..)
|
||||||
),
|
),
|
||||||
"accepted {:?} but journaled nothing",
|
"accepted {:?} but journaled nothing",
|
||||||
op
|
op
|
||||||
|
|||||||
Reference in new issue
Block a user