feat(cli): accept an archive of dranzer reports as one material
Summary
A single CERT/CC dranzer run emits one plain-text report per test mode (-b, -p, -s, -t), so the evidence arrives as an archive rather than a single file. CERTCC_DRANZER now accepts either a single report or a zip/tar.gz holding several, following the RADAMSA_CRASHES precedent: the archive is recorded whole as one material under the contract's declared material name, with the number of reports it holds recorded as an annotation.
Because the policy engine reads dranzer content, recording the archive is not sufficient on its own — parsing archive bytes as text yields an empty report, which makes a policy skip rather than evaluate. The policy-input projection therefore aggregates the archive's entries, summing the run counters and unioning the per-object and finding data, and carries a per-report breakdown alongside so a policy can be precise about which mode reported what. The aggregate is promoted to the top level of the projection, so policies that read tool, summary and findings behave the same whether the material was a single report or a bundle.
Archive container detection and walking move into a new archiveio leaf package, so the crafters and the policy-input projections share one implementation of the entry-count, uncompressed-size and path-traversal guards rather than each carrying their own. Two gaps in those guards are closed as part of the move: the size cap reported its error alongside a full buffer, which io.ReadFull discards, so a stream read in exact block sizes passed the limit unreported; and entries skipped as directories or symlinks counted against neither limit.
The dranzer fingerprint no longer falls back to searching the raw text for a per-object banner. The CSV companion file that ships beside real reports quotes that banner inside one of its columns, so the fallback accepted a file carrying no version, objects, findings or counters — which would then skip policy evaluation rather than being rejected. Genuine reports carry the banner on its own line and are unaffected.
Refs PFM-6467
AI assistance
This change was produced with assistance from Claude Code, disclosed via the Assisted-by trailer on the commit.