# ADR 0004: Report Contract - Status: accepted - Date: 2026-10-01 - PR: #6 ## Context The gate of step 6 evaluates werkcrappit's own JSON report, not PIT's XML (specification 6.4). That makes the report a contract: versioned, schema-checked, exact where a limit is compared, and never stale. ## Decision - **Files.** `mutation-crap.json` (machine-readable) and `mutation-crap.md` (for people), both in PIT's report directory, next to PIT's own reports; plus a few lines on the console. All three are written from one `ReportData`, so they cannot disagree. - **Schema.** `mutation-crap.schema.json` (JSON Schema 2020-12) ships in the plugin JAR next to the report classes. `schemaVersion` is 1; any change of the shape raises it. Since PR#36 it is 2: the section `classes` carries cohesion and coupling per top-level class (RFC 0002). Since PR#41 it is 3: each class also carries its change risk, exact and rounded, and what it is made of (ADR 0008). The schema is strict (`additionalProperties: false`), so drift between code and contract fails a test. - **Sections.** `run` (status, diagnostics), `definition` (metric id and version, policy, formula, complexity provider and version), `provenance` (plugin and PIT version, analysis context, code roots, origin and SHA-256 of every analysed class file), `configuration` (werkcrappit's options and the PIT options that are known), `scope`, `summary`, `methods`. - **Numbers.** Undefined values are `null`, never 0, `NaN` or `Infinity`; the writer refuses non-finite numbers. `mutationCrap` is rounded to four decimals for reading; `mutationCrapExact` carries the exact fraction as decimal strings, and only it may be compared against a limit. - **Summary.** Counts per state and the ten highest scores. No sum or average: a project-wide CRAP would be a metric the specification does not define. - **Determinism.** No timestamp, no run id; maps are written in a fixed order. The same inputs and configuration give the same bytes. - **Never stale.** `runStart()` replaces both files with a `RUNNING` marker; only `runEnd()` writes the finished report. A run that dies in between leaves a report that says so, never an older finished one. - **Configuration.** werkcrappit's options come from PIT's `pluginConfiguration` and use the prefix `werkcrappit.`: `analysisContext`, `excludeClasses`, `excludeMethods`. An unknown key with that prefix stops the run; keys of other plugins are left alone. - **PIT's configuration.** Target and excluded classes, excluded methods, mutators and features are recorded; target tests, mutation filters and test framework settings are not. The report says so (`pitConfigurationComplete: false`) instead of implying a complete fingerprint. Environment variables and system properties are never written; they may hold credentials. ## Consequences - A gate can tell a finished report from a running or broken one by `run.status` and the schema alone. - Writing the files is not atomic: a crash during the final write leaves a truncated file, which the schema rejects.