merge: P6, the evidence pipeline (branch P6, checkpoint ce366408)

The last of the paused branches, from its "not for merge" checkpoint:
extraction in ledger-import (text, pages, page images; capped, caught),
journaled as attachment.text_extracted; dedup by hash with
books.find_attachment and books.consolidate_duplicates; annotations and
books.set_entry_settles; the company record categories; the two fuzz
targets; feature attachment_text. Nineteen files conflicted with the
four P4b merges, resolved as plan 8.0.2 says: P6 retakes schema 18 (it
had taken 16, which S4.3 holds; S5 holds 17); the tool table is 129 and
the destructive count 31; both sides' appends kept with their seams
restored; one B-27 paragraph naming every feature; in the old-books
fixture P6's hand-written reimbursement is entry 7 (its ids collided
with S4.3's invoice); the replay fuzzer's list gains S7's
invoice.written_off; S4.3's issued-PDF test expects the extraction
record P6 journals right after a filing.

One integration defect between S7 and P6, found by the arap property
test and fixed here: under P6's dedup, a write-off whose evidence bytes
were filed already (the same collection letter for a second write-off of
the same invoice) was refused only after its entry had been journaled,
at the final link to the invoice ("already linked"). The write-off now
resolves the dedup before anything is journaled and keeps a link the
record already has to the invoice instead of refusing it; an explicit
existing_attachment_id already on an entry is still refused (S7's
one-receipt-once rule). P6's gate never ran before the pause; it runs on
main after this merge, and its ledger-store -> ledger-import dependency
is the open review point (plan 8.0.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-28 23:08:59 -06:00
commit 2ad192bbcd
70 files changed
+4618 -384

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
//! The PDF extractor on arbitrary bytes (plan P6): reading text, a page's
//! pixels and the page count is a refusal or an answer, never a panic or a
//! hang (D-32: it runs in the server's process).
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = ledger_import::extract::pdf_text(data);
let _ = ledger_import::extract::pdf_lines(data);
if let Ok(pages) = ledger_import::extract::page_count(data) {
for page in [0, pages.saturating_sub(1), pages] {
let _ = ledger_import::extract::page_png(data, page, 64);
}
}
let _ = ledger_import::extract::text_of(data, "application/pdf", "receipt.pdf");
});
+15
View File
@@ -0,0 +1,15 @@
//! The XLSX extractor on arbitrary bytes (plan P6): a zip or not, a sheet
//! or not, it is a value or a refusal, never a panic; a cell's number is
//! read as the decimal string the XML holds.
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = ledger_import::extract::xlsx_text(data);
let _ = ledger_import::extract::text_of(
data,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"sheet.xlsx",
);
});
+5 -2
View File
@@ -24,7 +24,8 @@ use serde_json::{Map, Value, json};
const TYPES: &[&str] = &[
"account.added", "account.renamed", "account.tax_set", "asset.depreciation_posted",
"asset.disposed",
"asset.registered", "attachment.withdrawn", "bill.created", "bill.paid", "class.added",
"asset.registered", "attachment.annotated", "attachment.text_extracted", "attachment.withdrawn",
"bill.created", "bill.paid", "class.added",
"customer.added", "document.filed", "entry.created", "entry.line_added",
"entry.line_class_set", "entry.posted", "entry.reversed", "entry.vendor_set",
"fx.rate_set", "invoice.created", "invoice.payment_received", "invoice.sent",
@@ -41,7 +42,9 @@ const TYPES: &[&str] = &[
"entry.party_set", "party.added", "party.role_set", "party.tin_set", "party.updated",
"party.w4_set", "party.w9_set", "payroll.taxes_remitted", "reconciliation.completed",
"reconciliation.voided", "role.assigned", "statement.recorded", "statement.voided",
"project.set",
"project.set", "entry.settles",
// Merged 2026-10-28 (S7, P6): every type EVENT_TYPES names.
"invoice.written_off",
];
const FIELDS: &[&str] = &[