M18 — Legal case files
State: to do — Depends on: M07, M09, M12, M14, M15, M16 — The
AdCodicem.Pdf.CaseFilesatellite (ADR 9, ADR 24); every resource through the resolver of ADR 38; the inventory's page numbers by the passes of ADR 39
Goal
Build a case file from one model — numbered pieces, their titles, dates and sources — and get from it the exhibit stamps, the bookmarks, the page labels, a hyperlinked bordereau de communication de pièces, a JSON export and an integrity manifest, all of which agree because none of them is written by hand; shape the result for the court portal it is going to, by rules kept as data and read from the official texts; turn e-mails into pieces with their attachments; and link every "pièce n° 12" in the pleadings to the piece it names.
M06 and M07 assemble and split; M09 stamps "Pièce n° 2.1" on a page it is told to stamp. None of them knows what a case file is. French practice does: every piece a party relies on is numbered and communicated with a bordereau (Code de procédure civile, art. 132; a bordereau récapitulatif annexed to appeal conclusions, art. 954); before the administrative courts an inventaire détaillé accompanies the pieces (Code de justice administrative, art. R. 412-2), and in Télérecours each piece must be a file of its own or be identified by a bookmark designating it as the inventory does (art. R. 414-3) — on which the Conseil d'État ruled in SA Finamur (5 October 2018). Those texts are cited here as the survey reports them; each is read in its current wording before a preset encodes it. The failures this milestone exists to prevent are the ordinary ones: a bookmark reading "Pièce 12" while the inventory reads "Pièce 12 — Lettre de résiliation du 3 mars 2025"; a piece inserted after communication that renumbers the stamps and not the bordereau; an inventory whose page numbers are all off by one because the inventory itself grew a page; an e-mail printed from Outlook with its attachments gone; a file a portal refuses for its size on the evening of a deadline; a fingerprint nobody can recompute.
Scope
In:
- the
AdCodicem.Pdf.CaseFilesatellite, onAdCodicem.PdfandAdCodicem.Pdf.Html, asdocs/architecture.mdplaces it; - the case-file model: the matter, the communicating party and its counsel, successive communications, and pieces — hierarchical numbers with the bis, ter suffixes French practice inserts, titles, dates, sources (a PDF, an image, an HTML fragment, an e-mail), sub-pieces —, with a versioned JSON form and its published schema;
- one resolution of the model from which every product follows: M09's exhibit stamps and separator pages, M07's bookmarks, M06's page labels and named destinations, the bordereau rendered by M12, its links, the JSON export, the integrity manifest, the document metadata — and a verifier that checks each of them against the model by extraction;
- outputs: one volume, bordereau first; one file per piece and the bordereau as a file of its own; or both;
- the bordereau: HTML written by the satellite from the resolved model, rendered by M12, tagged by M13, its lines linked to the pieces; its page column filled by ADR 39's bounded passes where the bordereau's own length shifts what it lists;
- signed, certified, encrypted and damaged pieces handled by M06, M09 and M16's policies, the signed original kept verifiable;
- an integrity manifest: SHA-256 of every piece's source as received and of every file produced, embedded as an associated file, summarized in a custom XMP schema (M14), the volume's own digest in an appended update, and a sidecar;
- court-portal presets — Télérecours, e-Barreau and the RPVA, PLEX — as data: a fixed vocabulary of rules in code, their values in JSON with the source each was read from, each preset verified against the current official texts and guides before it is encoded, as M10 and M14 do for their specifications;
- e-mail to a piece: EML (RFC 5322 and MIME) and Outlook MSG (MS-OXMSG over MS-CFB), a header block and the body rendered through M12, inline images from the message itself, attachments as associated files or as sub-pieces, nested messages as sub-pieces, the original message kept as the piece's source;
- references to pieces — "pièce n° 12", "pièces nos 3 à 5", "(pièce 2.1)" — found by M15's search in the pleadings and linked (M11) to the piece's first page, in the volume or across files;
- the command-line tool's
casefileverb.
Out, explicitly:
- redacting and sanitizing pieces before they enter a case file — M19, which adds its steps to the piece pipeline defined here; until then a caller passes a source it has already processed;
- OCR of scanned pieces, which a portal may require to be searchable — M22; M18 reports each page without text (M15's page kinds) under the preset's rule;
- reducing a piece's size beyond splitting it — recompression and downsampling are M23's; until then a piece over a cap is split when the preset allows it and reported otherwise;
- converting pieces to PDF/A — M21, through the tool's
casefile --pdf-a, so that this satellite never depends onAdCodicem.Pdf.Conformance; checking a PDF/A claim beyond reading it — M20; a preset that requires PDF/A reports every piece whose claim is absent, and says it could not validate the others — once M20 exists, it asks the core'sIPdfConformanceCheckerwhen the caller has registered one, still without that dependency; - signing or sealing the volume, the manifest or a piece, and time-stamping — M26; the manifest is shaped so that a signature over the volume binds it. No claim is made about the copie fiable regime of décret n° 2016-1673: digests make a change detectable, they do not certify a process;
- Word and office documents as pieces — M31 for DOCX; the others are an open question of the roadmap;
- sending anything to a court or through the RPVA — never: the library produces files; it holds no credential, opens no connection and talks to no court system;
- mailbox formats (MBOX, PST, EML collections), S/MIME decryption, DKIM verification — not planned; the EA-PDF archival profile of the PDF Association (2025) — not planned here, though the rendition leaves room for it (see Traps);
- presets for other jurisdictions — the vocabulary accepts them as data; only the three French ones ship, verified;
- an authored portfolio as the output — not planned (M07 unpacks received ones).
Design
Where it lives
AdCodicem.Pdf.CaseFile
Model/ CaseFileModel, CasePiece, CaseDate, CaseMatter, CaseCommunication, CasePieceSource,
CaseFileJson and its schema
Build/ CaseFileBuilder, ResolvedCaseFile, the page-label and bookmark plans, the passes
Inventory/ CaseInventoryWriter (HTML), the default stylesheet, CaseFileLocale (fr-FR shipped)
Integrity/ CaseIntegrityManifest, the XMP schema, the volume digest
Portals/ CasePortalPreset, the rule vocabulary, the shipped presets as embedded JSON
Email/ the MIME reader, the CFB and MS-OXMSG reader, EmailMessage, EmailToPiece
References/ CasePieceReferenceFinder, CasePieceLinker, the reference patterns as data
Verify/ CaseFileVerifier
The satellite adds no dependency beyond the two packages it builds on. Its parsers — MIME, compound files, LZFu,
RTF de-encapsulation — are written here, bounded and fuzzed like the core's, unless an ADR in slice 9 finds
that a maintained library (MimeKit for MIME, MsgReader for MSG, both MIT) meets the same bar: Native AOT and
trimming without warnings, deterministic output, and every limit below enforceable from outside. Taking one
amends the package table of docs/architecture.md.
Every operation that reads or writes takes M03's IProgress<PdfProgress> then CancellationToken, with the stages
Analyzing, Rendering, Assembling, Verifying, counted in pieces; every result carries a report of M06's
shape.
The model
CaseFileModel immutable: Matter, Party, Counsel, Communications (ordered), Pieces (ordered, a tree),
Numbering, Locale, Metadata overrides; With… methods for edits
CaseMatter court, chamber, case numbers, the parties as the caller names them — ordered label/value lines,
shown by the bordereau's header as written
CaseCommunication number (1, 2, …), date (supplied, never the clock), whether it is a récapitulatif
CasePiece Number (M09's PdfExhibitNumber, below), Title (the libellé), Date, Source, Communication,
Children, Notes, Tags
CaseDate Day, Month (a month and a year), Period, or Text as written; formatted by the locale
CasePieceSource Pdf(path | stream factory | document, credentials), Image(path | stream) → M07,
Html(HtmlSource) → M12, Email(eml | msg) → below, Part(an M06 assembly source)
CaseFileJson Read(Utf8JsonReader) and Write(Utf8JsonWriter); a versioned schema published on the site
- Numbers are the parties' contract. Once a communication is marked sent, the numbers of its pieces are frozen:
a model that renumbers them is refused, typed (
CaseFileException, naming the piece and the communication), since the court and the other side cite them. A piece inserted later takes the next number or a suffix (InsertPolicy.Suffixgives "12 bis"), as the caller chooses; a récapitulatif lists every piece of every communication. - Numbering is
Explicit(the model's numbers, checked unique and ordered),SequentialorHierarchical(sub-pieces numbered n.1, n.2), continued across communications. - One number model. A piece's number is M09's
PdfExhibitNumber— the value M09's stamps print through their{piece}field and M18's reference finder parses — with its components (2, 2.1, 2.1.3), an optional ordinal suffix (bis, ter, quater … up to the Latin ordinals it lists as data) and a total order, 12 < 12.1 < 12.2 < 12 bis < 12 ter < 13, an inserted piece following its neighbor's sub-pieces;Parseis total. The suffix and its order belong to the core type rather than to a second model here, since a caller stamping with M09 alone numbers exhibits as a case file does, and two models of one number would disagree on its order: M09's specification carries them, and if M09 closed without them, slice 1 adds them toPdfExhibitNumberin the core. - The JSON form is the model and nothing else — no paths resolved, no digests —, so that a caller can keep it
under version control; the resolved form is the export (below). Reading it is hostile input:
Utf8JsonReaderat its default depth, strings bounded by the stream, unknown properties refused with their path. - Locale data is ours. The strings of the bordereau and the stamps — "Bordereau de communication de pièces",
"Pièce n°", "Désignation", month and day names — are data in
CaseFileLocale, notCultureInfolookups: underInvariantGlobalization, which containers and AOT deployments commonly set,CultureInfo("fr-FR")gives no French month names, and ICU's data differs between distributions (the trap M15 records for folding). fr-FR ships; other locales are data.
One resolution, every product
CaseFileBuilder.Analyze(model, options) opens each source in turn — one open at a time — and produces a
ResolvedCaseFile: every piece with its page count, its page range in the volume, its labels, its bookmark title,
its file name, its source digest and what its source carries (signatures, encryption, text or not, active content,
from M04, M16 and M15's inventory). Write(resolved, output) then produces everything from that one object:
| Product | From the resolved model | Written through |
|---|---|---|
| Exhibit stamp and per-piece page numbers | Number, the stamp template, the piece's page range | M09's PdfExhibitStamper, pieces as ranges of the volume or as documents |
| Separator page | Number and title | M09's PdfSeparatorPage; labeled with the piece's prefix alone, so its pages start at "-1" |
| Bookmark | The title pattern (the preset's or the default "Pièce n° {number} — {title}"); sub-pieces nested; the piece's own outline nested beneath it (M06's Nest) or dropped | M07's PdfOutline |
| Named destination | piece-{number}, one per piece, at the top of its first page | M06's name tree |
| Page labels | The plan: bordereau in lower roman, each piece {number}- from 1 (default), continuous, or Bates-mirrored (M09) | M06's PdfPageLabels |
| Bordereau | Every line, and the numbers its page column shows | M12, from HTML the satellite writes |
| Links from the bordereau | Each line to piece-{number} in a volume, or a GoToR to the piece's file and its named destination | M12.6 and M07's open parameters |
| Metadata and viewer settings | Title from the locale and the matter, language, the dates the caller supplies; UseOutlines, DisplayDocTitle | M14's PdfDocumentMetadata, M06's viewer preferences |
| Integrity manifest | Every digest | M06's attachments, M14's XMP |
| JSON export | The resolved model | CaseFileJson.WriteResolved |
| Export anchors | Piece numbers per page | M15's PdfAnchorProvider, so that chunks of a volume cite "pièce 2.1, p. 3" |
No product reads another product: the bookmark title is not taken from the inventory's text, nor the stamp from the bookmark. That is what "cannot disagree" means in code; the verifier (below) is what proves it on the output.
Outputs
- A volume: M06's assembly, bordereau first (its own part,
RenderToPart), then each piece, optionally preceded by its separator. The whole is one forward-only write; memory follows the heaviest page plus the resolved model, a few hundred bytes a piece. - One file per piece: each piece its own assembly — stamps, its bookmark, its labels, the manifest entry for its
source —, named by the preset's pattern; sub-pieces in their parent's file or in files of their own, as the preset
or the options say. The files are written first, through a hashing sink, so that the bordereau, written last,
can list each file's SHA-256 and link to each by
GoToR. A file over the preset's size rule is cut by M07's split by size, when the preset allows a piece in several files, and named "{number} ({k}/{n})". - Both, from one analysis.
- File names follow M07's safe naming (reserved characters removed, NFC, bounded length) and then the preset's pattern, character set and length; accented letters transliterated to ASCII only when the preset's character set demands it, through M15's generated decomposition tables, never through the platform's culture.
The bordereau
- HTML written by the satellite, never concatenated from strings:
CaseInventoryWriterescapes every value of the model, so that a title containing</td>or<img src=…>is text. The default stylesheet — A4, the matter in a running header, "page X sur Y" in the footer through M12's late-filled counters — is replaceable, and so are a header and a footer fragment (a firm's letterhead, a signature block), loaded through the resolver of ADR 38 from the bases the caller declares. - Columns: number, title, date, and optionally the pages, the communication, the source's or the file's SHA-256, and "original signé joint" where a signed original travels with the piece. Sub-pieces are indented.
- Tagged by M13 (
Table,THwithScope,H1,Lang fr), PDF/UA-1 as its target: with the e-mail renditions, the bordereau is what the library itself writes into a case file, and it is accessible. Whether the volume keeps a claim is M06's structure policy, which removes it as soon as one piece is untagged, and says so. - Links:
href="#piece-2.1"names a destination the enclosing assembly defines, not an id in the HTML. M12.6 resolves#idagainst the document it lays out; M18 addsPdfRenderOptions.ExternalDestinationstoAdCodicem.Pdf.Html— the member M12 reserves for this milestone —, the destinations the caller declares will exist, each resolved by the enclosing assembly and reported by M06 if it never appears. In per-piece output the links areGoToRto the file with#nameddest=piece-2.1, through M07's open parameters.
The bordereau's page column: two bounded passes
What the page column shows decides whether the bordereau depends on its own length:
| Page plan | The column shows | Depends on the bordereau's length |
|---|---|---|
| Bordereau in roman, each piece labeled from its own start (default) | Nothing by default — every piece starts at "-1", and the link is the navigation | No: laid out once |
| Bordereau in roman, pieces numbered continuously from 1 | The label of each piece's first page | No: laid out once |
| Continuous numbering including the bordereau, or no labels (the viewer's physical page) | The physical page | Yes |
| Bates-mirrored labels starting on the bordereau | The Bates number | Yes |
In the last two rows the builder runs ADR 39's strategy itself, since the pieces are PDF parts M12 does not lay out
and target-counter() cannot reach them: a first render lays the bordereau out with every number replaced by a
placeholder of the reserved width (ReservedCounterDigits of the widest digit, tabular-nums), writes nothing and
keeps only its page count; the builder then knows every piece's first page and renders again, writing. A reserved
width that holds cannot change the wrapping of a line, so the page count holds; if it moves anyway — a number wider
than the reserve —, the builder renders again, up to M12's MaxLayoutPasses, then writes the last pass and reports
casefile.inventory-unsettled with the pieces whose numbers may be wrong. A measure-only render is the map-only
pass M12's orchestrator already runs on a non-seekable output; M12 exposes no call for it, so M18 adds one to .Html
beside ExternalDestinations — IPdfRenderer.MeasureAsync(HtmlSource, …), returning the page count and the page of
each id, writing nothing — with its tests in M12's suites.
Signed, certified, encrypted and damaged pieces
| Piece | In a volume | In its own file |
|---|---|---|
| Approval-signed | M06's AttachOriginal, the default here: the stamped copy in the volume, the signed original as a page-level associated file (Source) on its first page, its digest in the manifest, "original signé joint" in the bordereau | StampAfterSignature, the default here: M09's incremental stamp, which leaves every signature covering its revision and reports stamp.after-signature; or the original passed through byte for byte, unstamped, when the caller or the preset prefers |
Certified (DocMDP) | M06's Refuse, by default; Insist treats it as signed and reports the certification voided in the copy — the original travels intact beside it | Refused by M09; passed through unstamped when the caller asks |
| Encrypted | Opened with the source's PdfCredentials (M16); Assemble demanded under PdfPermissionPolicy.Respect, as M16's table says; the output unencrypted unless the options encrypt it | Likewise |
| Damaged | Read by the tolerant reader, optionally repaired first (M05); every loss M01 and M05 report is carried into the piece's report, never silent | Likewise |
| Without text | Assembled; reported per page under the preset's text rule, if it has one | Likewise |
| With active content | Assembled as it is (M06 keeps scripts); reported under the preset's active-content rule; removed by M19 once it exists | Likewise |
The integrity manifest
- What is hashed. Each piece's source as received — the bytes of the PDF, image or message file, streamed
through
SHA256—; each file produced in per-piece output; the volume. Sub-pieces extracted from an e-mail are hashed as the bytes the message carried, after transfer decoding. - The manifest is a JSON document — algorithm, matter reference, generator name and version, each piece's number,
title, source name, media type, length and digest, its page range, its output file's name and digest where there
is one — serialized deterministically (fixed property order, invariant numbers, no insignificant whitespace), under
a published schema. It is embedded in the volume as a document-level associated file,
AFRelationship /Supplement,application/json, and written beside it as a sidecar. - XMP: a custom schema of M14's — a namespace URI under the project's control, fixed before the first stable
release, prefix
adcCase— carries the matter reference, the communication, the piece count and the manifest's own digest, with its extension schema description generated for PDF/A. - The volume's digest cannot sit inside the bytes it digests. It is written in an appended update: revision 1
is the volume, written through a hashing sink; revision 2 changes only the metadata stream, adding
adcCase:volumeDigest, the SHA-256 of revision 1's bytes — the file up to the end M04's revision history gives revision 1. Anyone can recompute it with M04'sPdfRevision.CopyTo, pyHanko's revision extraction orhead -c. Forward-only, no placeholder patched; the update needs the writer's index of revision 1, which it still holds, and an output it can append to. A non-seekable output, a preset that forbids incremental updates, or the caller selectsVolumeDigest.SidecarOnly, and the report says where the digest went. A later signature (M26) covers both revisions. - Recomputation is
CaseFileVerifier's: a piece's digest that no longer matches its source names the piece.
Court-portal presets, as data
A preset is a JSON document shipped as an embedded resource:
CasePortalPreset id, name, jurisdiction, version; Sources: [{ url, title, clause, retrieved, verifiedOn }];
Rules: [{ rule, value, source }]
CasePortalRule one class per rule of the vocabulary: Check(resolved, outputs) -> findings; Shape(plan)
| Rule | Checks | Shapes by |
|---|---|---|
portal.layout | One file per piece, one file with a bookmark per piece, or either | Choosing the output |
portal.bookmark-title | Each piece's bookmark title against the pattern, and its length | Writing it from the model |
portal.file-name | Each file name against the pattern, character set and length | Naming |
portal.file-size | Each file's length | M07's split by size, when split-allowed; otherwise a finding |
portal.total-size, portal.file-count | The submission's sums | A finding — dividing a filing into several sendings is the caller's decision |
portal.inventory | The bordereau present, where the preset wants it — first in the volume, or a file of its own | Writing it |
portal.text | Every page of every piece has text (M15's page kinds) | A finding naming pieces and pages — OCR is M22's |
portal.encryption | No piece or file encrypted | Writing unencrypted; an encrypted piece whose permissions forbid it is a finding |
portal.active-content | No script, launch or submit action (M15's inventory) | A finding — removal is M19's |
portal.pdf-version | The header version | M03's output version |
portal.pdfa | A PDF/A claim on every file | A finding — validation is M20's, conversion M21's |
portal.page-size | The page boxes | M09's fit to A4, when the preset allows it |
portal.stamp | The exhibit stamp on each piece's first page | M09 |
- Values are sourced or absent. Every value cites one of the preset's sources;
CasePortalPresetTestsfails on a value without a source, a source withoutverifiedOn, or a rule the vocabulary does not know. No number is written in this document — no size cap, no file count: each is read from the text or guide in force when the preset is encoded, recorded with the preset, and cited in the commit that encodes it. - Shipped presets, each verified before it is encoded:
telerecours— the administrative courts' application: the Code de justice administrative (R. 412-2 on the inventory, R. 414-1 and following on Télérecours, R. 414-3 on how pieces are presented), SA Finamur on what a bookmark must carry, and the application's own guide and FAQ for what the texts leave to it;rpva— electronic communication before the judicial courts through e-Barreau and the RPVA: the Code de procédure civile (art. 748-1 and following) and the technical orders in force, and the platform's guide;plex— the ministry of justice's platform for exchanging files too large for the RPVA: its conditions of use and guide.
- Re-verification is part of the release procedure (
docs/releasing.md): a stable release re-reads every preset's sources and records the date. No test reads the clock. - The vocabulary's own tests use synthetic presets with arbitrary values; shipped presets are tested by what they encode, not by numbers invented for the test.
E-mail to a piece
EmailMessage parsed: headers as written and decoded (From, Sender, To, Cc, Bcc when present, Reply-To,
Date, Subject, Message-ID, In-Reply-To), bodies (text, HTML, related parts), attachments
(name, media type, disposition, content id, length), nested messages; the source's digest
EmailReader ReadEml(stream, EmailLimits), ReadMsg(stream, EmailLimits) -> EmailMessage + report
EmailToPiece Render(message, EmailRenderOptions) -> a CasePieceSource (an M06 part) and its sub-pieces
EmailRenderOptions header fields shown and their order; date display (as written, or at a fixed offset the
caller gives); body preference (Html, Text); attachments (AssociatedFiles, SubPieces, Both,
ListOnly); nested messages (SubPieces); attachment converters (a caller's map from a media
type to a function returning a CasePieceSource); stylesheet; fonts; conformance target (M14's
PDF/A-3 when attachments are embedded); limits
- EML — RFC 5322 headers, unfolded; RFC 2047 encoded words (B and Q) in any charset the framework's
CodePagesEncodingProviderknows, an unknown one reported and read as UTF-8 then Latin-1; RFC 2231 parameter continuations and charsets for file names; base64 tolerant of line breaks and junk, quoted-printable tolerant of bad escapes; multipart boundaries with a missing final boundary read to the end, reported;format=flowed(RFC 3676);message/rfc822nested;multipart/signedrendered with its signature part kept as an attachment and reported unverified;application/pkcs7-mimeand PGP-encrypted bodies reported unreadable;winmail.dat(TNEF) kept as an attachment and reported. - The rendition is an HTML document the satellite writes and M12 renders: a header block — a labeled table,
visibly framed, escaped, with the attachments listed by name, size and digest and where each went ("pièce 12.1",
"fichier joint") —, then the body. An HTML body keeps its own styles, placed in a cascade layer below the
header's (M12.1), so that a message cannot hide or restyle the header block;
cid:references resolve from the message's own parts through a resolver over the message;data:is allowed; every remote reference is refused by ADR 38's default — a tracking pixel is never fetched, and each refusal is in the report. A text body is escaped into apre-wrapblock, flowed lines joined. Conditional comments, VML andmso-properties degrade as M12 degrades anything it does not support. - Attachments: as associated files on the piece's first page (
AFRelationshipUnspecifiedby default, configurable), as sub-pieces — PDFs appended as 12.1, 12.2, images through M07, nested messages rendered in turn —, or both. What cannot become pages (a spreadsheet, an archive) is attached and listed, never opened — unless the caller's attachment converters name its media type: M31's DOCX conversion is the case in view, so that a Word attachment becomes a sub-piece while this satellite never referencesAdCodicem.Pdf.Docx. The original message is attached at document level withAFRelationship /Source; its digest is the piece's source digest. - Dates are shown as written, with their offset; a normalized display uses the offset in the message or one the caller supplies, never the machine's zone (invariant 6). MSG carries times in UTC only (below): shown in UTC unless the caller gives an offset.
MSG
- MS-CFB: header, FAT and DIFAT chains, the mini stream, the directory's red-black tree walked iteratively with a visited set; a chain longer than the file's sector count is a cycle, cut and reported.
- MS-OXMSG: the property streams (
__substg1.0_XXXXYYYY,__properties_version1.0), recipients (type, display name, SMTP address — an Exchange X.500 address shown as written when no SMTP address exists), attachments (PidTagAttachDataBinary, embedded messages as sub-storages),PidTagInternetCodepagefor 8-bit bodies. - Bodies:
PidTagHtmlwhen present; otherwisePidTagRtfCompresseddecompressed (MS-OXRTFCP's LZFu, its CRC checked, its declared size bounded) and, when it encapsulates HTML (\fromhtml1), de-encapsulated as MS-OXRTFEX describes; otherwisePidTagBody; a native RTF body with no plain-text twin is rendered as its text, reported (email.rtf-body-as-text). - The roadmap calls MSG optional; it is specified here in a slice of its own, behind the same
EmailMessage, so that it can be taken or deferred without touching the rest. M18 may close without it: the MSG half of the e-mail row and its exit criterion then move to a debt issue, naming what brings it back — a caller whose pieces arrive as Outlook messages.
References to pieces
CasePieceReferenceFinder Find(document, resolved, CaseReferencePatterns) -> references: text, quads, numbers, page
CasePieceLinker Link(document, references, target: volume | per-piece files) -> M11 link annotations
CaseReferencePatterns data: French by default — "pièce n° 12", "pièce no 12", "Pièce N° 12", "nº", "pièces nos
3, 4 et 7", "pièces n° 3 à 5", "(pièce 2.1)", "pièce 12 bis" — and exclusions ("pièce
adverse n° 4", "pièce n° 4 adverse", "pièces justificatives")
- Runs on M15's search — regular expressions without backtracking, folding the spaces of U+00A0 and U+202F, across line breaks and hyphens —, so that "pièce n°⏎12" is found with a quad per line fragment.
- A single reference is linked on its whole phrase; an enumeration or a range links each number, the prefix linking
to the first. A number the model does not hold is reported (
casefile.reference-unknown), never linked; an excluded context is reported as seen and left alone. - Never linked: text inside our own exhibit stamps and Bates numbers — M09's marks are
Paginationartifacts, which M15 marks as furniture —, the bordereau's own lines, text already under a link, text hidden by optional content. - Targets: in a volume, a
GoTotopiece-{number}; with per-piece files, aGoToRto the file with its named destination (M07). In a tagged document each link gets itsLinkelement and object reference, as M11 does.
Checking a case file
CaseFileVerifier.Verify(output, resolved | embedded manifest, preset?) reads what was produced and compares it with
the model, by extraction rather than by trusting the builder: the stamp text on each piece's first page and the
per-piece page numbers (M15's search), each bookmark's title and target (M07's outline), each label (M06), each line of
the bordereau and the page its link reaches (M15's text in structure order, M11's links), each digest recomputed,
each preset rule. The same verifier checks a volume the caller received, against a model the caller supplies. Its
findings are casefile.* codes; the casefile check verb prints them.
Bounds (ADR 34, applied to the satellite)
| Bound | Kind | Why |
|---|---|---|
| MIME nesting depth, parts per message, decoded part length, unfolded header length, nested messages | Guards, EmailLimits, on by default, each reached reported as email.limit-* naming its property; ThrowOnLimit as the reader has it | A valid message can nest forwarded chains deeply, carry hundreds of parts or a header of thousands of recipients; defaults set from the corpus messages with a margin and recorded |
| An LZFu body's declared size; a CFB stream's declared size | Checked against EmailLimits.MaxDecodedPartLength before any allocation | Sizes come from the file |
| A CFB sector chain longer than the file's sector count | Internal: a cycle, cut | No valid file has one |
| RTF group nesting | No bound: an explicit stack, memory proportional to the input | Walked iteratively |
| Bordereau layout passes | M12's MaxLayoutPasses | ADR 39 |
| Reference patterns' match time | None needed: NonBacktracking | M15 |
| Pieces in a model | None: the model is the caller's, a few hundred bytes a piece | Not read from a hostile file |
Report codes
casefile.*, portal.* (the rules above) and email.*, public and stable from the first stable release, disjoint
from reader diagnostics, validation rules and every other report family:
| Code | Severity | Meaning |
|---|---|---|
casefile.numbering-frozen | Error (refusal) | The model renumbers a piece of a communication already sent |
casefile.signed-original-attached | Information | A signed piece travels as a stamped copy with its original attached |
casefile.piece-without-text | Warning | A piece has pages without text (the page kinds of M15) |
casefile.piece-split | Information | A piece was cut into several files by the size rule |
casefile.inventory-unsettled | Warning | The bordereau's passes ran out; the pieces named may be listed a page off |
casefile.reference-unknown | Warning | A reference to a number the model does not hold |
casefile.reference-excluded | Information | A reference in an excluded context, left unlinked |
casefile.stamp-mismatch, casefile.bookmark-mismatch, casefile.label-mismatch, casefile.inventory-mismatch, casefile.link-unresolved | Error | The verifier found a product that disagrees with the model |
casefile.digest-mismatch | Error | A recomputed digest differs from the manifest's |
email.charset-unknown, email.boundary-missing, email.encoding-damaged | Warning | Read through, as described |
email.resource-refused | Information | A remote reference in a body, not fetched (ADR 38) |
email.signed-unverified, email.encrypted-body, email.tnef-kept | Warning | Kept as attachments; the content could not be verified or read |
email.rtf-body-as-text | Warning | An MSG body available only as native RTF, rendered as text |
email.limit-* | Warning | A guard reached; the message names the EmailLimits property |
The command-line tool
casefile build MODEL.json -o OUT.pdf | --per-piece DIR [--preset ID] [--manifest SIDECAR.json] [--volume-digest update|sidecar]
casefile check (VOLUME.pdf | DIR) [--model MODEL.json] [--preset ID]
casefile inventory MODEL.json -o BORDEREAU.pdf
casefile email IN.eml|IN.msg -o OUT.pdf [--attachments embed|pieces|both|list]
casefile link PLEADINGS.pdf --model MODEL.json -o OUT.pdf [--volume | --per-piece DIR]
casefile presets [--json] each preset, its rules, its sources and when they were verified
Every sub-verb takes --json and M06's exit codes; a refusal by policy — frozen numbering, a certified piece, a
permission — is exit code 4, a verifier finding at error severity exit code 1.
Slices
Each slice ends on a green commit, with the codes it introduces documented and its benchmark, if it has one,
recorded in docs/status.md.
- The model, its JSON and the analysis. Delivers
CaseFileModeland its parts,PdfExhibitNumber's suffixes if M09 has not, numbering and frozen communications,CaseFileJsonand its schema,CaseFileBuilder.Analyzewith streamed source digests and each source's facts from M04, M15 and M16. Proved by unit tests (number parsing and ordering, 12 bis after 12's sub-pieces and before 13, a renumbering refused, every JSON error at its path); FsCheck — any model round-trips through JSON unchanged, and numbering is a total order —; integration: the schema validated by Python'sjsonschema; the reference case file analyzed, its page counts equal the manifest's and its digestssha256sum's. Leaves output. - The volume without its bordereau. Delivers separators, stamps, bookmarks, labels, named destinations, viewer
settings and metadata from the resolved model, through M06's assembly; the signed, certified, encrypted and
damaged rows. Proved by integration:
pdftotextfinds each stamp on its piece's first page and "p. k/n" on every page;qpdf --jsonlists the outline and the labels as the model says; pdf.js resolves each bookmark and label; the signed original, extracted withpdfdetach, is byte-identical to the source and pyHanko finds its signature covering its revision. Leaves the bordereau. - The bordereau. Delivers
CaseInventoryWriter, the fr-FR locale, the default stylesheet, the header and footer fragments, the links throughExternalDestinations, tagging, and the page column with the bounded passes overMeasureAsync— the two members added to.Html. Proved by unit tests (escaping every value, every column, the locale without ICU); FsCheck — for random piece counts, page counts and title lengths, under each page plan, every page number the bordereau prints is the page its link reaches, within the pass bound —; integration: M15's tables and pdfplumber read the bordereau's lines as the model's; PyMuPDF'sget_linksand pdf.js land each line on its piece's first page; veraPDF passes the bordereau alone under PDF/UA-1. Leaves per-piece files. - Files per piece. Delivers per-piece output, naming, the size rule's split,
GoToRlinks with named destinations, the digests listed in the bordereau,StampAfterSignaturefor signed pieces. Proved by integration: every file named as the pattern says; eachGoToRresolved by pdf.js in a directory of the files; each listed digest equal tosha256sum's; pyHanko on each signed piece's file finds every signature intact and the stamp a later change. Leaves integrity. - The integrity manifest. Delivers the manifest and its schema, the associated file, the XMP schema with its
PDF/A description, the appended update carrying the volume's digest, the sidecar, recomputation in the verifier.
Proved by integration:
pdfdetachextracts the manifest,jsonschemavalidates it; ExifTool reads the XMP properties; an independent Python script — no code of ours — finds revision 1's end and recomputes every digest; qpdf and pikepdf list two revisions, the second changing only the metadata stream. Leaves presets. - The preset framework. Delivers
CasePortalPreset, the rule vocabulary with checks and shaping, the presets' schema with mandatory sources,casefile presets. Proved by unit tests per rule with synthetic presets — a case file that satisfies it, one that violates it, one the shaping repairs —;CasePortalPresetTestsover every shipped file. Leaves the French presets. - Télérecours. Delivers the
telerecourspreset, read from the Code de justice administrative in force, the decision cited above and the application's current guide, sources and dates recorded in the file and in the commit. Proved by the per-rule tests on the reference case file shaped by it, and the verifier's check. Leaves the judicial presets. - e-Barreau, the RPVA and PLEX. Delivers
rpvaandplex, verified the same way. Proved by the same tests. Leaves e-mail. - EML. Delivers the MIME reader,
EmailMessage,EmailToPiece, attachments and nested messages as sub-pieces,EmailLimits, the ADR on writing the parser or taking MimeKit. Proved by unit tests over every construct above, hostile messages (a MIME bomb, a boundary that never ends, a header of a megabyte, a charset that lies); FsCheck — any message Python'semailpackage writes is read to the same headers, parts and bytes —; integration: Python'semailpackage as the oracle on the corpus messages;pdftotextfinds every header and the body's text;pdfdetachgives each attachment byte for byte; the resolver's log shows no request. Adds the messages the corpus lacks (below), described through M07'semlandmsgformats and their block, under amessageuse case this slice adds to the schema's enum. Leaves MSG. - MSG. Delivers the CFB and MS-OXMSG readers, LZFu, HTML de-encapsulation,
email.rtf-body-as-text. Proved by unit tests (a FAT cycle, a mini stream past its end, a CRC that fails, a declared size of 4 GB); integration:olefileandextract_msg, in a container, as oracles for streams, properties, bodies and attachments; the same rendition checks as EML. Leaves references. - References to pieces. Delivers
CaseReferencePatterns(French), the finder and the linker, tagged links. Proved by unit tests over every pattern and exclusion, across a line break and a hyphen; FsCheck — any phrase the patterns generate, placed by M08's builder, is found with quads covering exactly its glyphs —; integration: on the pleadings the corpus lacks (below), PyMuPDF'sget_linksand pdf.js land each reference on its piece; no link on the stamped volume's own stamps nor on the bordereau. Leaves the tool. - The verifier, the tool, budgets. Delivers
CaseFileVerifieras a public API, thecasefileverb,CaseFileBenchmarkswithMemoryDiagnoser, the documentation, the remote run. Proved byCorpusToolTests, the tampered-copy rows, the budget rows and a greenRemote corpusrun recorded indocs/status.md.
Tests required
Unit — under CaseFile/, in tests/AdCodicem.Pdf.Tests as M10 placed its satellite's, or in the test project
M12.1's first slice gives the satellites that carry .Html's native assets, if it gives them one:
- The model: numbering in every mode; suffixes and their order; a frozen communication; a récapitulatif; sub-pieces three deep; JSON round trip; every schema error; a title of every Unicode plane.
- Resolution: the page ranges, labels and bookmark titles for every page plan; a piece of zero pages (refused, typed); a separator's label; the same source given twice (one digest, two pieces, reported).
- The bordereau: escaping — a title that closes the table, opens a script, carries a
url()—; every column; the passes under each plan, including a plan that cannot settle (a reserve of one digit and a thousand pages). - The manifest: deterministic serialization; revision 1's digest; the sidecar; recomputation after a byte is flipped in a piece, in the manifest and in revision 1.
- Presets: each rule's check and shaping; a preset without a source refused; a value of the wrong type refused.
- E-mail: each RFC construct; hostile messages at every guard, raised and thrown; CFB and MS-OXMSG faults; a body in each form MSG has; dates with and without offsets.
- References: each pattern, each exclusion, numbers the model lacks, our own stamps ignored.
- Determinism: FsCheck over random models built from corpus documents — two builds, identical bytes, volume and files alike, the manifest included.
Integration — tests/AdCodicem.Pdf.IntegrationTests, every referee in a container
(ADR 27):
- poppler —
pdftotextfor stamps, bordereau lines and e-mail renditions;pdfdetachfor the manifest, signed originals and e-mail attachments;pdfinfofor page counts; - qpdf —
--checkon every output;--jsonfor the outline, labels and revisions; - pikepdf — associated files and their relationships, the XMP packet, the structure tree of the bordereau;
- PyMuPDF — links and their targets, the table of contents, words and their quads for references;
- pdf.js, pinned under Node — labels, bookmarks and
GoToRlinks as a viewer resolves them; - MuPDF —
mutool drawfor the e-mail renditions' inline images and the bordereau against its reference image; - veraPDF — PDF/UA-1 on the bordereau; the preserved claims of M06 on volumes whose pieces all claim;
- pyHanko — signed originals and signed pieces stamped after their signatures;
- ExifTool — the
adcCaseXMP properties; - Python —
jsonschemafor the model, the export and the manifest;emailas the EML oracle;olefileandextract_msgas the MSG oracles; an independent digest script; - pdfplumber — the bordereau's table.
Acceptance conditions
"The reference case file" is the model kept in the test support: 1 documents/contract/chromium-contract-fr.pdf,
2 vendor/uk-ogl/word2019-ccs-contract-schedule.pdf, 2.1 vendor/us-federal/xerox-workcentre-5335-ocr-hud-fonsi-linearized.pdf,
2.2 documents/invoice/libreoffice-invoice-fr.pdf, 3 vendor/pdf-association/indesign13-pdfua1-german-book-chapter.pdf,
4 vendor/opf-format-corpus/pdfmaker7-powerpoint-va-cancer-database-course.pdf — M09's exhibit set —, then
5 vendor/fr-licence-ouverte/fop-dictao-dila-signed-joafe-notice.pdf (approval-signed, its own page labels),
6 vendor/fr-licence-ouverte/pdfmaker-acrobat-cerfa-12156-form.pdf (its own outline, fields and scripts),
7 vendor/us-federal/acrobat3-import-irs-1040-1988-scan.pdf (a CCITT scan with no text, two page sizes),
7 bis vendor/opf-format-corpus/openoffice32-writer-rc4-128-open-password.pdf (encrypted, its recorded password) and
8 documents/form/reportlab-subscription-form.pdf (fields): eleven pieces, 99 pages.
| Documents | Behavior | Verified by |
|---|---|---|
| The reference case file, as one volume | Every piece's first page carries "Pièce n° {number}" and the firm, every page "p. k/n" with n its piece's count, as pdftotext reads them; one bookmark per piece titled "Pièce n° {number} — {title}", sub-pieces nested, the Cerfa's and the PowerPoint's own outlines nested beneath their pieces, as qpdf --json and PyMuPDF's table of contents list them; labels "i", "ii" on the bordereau and "{number}-1…" on each piece, as pdf.js and qpdf read them; the bordereau's lines in model order with 7 bis after 7, each link landing on its piece's first page in pdf.js; the PowerPoint's and the DILA notice's own page labels replaced and reported | CorpusCaseFileTests.Stamps_bookmarks_labels_and_bordereau_agree_with_the_model (new), PdfJsCaseFileRefereeTests (new) |
| The same volume, through the verifier | No finding; and on copies altered in the test — a bookmark retitled, a label range shifted, a stamp removed by M09, a piece's page deleted — exactly the finding for the alteration, naming the piece | CorpusCaseFileTests.The_verifier_finds_every_disagreement (new) |
The signed notice (piece 5) in the volume; vendor/us-federal/itext-govinfo-us-code-certified.pdf added as a certified piece | The stamped copy in the volume, the original attached to its first page with AFRelationship /Source, byte-identical to the source under pdfdetach, and pyHanko finding its signature covering its revision; the certified piece refused, typed, unless the caller insists, then attached alike and assembly.certification-voided reported | CorpusCaseFileTests.Signed_pieces_travel_with_their_signed_original (new), PyHankoCaseFileRefereeTests (new) |
| The reference case file, one file per piece | Eleven files and a bordereau named by the default pattern; each GoToR in the bordereau opening its file at piece-{number} in pdf.js; each SHA-256 the bordereau prints equal to sha256sum's; the signed notice's file stamped after its signature, pyHanko reporting the signature intact and the stamp a later change | CorpusCaseFileTests.Files_per_piece_are_named_linked_and_fingerprinted (new) |
Piece 4, the 53-page PowerPoint, and remote remote/opf-format-corpus/jhove-hul-117-acrobat101-student-design-report.pdf (247 pages, 15 MB), under a synthetic preset whose size rule each exceeds | Split by M07 into files under the cap, named "4 (1/2)", "4 (2/2)"; every page present once across the parts (qpdf); casefile.piece-split reported; under a preset that forbids splitting, portal.file-size names the piece and nothing is split | CorpusPortalPresetTests.The_size_rule_splits_or_reports (new) |
| The reference case file with its bordereau's page column showing physical pages, and the hundred committed documents M06's memory row assembles, whose bordereau spans several pages | Every page number printed equals the page its line's link reaches; the passes stay within MaxLayoutPasses; casefile.inventory-unsettled never reported; with a reserve of one digit on the hundred-piece file it is, naming the pieces | CorpusCaseFileTests.Bordereau_page_numbers_match_their_pieces (new) |
| The volume and the files per piece | The manifest extracted by pdfdetach validates under its schema; each piece's source digest equals sha256sum of the corpus file; ExifTool reads adcCase:manifestDigest and adcCase:volumeDigest; the independent script recomputes revision 1's digest from the file alone and agrees; a byte flipped in a copy of any piece, of the manifest or of revision 1 is named by the verifier | CorpusIntegrityTests.Every_digest_recomputes_independently (new), ExifToolCaseFileRefereeTests (new) |
| The bordereau of the reference case file, rendered alone | veraPDF's PDF/UA-1 profile reports no failure; the structure reads number, title and date per row in pdfinfo -struct-text; in the volume, the claim is removed because pieces are untagged, and assembly.conformance-claim-removed says so | VeraPdfCaseFileRefereeTests.The_bordereau_is_accessible (new) |
The reference case file shaped by each shipped preset — telerecours, rpva, plex | Every rule the preset encodes is satisfied by the output, one test per rule; the CCITT scan (piece 7) named under portal.text where the preset has that rule; the Cerfa's scripts named under portal.active-content where it has that one; each preset's sources carry a verification date | CorpusPortalPresetTests.Each_preset_rule_holds_on_the_shaped_case_file (new), CasePortalPresetTests.Every_rule_value_cites_its_source (new) |
E-mails: plain and HTML, multipart/alternative, inline cid: images, a PDF, an image and a spreadsheet attached, a forwarded message/rfc822, encoded-word subjects in UTF-8 and ISO-8859-1, quoted-printable and base64, format=flowed, a remote tracking image; and MSG from Outlook with HTML, with RTF-encapsulated HTML and with plain text — not in the corpus (below) | Each becomes a piece whose header block pdftotext reads with every header as written; the body's text found in reading order; each attachment byte-identical under pdfdetach or present as sub-piece 12.1, 12.2 with its own stamp; the inline image drawn (MuPDF renders it); the original message attached as Source, its digest the piece's; no request made for the tracking image, email.resource-refused reported; headers, parts and bytes equal to what Python's email package, olefile and extract_msg read | CorpusEmailTests.Emails_become_pieces_with_their_headers_body_and_attachments (new) |
| Hostile messages generated in the test — a MIME bomb, an unterminated boundary, a megabyte header, an LZFu body declaring 4 GB, a CFB chain that loops | A report and a typed exception where the caller asked for one, within the time and allocation budgets; nothing allocated from a declared size | CorpusEmailTests.Hostile_messages_never_break_the_reader (new) |
| Pleadings citing pieces in every form the patterns know — not in the corpus (below) — linked against the reference case file | Every reference found at the quads PyMuPDF's search_for reports, linked to its piece's first page in the volume and, in per-piece mode, to its file; "pièce adverse n° 4" and "pièces justificatives" unlinked and reported; a reference to piece 99 reported, unlinked | CorpusReferenceTests.Every_reference_reaches_its_piece (new) |
| The volume of the reference case file itself, and its bordereau, run through the linker | No link added: the stamps are M09's artifacts, the bordereau's lines already links | CorpusReferenceTests.Our_own_stamps_and_bordereau_are_never_linked (new) |
| Any case file above, built twice | Identical bytes: volume, files, manifest, sidecar and JSON export | CorpusCaseFileTests.A_case_file_builds_to_the_same_bytes_twice (new) |
A case file of the hundred committed documents M06 assembles; remote remote/govinfo/us-code-2023-title42.pdf (9,302 pages, signed) as one piece of a volume | Retained memory proportional to the largest page, flat between the 10 % and 100 % checkpoints, under a budget recorded in status.md; the signed original attached by streaming, never held | CorpusCaseFileTests.Building_a_large_case_file_holds_its_budget (new), CaseFileBenchmarks (new) |
| The reference case file, through the tool | casefile build, check, inventory and link give what the API gives, byte for byte, as a dotnet tool and as the AOT binary; a frozen renumbering exits with 4 | CorpusToolTests.Casefile_matches_the_api (new) |
The remote rows close only on a green Remote corpus run, recorded in status.md with its date. The rows that name
documents not in the corpus close when the priority-1 gaps below are filled. The MSG half of the e-mail row closes
with slice 10, or moves to a debt issue with it.
Corpus
What the corpus holds
- Pieces of every kind a case file receives: our own contract, invoice and form from Chromium, LibreOffice and
ReportLab; Word 2019's contract schedule; an InDesign PDF/UA chapter with 21 thumbnails and 23 links; PDFMaker's
53-page PowerPoint export with rotated pages; French administrative forms — the Cerfa 12156 with its outline and 47
JavaScript actions, the Cerfa 13983, the static XFA Cerfa 14880; French and Luxembourg legal publications under signature or
seal (
fop-dictao-dila-signed-joafe-notice.pdf,antenna-house-legilux-memorial-pades-lta.pdf,fop22-legilux-memorial-seal-renewed-timestamps.pdf). - Scans, for the text rule: CCITT (
acrobat3-import-irs-1040-1988-scan.pdf,finereader8-frb-sr0115-examiner-guidance.pdf), JBIG2 with and without OCR (the Xerox files), DCT (documents/scan/reportlab-scanned-receipt.pdf), JPX; remote, the copier scans. - Signatures: six approval-signed committed documents and a DocMDP certification (
itext-govinfo-us-code-certified.pdf); remote, certified and sealed documents in number. - Encryption: eighteen committed encrypted documents, eleven opening with an empty password, with their recorded
passwords (
openoffice32-writer-rc4-128-open-password.pdf), and permissions that withhold assembly (livecycle-dod-dd293-aes128-xfa.pdf,pdfwriter3-copyright-office-dmca-summary-1998-rc4-40.pdf). - Outlines and labels in the pieces (
outlinesin some sixty documents,page-labelsin some fifty), which the case file nests or replaces. - Scale: the 1000-page journal committed; remote, the 9,302-page United States Code and the 247-page, 15 MB student report for the size rule.
- Damage:
documents/damaged/*and the remote damaged files, for pieces that arrive broken.
What it lacks
| Need | Why | Priority | Likely source |
|---|---|---|---|
E-mail messages — EML from the clients lawyers receive mail in (Outlook, Gmail's export, Thunderbird, Apple Mail) and MSG from Outlook, covering the constructs of the e-mail row: HTML and text alternatives, inline cid: images, PDF, image and office attachments, a forwarded message, encoded words in two charsets, a remote image, a signed message | The roadmap's third acceptance condition, "the corpus e-mails convert", has no e-mail to run on: the corpus holds PDFs only | 1 | Generated here: Python's email package from our own fictitious content, recorded in build_corpus.py; a public source: the MSG test files of Apache POI's HSMF (Apache-2.0) and the EML test messages of open-source MIME parsers under attribution-only licenses, each screened; a contribution for real clients' output, anonymized (W17) |
| A manifest that can describe non-PDF inputs, with expectations of their own (headers, body strings, attachment names and digests) | Done by M07 for every format, eml and msg among them, with this block (the maintainer's decision of 2026-09-27); this milestone adds fields to it only if its first messages need them | 1 | M07's slice 8 |
| Pleadings citing pieces — French conclusions whose references take every form the patterns know, across line breaks, hyphenated, in a table and a footnote, with an opposing party's pieces and "pièces justificatives" beside them —, from Chromium, LibreOffice and Word, with the references recorded | The linking acceptance needs real text to find, and no corpus document cites pieces | 1 | Generated here: our own source in HTML, ODT and through build_word.ps1, the expected references recorded from the source |
| A case file made by the tools French lawyers use — a bordereau and stamped pieces from Kleos, Hub-Avocat or Acrobat by hand | The verifier should read a real bordereau, and the presets' defaults should match what practitioners already file | 3 | A contribution, anonymized (M09 lists the stamped pieces at the same priority) |
| A filing a portal refused, with the portal's message | The strongest test of a preset is the rule it would have caught | 3 | A contribution |
| Image files as pieces | Pieces that are photographs or scanned TIFFs go through M07, whose own gap this is | 2 | M07's image files, which M07's format field describes |
| A committed piece over the size caps the presets encode | The size rule meets real documents only in the remote corpus, since a committed file stays under 2 MB | 3 | Stays remote: a synthetic preset with a small cap covers the committed run |
Traps
- The bookmark must say what the inventory says. A signet built from a different string than the inventory line — a short title here, the full libellé there, a non-breaking space in one and a space in the other — is the exact mismatch the texts sanction. One model, one string, and a verifier that compares by extraction with spaces folded.
- Numbers are cited by others. Renumbering after communication breaks every reference in the pleadings of both parties and of the court; "12 bis" exists for this reason.
- The bordereau is part of what it counts. Placed first, its own length moves every physical page after it; with labels that restart per piece nothing moves — choose the plan before choosing the algorithm.
- "n°" has many spellings: the degree sign, the masculine ordinal (U+00BA), a superscript "o", "no", "N°", with or without a space, "nos" for the plural; an OCR layer adds its own. A pattern that knows one misses half the references.
- Our own stamps say "Pièce n° 12". A linker that searches the volume finds them first.
- A volume carries no signature. Merging voids every signature it copies; the signed original must travel beside the stamped copy, and a file per piece can instead stamp after the signature.
- A digest cannot contain itself. The volume's digest lives in an appended update over revision 1, or beside the file — never "patched in" to bytes it covers.
- Portal rules change and are not ours. A number copied from a blog or a colleague is a rejected filing waiting to happen: every value cites the text or the guide it was read from, and the date.
- E-mail is hostile HTML. A body can restyle whatever it shares a stylesheet with, fetch a pixel that tells its sender the message was opened, and pretend to be a header block. The header's styles win by layer, nothing remote is fetched, and the header block is framed as ours.
- MSG has no time zone. Its times are UTC; the sender's offset is gone. Showing the machine's local time would make the same file render differently on two servers.
CultureInfois not a locale database under invariant globalization: French month names come from the satellite's data.- An Exchange sender may have no SMTP address — an X.500 path is what the message holds, and what is shown.
- What would reopen EA-PDF: an archive or a court asking for e-mail pieces in the PDF Association's archival profile; the rendition already keeps the source message as an associated file, which EA-PDF also asks for.
Documentation
docs/website/docs/guides/case-files.md(new): the model, numbering and communications, the volume and files per piece, the bordereau and its page plans, signed pieces, the integrity manifest, checking a case file.docs/website/docs/guides/email-to-pdf.md(new): EML and MSG to a piece, attachments, what is never fetched.docs/website/docs/reference/court-portals.md(new): each preset, its rules, its sources and when they were verified, generated from the preset files so that the page cannot drift from them.docs/website/docs/reference/case-file-schema.md(new): the JSON schemas of the model, the export and the manifest.docs/website/docs/reference/casefile-report-codes.md(new): everycasefile.*,portal.*andemail.*code.docs/website/docs/reference/tool/index.md: thecasefileverb.docs/website/docs/introduction.md: case files added to what the library does.docs/architecture.md: the CaseFile satellite's parts, and any dependency the slice-9 ADR takes.- The HTML engine's guide and API reference (M12's):
PdfRenderOptions.ExternalDestinationsandIPdfRenderer.MeasureAsync, the two members this milestone adds toAdCodicem.Pdf.Html. docs/releasing.md: re-verifying the presets' sources before a stable release.docs/corpus.md: e-mails and other non-PDF inputs in the manifest; the new referees (Python'semail,olefile,extract_msg).docs/features/features.json: thecase-filesentry brought to its state.docs/status.md: the measurements and the presets' verification dates.
Exit criteria
- The model, its JSON form and schema, numbering with suffixes and frozen communications exist, and every product follows from one resolution.
- A volume and files per piece carry stamps, bookmarks, labels, named destinations and a bordereau that agree with the model, confirmed by the verifier and by external extraction.
- The bordereau is rendered by M12, tagged, linked, and its page numbers are right under every page plan, within ADR 39's bounded passes.
- Signed pieces keep a verifiable original; certified pieces are refused unless the caller insists; encrypted and damaged pieces follow M16's and M05's policies, and every loss is reported.
- The integrity manifest is embedded, summarized in XMP, and every digest — the volume's included — recomputes with an independent script.
- The Télérecours, e-Barreau/RPVA and PLEX presets exist as data, each value citing a source read and dated, and a test per rule holds on the reference case file.
- EML becomes pieces with their headers, body and attachments intact, and MSG likewise — or its deferral is a debt issue, the roadmap making it optional; nothing remote is fetched; every guard is reported under its own code.
- References to pieces are found and linked, and never on our own stamps.
- The acceptance conditions above pass on the corpus, in CI, with no document skipped, and the remote rows on a
green
Remote corpusrun recorded instatus.md. - Unit tests cover each behavior, its degenerate and its hostile cases; the MIME, CFB, LZFu and RTF readers join the nightly fuzzing campaign.
- Integration tests run poppler, qpdf, pikepdf, PyMuPDF, pdf.js, veraPDF, pyHanko, ExifTool, pdfplumber and the Python oracles in containers.
-
CaseFileBenchmarksmeasures analysis, a hundred-piece volume and e-mail rendering withMemoryDiagnoser; the memory budget is enforced in CI and recorded instatus.md. - The tool's
casefileverb ships with documented JSON. - The documentation site publishes the pages above;
features.jsonandstatus.mdare brought in line. - Every page of Documentation is written in its Diátaxis section, one mode per page (ADR 47).