cfs.auditPack.* — audit-pack generation

Phase 10 transport note. Was /api/manifests/[id]/audit-pack. buildAuditPackArtifact in packages/core/src/handlers/audit-pack.ts now backs two IPC channels: cfs:audit-pack:get (returns bytes in-process — used by the iframe preview via cfs-blob://) and cfs:audit-pack:save (native save dialog). Contract preserved. Supports PDF and markdown output, with RFC 6266 filenames.

cfs.auditPack.get(req) — channel cfs:audit-pack:get

Generate a self-contained audit-pack and return the bytes in-process. Used by the renderer for the iframe preview pane.

type AuditPackRequest = {
  id: string;                              // manifest namespace
  format?: 'pdf' | 'markdown';             // default 'pdf'
  against?: string | null;                 // BASELINE_CATALOG id for compliance section
  disposition?: 'inline' | 'attachment';   // default 'attachment'
};

type AuditPackArtifact = {
  filename: string;                        // <sanitized-ns>-audit-pack-YYYYMMDD.{pdf|md}
  contentType: string;
  bytes: Uint8Array;                       // PDF binary or UTF-8 markdown
};

cfs.auditPack.save(req) — channel cfs:audit-pack:save

Same handler, but main pops a native save dialog and writes bytes directly. Returns { ok: true, path, filename } on success or IpcErrorEnvelope on user-cancel. The pure artifact also carries a contentDisposition field (ASCII + RFC 5987 filename*=UTF-8''…) for non-IPC hosts.

Sections (PDF)

Builder: packages/core/src/audit-pack/. Section order: header → device audit (when present) → compliance → version history → rationale log → citations → footer.

SectionSourceWithout source
Headerregistration metadataalways present
Device audit~/.configforge/audit-results/<ns>.json (v0.1.6)omitted
ComplianceComplianceReport (when against is set AND CIS data present)"not available" line
Version historyhistory snapshots + author/rationalecolumns render ; max 50 rows
Rationale log~/.configforge/rationale/<ns>.jsonlomitted; max 30 entries
Citationsanalysis Provenance recordsomitted (no on-disk provenance store yet)
Footertimestamp + page numbersalways present

Each section is no-throw best-effort. A failing section emits a single "(section unavailable)" line; one bad input doesn't poison the whole PDF.

HTML escaping (CF-SEC-005 / 006)

Markdown (packages/core/src/audit-pack/markdown.ts) and PDF text both escape user values via packages/core/src/markdown/escape.ts before embedding. <script> in a rationale reason renders as literal text in both formats.

Performance

InputWall-clockByte size
50 rules, 5 history entries< 0.5 s
350 rules, 50 history entries< 3 s< 2 MB

pdfkit returns a Readable; the handler concatenates to a Buffer and returns new Uint8Array. Streaming-large packs go through the cfs-blob://audit-pack/<id> protocol instead. Built-in Helvetica fonts; non-WinAnsi chars → ?.

Errors

StatusWhen
400Missing manifest id, or format not pdf / markdown.
404Manifest not registered.
500Unexpected error (e.g. pdfkit crash).

See also