Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
# @suaveplan/dmarc - DMARC report parser — parses aggregate (RUA) and forensic (RUF) DMARC XML reports into typed structures
## Summary
DMARC report parser for `@suaveplan` — parses aggregate (RUA) and forensic (RUF) DMARC XML reports into validated, strongly-typed structures. Accepts raw XML strings or gzip-compressed binary blobs.
<!-- begin:auto-docs -->
## Documentation tree
- [gzip](./src/decoders/gzip.md) — Provides gzip decompression and input-decoding utilities for `@suaveplan/dmarc`. DMARC aggregate and forensic reports a…
- [xml](./src/decoders/xml.md) — Wraps `fast-xml-parser` with a fixed, strict configuration tuned for DMARC report XML. The module isolates all parser c…
- [errors](./src/errors/dmarc-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [dmarc-error](./src/errors/dmarc-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [dmarc-otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [aggregate](./src/parsers/aggregate.md) — Implements `parseDmarcAggregate`, the primary entry point for converting raw DMARC aggregate (RUA) report data into val…
- [forensic](./src/parsers/forensic.md) — Implements `parseDmarcForensic`, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify doma…
<!-- end:auto-docs --> - [dmarc-error](./src/errors/dmarc-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [dmarc-otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [aggregate](./src/parsers/aggregate.md) — Implements `parseDmarcAggregate`, the primary entry point for converting raw DMARC aggregate (RUA) report data into val…
- [forensic](./src/parsers/forensic.md) — Implements `parseDmarcForensic`, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify doma…
- [dmarc-error](./src/errors/dmarc-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [dmarc-otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [aggregate](./src/parsers/aggregate.md) — Implements `parseDmarcAggregate`, the primary entry point for converting raw DMARC aggregate (RUA) report data into val…
- [forensic](./src/parsers/forensic.md) — Implements `parseDmarcForensic`, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify doma…
- [dmarc-error](./src/errors/dmarc-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [dmarc-otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [aggregate](./src/parsers/aggregate.md) — Implements `parseDmarcAggregate`, the primary entry point for converting raw DMARC aggregate (RUA) report data into val…
- [forensic](./src/parsers/forensic.md) — Implements `parseDmarcForensic`, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify doma…
- [dmarc-error](./src/errors/dmarc-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_ - [dmarc-otel](./src/otel/dmarc-otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [aggregate](./src/parsers/aggregate.md) — Implements `parseDmarcAggregate`, the primary entry point for converting raw DMARC aggregate (RUA) report data into val…
- [forensic](./src/parsers/forensic.md) — Implements `parseDmarcForensic`, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify doma…
## Packages
| Module | Description |
|---|---|
| [`src/parsers/aggregate.ts`](src/parsers/aggregate.md) | Parse DMARC aggregate (RUA) reports |
| [`src/parsers/forensic.ts`](src/parsers/forensic.md) | Parse DMARC forensic (RUF) reports |
| [`src/decoders/gzip.ts`](src/decoders/gzip.md) | Gzip detection and decompression |
| [`src/decoders/xml.ts`](src/decoders/xml.md) | XML parsing via fast-xml-parser |
| [`src/errors/dmarc-error.ts`](src/errors/dmarc-error.md) | Typed error class and code registry |
| [`src/otel/dmarc-otel.ts`](src/otel/dmarc-otel.md) | OpenTelemetry metrics |
## Quick Start
```ts
import { parseDmarcAggregate, parseDmarcForensic } from "@suaveplan/dmarc";
// Parse an aggregate report from an XML string
const aggregate = parseDmarcAggregate(xmlString);
console.log(aggregate.reportMetadata.orgName);
console.log(aggregate.record[0].row.sourceIp);
// Parse from gzip-compressed bytes (auto-detected)
const { gzipSync, strToU8 } = await import("fflate");
const compressed = gzipSync(strToU8(xmlString));
const report = parseDmarcAggregate(compressed);
// Parse a forensic report
const forensic = parseDmarcForensic(forensicXml);
console.log(forensic.feedback.policyPublished.domain);
```
## Error Handling
```ts
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc";
try {
parseDmarcAggregate(input);
} catch (err) {
if (err instanceof DmarcError) {
switch (err.code) {
case DMARC_CODES.PARSE_ERROR: // XML could not be parsed
case DMARC_CODES.INVALID_REPORT: // Schema validation failed
case DMARC_CODES.UNSUPPORTED_ENCODING: // Gzip decompression failed
}
}
}
```

View File

@@ -0,0 +1,76 @@
# gzip
## Purpose
Provides gzip decompression and input-decoding utilities for `@suaveplan/dmarc`. DMARC aggregate and forensic reports are commonly delivered as gzip-compressed XML attachments (`.xml.gz`). This module abstracts the decompression step so that parsers can accept both raw XML strings and compressed binary blobs through a single entry point without depending on Node.js stream APIs or native bindings.
## Features
- `isGzip(bytes)` — magic-byte probe to identify gzip streams before attempting decompression
- `decompressGzip(bytes)` — synchronous gunzip via `fflate.gunzipSync` + `strFromU8` for zero-dependency UTF-8 conversion
- `decodeInput(input)` — unified entry point: pass-through for strings, auto-gunzip for gzip blobs, plain UTF-8 decode for non-gzip bytes
- Structured errors via `DmarcError` with code `DMARC.UNSUPPORTED_ENCODING` on decompression failure
- No Node.js built-ins (`node:zlib`, `node:fs`) — pure `fflate` implementation works in browsers and edge runtimes
## Usage: Basic Example
```ts
import { decodeInput } from "@suaveplan/dmarc/decoders/gzip";
// Accepts a plain XML string
const xml1 = decodeInput("<feedback>...</feedback>");
// Accepts a gzip-compressed Uint8Array
const gzippedBytes = new Uint8Array([0x1f, 0x8b, /* ... */]);
const xml2 = decodeInput(gzippedBytes);
```
## Usage: Advanced Example
```ts
import { isGzip, decompressGzip, decodeInput } from "@suaveplan/dmarc/decoders/gzip";
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
async function fetchAndDecode(url: string): Promise<string> {
const resp = await fetch(url);
const bytes = new Uint8Array(await resp.arrayBuffer());
if (isGzip(bytes)) {
// Explicit probe — useful for logging / metrics before decode
console.log("Input is gzip-compressed, decompressing…");
return decompressGzip(bytes);
}
return new TextDecoder().decode(bytes);
}
// Error handling
try {
decodeInput(new Uint8Array([0x1f, 0x8b, 0x00])); // truncated gzip
} catch (err) {
if (err instanceof DmarcError && err.code === DMARC_CODES.UNSUPPORTED_ENCODING) {
console.error("Decompression failed:", err.message, err.cause);
}
}
```
## API Reference
### `isGzip(bytes: Uint8Array): boolean`
Returns `true` if `bytes` starts with the two gzip magic bytes (`0x1f 0x8b`). Returns `false` for any input shorter than 2 bytes.
### `decompressGzip(bytes: Uint8Array): string`
Decompresses `bytes` synchronously via `fflate.gunzipSync` and converts the result to a UTF-8 string using `fflate.strFromU8`.
**Throws:** `DmarcError` with code `DMARC.UNSUPPORTED_ENCODING` if decompression fails.
### `decodeInput(input: string | Uint8Array): string`
Unified decoder: returns strings unchanged, decompresses gzip bytes, and UTF-8 decodes non-gzip bytes.
**Throws:** `DmarcError` with code `DMARC.UNSUPPORTED_ENCODING` if gzip decompression fails.
## Implementation Notes
`fflate.gunzipSync` is chosen over `DecompressionStream` (Streams API) because the synchronous path avoids async complexity in callers and works identically across Node.js, Bun, Deno, and browser environments. `strFromU8` is preferred over `TextDecoder` on the decompressed output because it avoids an extra allocation when fflate has already produced a UTF-8 buffer.

View File

@@ -0,0 +1,69 @@
# xml
## Purpose
Wraps `fast-xml-parser` with a fixed, strict configuration tuned for DMARC report XML. The module isolates all parser configuration in one place so that both aggregate and forensic parsers produce a consistently shaped raw object tree, reducing the surface area for subtle parsing divergences between the two report types.
## Features
- Shared `XMLParser` instance configuration: no-op attribute prefix, numeric tag-value coercion, whitespace trimming
- `isArray` hook forces `<record>`, `<dkim>`, `<spf>`, and `<error>` nodes to always deserialise as arrays, preventing single-item vs. multi-item inconsistency
- Structured errors via `DmarcError` with code `DMARC.PARSE_ERROR` on any parse failure
- Pure function — stateless, no module-level side effects
## Usage: Basic Example
```ts
import { parseXml } from "@suaveplan/dmarc/decoders/xml";
const raw = parseXml(`
<feedback>
<version>1.0</version>
<report_metadata>
<org_name>Example</org_name>
</report_metadata>
</feedback>
`);
console.log((raw as any).feedback.version); // "1.0"
```
## Usage: Advanced Example
```ts
import { parseXml } from "@suaveplan/dmarc/decoders/xml";
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
function safeParse(xml: string): Record<string, unknown> | null {
try {
return parseXml(xml);
} catch (err) {
if (err instanceof DmarcError && err.code === DMARC_CODES.PARSE_ERROR) {
console.error("XML parse failed:", err.message);
return null;
}
throw err;
}
}
// Single <record> is still an array
const result = parseXml("<feedback><record><row/></record></feedback>");
const records = (result as any).feedback.record; // Array, length 1
```
## API Reference
### `parseXml(xml: string): Record<string, unknown>`
Parses a UTF-8 XML string into a raw nested JS object.
**Parameters:**
- `xml` — Well-formed UTF-8 XML string.
**Returns:** Raw `Record<string, unknown>` mirroring the XML element hierarchy.
**Throws:** `DmarcError` with code `DMARC.PARSE_ERROR` if parsing fails or the parser returns a non-object value.
## Implementation Notes
`fast-xml-parser` is used in preference to DOM-based parsers (e.g. `DOMParser`) because it works in all JavaScript environments — including Node.js and Bun — without requiring a DOM polyfill. The `isArray` hook is critical for DMARC reports: RFC 7489 allows a single `<record>` element, and without forcing array mode a one-record report would produce an object where `record` is a plain object rather than a length-1 array, breaking downstream mapping code. Numeric coercion (`parseTagValue: true`) handles the `<begin>`, `<end>`, `<count>`, and `<pct>` fields that appear as integer text nodes.

View File

@@ -0,0 +1,84 @@
# dmarc-error
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the lightweight error class and stable code registry for `@suaveplan/dmarc`. Because this package carries no `@suaveplan/error` runtime dependency (it is a leaf package with no `@suaveplan/*` runtime deps), it provides its own self-contained error substrate that gives callers the same discriminated-error contract: a strongly-typed `code` field for `switch`/`instanceof` discrimination, plus an idiomatic `cause` chain via the built-in `ErrorOptions`.
## Features
- `DMARC_CODES` — three stable string-literal constants (`DMARC.PARSE_ERROR`, `DMARC.INVALID_REPORT`, `DMARC.UNSUPPORTED_ENCODING`)
- `DmarcCode` — union type of all valid code values
- `DmarcError` — `Error` subclass with a `readonly code: DmarcCode` field and preserved `name`
## Usage: Basic Example
```ts
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
try {
// ... parse logic
} catch (cause) {
throw new DmarcError(DMARC_CODES.PARSE_ERROR, "Failed to parse XML", { cause });
}
```
## Usage: Advanced Example
```ts
import { DmarcError, DMARC_CODES, type DmarcCode } from "@suaveplan/dmarc/errors";
function handleDmarcError(err: unknown): never {
if (err instanceof DmarcError) {
switch (err.code) {
case DMARC_CODES.PARSE_ERROR:
console.error("XML parse failure:", err.message, err.cause);
break;
case DMARC_CODES.INVALID_REPORT:
console.error("Schema validation failed:", err.message);
break;
case DMARC_CODES.UNSUPPORTED_ENCODING:
console.error("Cannot decode input:", err.message);
break;
}
}
throw err;
}
```
## API Reference
### `DMARC_CODES`
`{ PARSE_ERROR: "DMARC.PARSE_ERROR"; INVALID_REPORT: "DMARC.INVALID_REPORT"; UNSUPPORTED_ENCODING: "DMARC.UNSUPPORTED_ENCODING" }`
Immutable code registry. Use these constants instead of raw strings so that typos produce type errors.
### `DmarcCode`
`"DMARC.PARSE_ERROR" | "DMARC.INVALID_REPORT" | "DMARC.UNSUPPORTED_ENCODING"`
Union of all valid code values for use in signatures and `switch` exhaustiveness checks.
### `DmarcError`
`class DmarcError extends Error`
**Constructor:** `new DmarcError(code: DmarcCode, message: string, options?: ErrorOptions)`
- `code` — one of the `DMARC_CODES` values
- `message` — human-readable description
- `options.cause` — original error for cause-chain logging
**Properties:**
- `code: DmarcCode` — readonly; set during construction
- `name: string` — always `"DmarcError"`
**Throws:** nothing
## Implementation Notes
The absence of `@suaveplan/error` as a runtime dependency is intentional: this package is a leaf node in the dependency graph and must remain importable in environments where the broader Suaveplan stack is not installed. The same discriminated-error pattern (stable `code` field, `cause` chain, subclassed `Error`) is preserved so that tooling and callers that understand `@suaveplan/error` conventions can handle these errors uniformly.

View File

@@ -0,0 +1,69 @@
# dmarc-otel
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides OpenTelemetry metric instruments for `@suaveplan/dmarc`. It gives operators visibility into parse throughput and latency without coupling the parse logic itself to observability concerns. All instruments are created lazily by the OTel SDK's no-op implementation, so importing this module in an environment without an OTel SDK installed is always safe — the calls become no-ops.
## Features
- `dmarcParseTotal` — counter tracking total parse invocations by `kind` and `status`
- `dmarcParseDurationMs` — histogram recording parse latency in milliseconds by `kind`
- `recordParse()` — convenience wrapper that updates both instruments in one call
- Zero runtime dependencies beyond `@opentelemetry/api`
## Usage: Basic Example
```ts
import { recordParse } from "@suaveplan/dmarc/otel/dmarc-otel";
const start = performance.now();
try {
// ... parse logic
recordParse("aggregate", "success", performance.now() - start);
} catch {
recordParse("aggregate", "error", performance.now() - start);
throw;
}
```
## Usage: Advanced Example
```ts
import { dmarcParseTotal, dmarcParseDurationMs, recordParse } from "@suaveplan/dmarc/otel/dmarc-otel";
// Direct instrument access for custom attribute sets
dmarcParseTotal.add(1, { kind: "aggregate", status: "success", source: "s3" });
dmarcParseDurationMs.record(42, { kind: "forensic", source: "email" });
// Or use the convenience wrapper for standard usage
recordParse("forensic", "success", 38);
```
## API Reference
### `dmarcParseTotal`
`Counter` — incremented once per parse call. Attributes: `kind` (`"aggregate" | "forensic"`), `status` (`"success" | "error"`).
### `dmarcParseDurationMs`
`Histogram` — records elapsed parse time in milliseconds. Attributes: `kind` (`"aggregate" | "forensic"`).
### `recordParse(kind, status, durationMs): void`
Convenience function that increments `dmarcParseTotal` and records `durationMs` in `dmarcParseDurationMs` in a single call.
**Parameters:**
- `kind` — `"aggregate"` or `"forensic"`
- `status` — `"success"` or `"error"`
- `durationMs` — elapsed time in milliseconds
**Returns:** `void`
## Implementation Notes
The meter name is `"@suaveplan/dmarc"` which matches the npm package name, making it trivial to filter metrics by package in dashboards. Histogram bucket boundaries are left to the OTel SDK/exporter configuration rather than hardcoded here, which is the recommended practice per the OTel specification. Importing this module in test environments without an SDK causes zero issues because the OTel API ships a built-in no-op implementation.

View File

@@ -0,0 +1,108 @@
# aggregate
## Purpose
Implements `parseDmarcAggregate`, the primary entry point for converting raw DMARC aggregate (RUA) report data into validated, strongly-typed `DmarcAggregateReport` objects. It handles the full pipeline: gzip auto-detection and decompression, XML parsing, snake_case-to-camelCase field mapping, single-vs-multiple element normalisation, and Zod schema validation with structured error reporting.
## Features
- Auto-detects gzip input via magic-byte probe — no caller configuration needed for compressed reports
- Accepts both raw XML strings and `Uint8Array` binary blobs in a single overload
- Full snake_case → camelCase mapping for all RFC 7489 field names
- Handles variable-arity elements: `<record>`, `<dkim>`, `<spf>` can be single or multiple
- Validates the mapped structure with `DmarcAggregateReportSchema` (Zod)
- Emits OTel counter and histogram via `recordParse()` on both success and error paths
- Wraps all failures in typed `DmarcError` with stable codes
## Usage: Basic Example
```ts
import { parseDmarcAggregate } from "@suaveplan/dmarc/parsers/aggregate";
const report = parseDmarcAggregate(`
<feedback>
<version>1.0</version>
<report_metadata>
<org_name>Example Corp</org_name>
<email>dmarc@example.com</email>
<report_id>12345</report_id>
<date_range><begin>1609459200</begin><end>1609545600</end></date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim><aspf>r</aspf><p>none</p><pct>100</pct>
</policy_published>
<record>
<row>
<source_ip>192.0.2.1</source_ip><count>5</count>
<policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>pass</spf></policy_evaluated>
</row>
<identifiers><header_from>example.com</header_from></identifiers>
<auth_results>
<dkim><domain>example.com</domain><result>pass</result></dkim>
<spf><domain>example.com</domain><result>pass</result></spf>
</auth_results>
</record>
</feedback>
`);
console.log(report.reportMetadata.orgName); // "Example Corp"
console.log(report.record[0].row.count); // 5
```
## Usage: Advanced Example
```ts
import { parseDmarcAggregate } from "@suaveplan/dmarc/parsers/aggregate";
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
import { gzipSync, strToU8 } from "fflate";
// Parse from gzip-compressed bytes
const xmlBytes = strToU8(xmlString);
const compressed = gzipSync(xmlBytes);
const report = parseDmarcAggregate(compressed);
// Error discrimination
try {
parseDmarcAggregate("not xml");
} catch (err) {
if (err instanceof DmarcError) {
switch (err.code) {
case DMARC_CODES.PARSE_ERROR:
// XML could not be parsed
break;
case DMARC_CODES.INVALID_REPORT:
// Parsed but failed schema validation
break;
case DMARC_CODES.UNSUPPORTED_ENCODING:
// Gzip decompression failed
break;
}
}
}
```
## API Reference
### `parseDmarcAggregate(input, options?): DmarcAggregateReport`
**Parameters:**
- `input: string | Uint8Array` — raw XML string or binary data (plain or gzip-compressed)
- `options.decompress?: boolean` — force gzip decompression even without magic bytes
**Returns:** `DmarcAggregateReport` — fully validated aggregate report
**Throws:**
- `DmarcError(DMARC.PARSE_ERROR)` — XML parse failure
- `DmarcError(DMARC.INVALID_REPORT)` — missing required elements or schema validation failure
- `DmarcError(DMARC.UNSUPPORTED_ENCODING)` — gzip decompression failure
### `ParseDmarcAggregateOptions`
`{ decompress?: boolean }`
## Implementation Notes
The parser uses a two-phase approach: structural mapping first (XML → plain object), validation second (Zod). This separation means that when validation fails the error message includes Zod's structured issue list rather than a raw type error, making debugging significantly easier. The `performance.now()` timing wraps the entire pipeline so OTel histograms reflect the true end-to-end cost including decompression and schema validation.

View File

@@ -0,0 +1,89 @@
# forensic
## Purpose
Implements `parseDmarcForensic`, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify domain owners of individual authentication failures; they share the `<feedback>` / `<report_metadata>` / `<policy_published>` structure with aggregate reports but omit the `<record>` block and may include a raw MIME-formatted `auth_failure_report` element. This module provides the same auto-detect-decompress → parse → map → validate pipeline as the aggregate parser but targets the forensic schema.
## Features
- Auto-detects gzip input via magic-byte probe
- Accepts raw XML strings or `Uint8Array` binary blobs
- Maps snake_case XML fields to camelCase typed fields
- Surfaces `authFailureReport` string when present in the source XML
- Validates against `DmarcForensicReportSchema` (Zod)
- Emits OTel counter and histogram for both success and error paths
- Wraps all failures in typed `DmarcError` with stable codes
## Usage: Basic Example
```ts
import { parseDmarcForensic } from "@suaveplan/dmarc/parsers/forensic";
const report = parseDmarcForensic(`
<feedback>
<version>1.0</version>
<report_metadata>
<org_name>Example Corp</org_name>
<email>dmarc@example.com</email>
<report_id>ruf-001</report_id>
<date_range><begin>1609459200</begin><end>1609545600</end></date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim><aspf>r</aspf><p>none</p><pct>100</pct>
</policy_published>
</feedback>
`);
console.log(report.feedback.reportMetadata.orgName); // "Example Corp"
```
## Usage: Advanced Example
```ts
import { parseDmarcForensic } from "@suaveplan/dmarc/parsers/forensic";
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
import { gzipSync, strToU8 } from "fflate";
const compressed = gzipSync(strToU8(xmlString));
const report = parseDmarcForensic(compressed);
// Access optional auth failure report body
if (report.authFailureReport) {
console.log("Failure report MIME body:", report.authFailureReport);
}
// Discriminated error handling
try {
parseDmarcForensic("<bad>xml</bad>");
} catch (err) {
if (err instanceof DmarcError && err.code === DMARC_CODES.INVALID_REPORT) {
// Schema validation failed — missing required elements
}
}
```
## API Reference
### `parseDmarcForensic(input, options?): DmarcForensicReport`
**Parameters:**
- `input: string | Uint8Array` — raw XML string or binary data
- `options.decompress?: boolean` — force gzip decompression
**Returns:** `DmarcForensicReport` — validated forensic report
**Throws:**
- `DmarcError(DMARC.PARSE_ERROR)` — XML parse failure
- `DmarcError(DMARC.INVALID_REPORT)` — schema validation failure or missing required elements
- `DmarcError(DMARC.UNSUPPORTED_ENCODING)` — gzip decompression failure
### `ParseDmarcForensicOptions`
`{ decompress?: boolean }`
## Implementation Notes
The forensic parser is structurally identical to the aggregate parser except for the output schema. Both share the same `str`/`num` coercion helpers and the same two-phase (map → validate) approach. In practice, RFC 7489 forensic reports are far less common than aggregate reports and are often transmitted as raw MIME messages rather than standalone XML files; the `authFailureReport` field is therefore optional and surfaced as a plain string for callers to process further if needed.