# PDL-JSON-1: Strict-JSON Inventory of Public summarize_* Functions **Status:** OPEN debt ticket. Filed in v0.31b0. Migration executes across v0.31b1 (discovery_task_result), v0.32b (generator_confidence), and follow-up per-summary PRs on a rolling basis. ## Motivation `src/pdelie/reporting/summaries.py` currently exposes two JSON-validation helpers side by side. `_validate_json_compatible(value, *, name)` at lines 187–193 calls `json.dumps(safe_value)` with the default `allow_nan=True`; `_validate_strict_json_compatible(value, *, name)` at lines 196–202 calls `json.dumps(safe_value, allow_nan=False)` and rejects `NaN`, `+Inf`, `-Inf`, and non-JSON-native types. Both are reachable from public `summarize_*` entry points, and the choice between them is made at the summary-writing site rather than at a central schema layer. The permissive helper is wired into `_summary_payload(summary_type, **items)` at line 261, which is the common return path for every summarizer that does not open-code its own strict wrap. The concrete risk is that any producer inside a permissive summary can emit `float("nan")` — from `numpy` division-by-zero, from an unpopulated diagnostics slot, from a fit path that fell back to sentinel values — and that value will pass through `_summary_payload` unchanged, serialize under `allow_nan=True`, and become a downstream `NaN` token in JSON reports, notebook outputs, and workflow manifests. The strict-JSON gate documented in `docs/design/V0_30_HYGIENE_AUDIT.md` under "Strict JSON / no-NaN policy" only applies at the sites that opted in; permissive summaries are outside that gate today. A bulk migration — flipping every `_summary_payload` invocation to strict validation in one PR — is not the right shape. Sub-summaries currently emit `NaN` under the permissive contract in paths that today's tests explicitly exercise (fit fallbacks, missing-verification stubs, unavailable robustness metrics). Migrating a summarizer to strict without first hardening each of its embedded producers (replacing `NaN` with `None` or an explicit unavailable-status string, per the audit policy) would surface the debt as test failures rather than as behavior change. The migration must therefore be per-summary, with the composed summaries (those that embed other `summarize_*` outputs) migrated only after their leaf dependencies have been hardened. ## Inventory table Line numbers below reference `src/pdelie/reporting/summaries.py` at `feat/v0.31b0-prep` HEAD. All permissive-category entries route their final payload through `_summary_payload(...)`, which calls `_validate_json_compatible` at line 261. Strict-category entries call `_validate_strict_json_compatible` directly on the outgoing payload dict. | Function | file:line | Validation helper | Category | Composed? | Migration priority | | --- | --- | --- | --- | --- | --- | | `summarize_field_batch_readiness` | `src/pdelie/reporting/summaries.py:888` | `_validate_json_compatible` (via `_summary_payload`) | permissive | N | medium | | `summarize_xarray_dataset_readiness` | `src/pdelie/reporting/summaries.py:1155` | `_validate_strict_json_compatible` (line 1311) | strict | Y (embeds `field_batch_readiness` via internal `summarize_field_batch_readiness` call) | n/a — already strict | | `summarize_residual_batch` | `src/pdelie/reporting/summaries.py:1314` | `_validate_json_compatible` (via `_summary_payload`) | permissive | N | low | | `summarize_weak_residual_report` | `src/pdelie/reporting/summaries.py:1341` | `_validate_json_compatible` (via `_summary_payload`) | permissive | N | low | | `summarize_weak_form_supportability` | `src/pdelie/reporting/summaries.py:1845` | `_validate_strict_json_compatible` (line 1954) | strict | Y (accepts pre-summarized `weak_summary` and `strong_summary` inputs) | n/a — already strict | | `summarize_generator_fit_diagnostics` | `src/pdelie/reporting/summaries.py:2003` | `_validate_json_compatible` (via `_summary_payload`) | permissive | N | low | | `summarize_generator_family` | `src/pdelie/reporting/summaries.py:2042` | `_validate_json_compatible` (via `_summary_payload`) | permissive | N | low | | `summarize_formula_generator_family` | `src/pdelie/reporting/summaries.py:2097` | `_validate_json_compatible` (via `_summary_payload`) | permissive | N | low | | `summarize_verification_report` | `src/pdelie/reporting/summaries.py:2145` | `_validate_json_compatible` (via `_summary_payload`) | permissive | N | low | | `summarize_vertical_slice` | `src/pdelie/reporting/summaries.py:2165` | `_validate_json_compatible` (via `_summary_payload`) | permissive | Y (embeds `summarize_residual_batch`, `summarize_generator_family`, `summarize_verification_report`) | medium | | `summarize_generator_confidence` | `src/pdelie/reporting/summaries.py:2193` | `_validate_json_compatible` (via `_summary_payload`) | permissive | Y (embeds `summarize_generator_fit_diagnostics` and accepts pre-summarized residual, generator, verification, candidate-validation, coverage, consistency, and orbit inputs) | high — v0.32b | | `summarize_invariant_workflow` | `src/pdelie/reporting/summaries.py:2279` | `_validate_json_compatible` (via `_summary_payload`) | permissive | Y (embeds `summarize_generator_fit_diagnostics`; accepts orbit, coverage, consistency, generator, verification, fit-diagnostics reports) | medium | | `summarize_split_leakage_provenance` | `src/pdelie/reporting/summaries.py:2632` | `_validate_json_compatible` (via `_summary_payload`) at the outer wrap; `_validate_strict_json_compatible` on `orbit_batch`, `source_report_id`, and `extra_metrics` sub-fields (lines 2650, 2664, 2668) | permissive at outer wrap | Y (embeds `orbit_batch_summary`) | medium | | `summarize_downstream_discovery_workflow` | `src/pdelie/reporting/summaries.py:2807` | `_validate_json_compatible` (via `_summary_payload`) | permissive | Y (embeds `field_batch_readiness`, `generator_confidence`, orbit-batch, discovery-inputs, discovery-result, and split-provenance reports) | high — v0.31b1 | ## Priority guidance `summarize_generator_confidence` is the high-priority target for v0.32b. It is the widest-composed summary in the current inventory: it accepts pre-summarized residual, generator, fit-diagnostics, verification, candidate-validation, coverage, consistency, and orbit reports, and it is a direct input to `summarize_downstream_discovery_workflow`. Migrating it to strict validation forces upstream hardening of every embedded producer, and it is the summary that most benefits from a `NaN`-free contract because the confidence label at line 2263 is a decision-grade output consumed by downstream discovery flows. `summarize_vertical_slice` and `summarize_invariant_workflow` are medium priority. Both are composed summaries whose embedded leaf producers (`summarize_residual_batch`, `summarize_generator_family`, `summarize_verification_report`, `summarize_generator_fit_diagnostics`) are permissive today. Migration of the composed wrap must follow migration of at least the embedded leaves it depends on; sequencing this before the leaves are hardened would flip existing passing tests to failures without adding safety. `summarize_split_leakage_provenance` sits in the same medium band — it already validates its `orbit_batch`, `source_report_id`, and `extra_metrics` sub-fields under the strict helper (lines 2650, 2664, 2668), but the outer payload wrap is still permissive; tightening that outer wrap is a small change once the inputs are known clean. `summarize_field_batch_readiness` is medium because it is embedded downstream (by `summarize_xarray_dataset_readiness` at line 1260 and by `summarize_downstream_discovery_workflow`), so its migration unblocks two composed summaries at once. The leaf permissive summaries — `summarize_residual_batch`, `summarize_weak_residual_report`, `summarize_generator_fit_diagnostics`, `summarize_generator_family`, `summarize_formula_generator_family`, `summarize_verification_report` — are low priority. Each is a self-contained wrap over a single dataclass; they can be migrated independently on a rolling basis in follow-up PRs, and the required change per file is small (open-code a `_validate_strict_json_compatible` call in place of `_summary_payload`, or introduce a strict variant of `_summary_payload` and switch the call site). Ordering among the leaves is not load-bearing. ## Cross-references - `docs/planning/PDL_JSON_2_STRICT_JSON_MIGRATION.md` — companion migration-plan ticket. Sketches the per-summary migration steps, the shape of a `_summary_payload_strict` helper (or a `strict=True` parameter on `_summary_payload`), the test-hardening steps required at each embedded producer, and the release-sequencing that avoids composed-summary migration ahead of its leaves. - `docs/specs/LABEL_REGISTRY.md` — label-vocabulary context. Several of the summaries in the inventory table emit categorical labels (`readiness_label`, `confidence_label`, `supportability_label`, `workflow_label`, `risk_label`); the label-registry constraints interact with strict validation because unavailable-status sentinels must be strings, not `NaN`. - `docs/design/DISCOVERY_TASK_RESULT_SCHEMA.md` — v0.31b's first strict-migration-of-composed-summary target. Documents the shape of the `discovery_task_result` payload embedded inside `summarize_downstream_discovery_workflow` (line 2807), which is the high-priority v0.31b1 migration and the first composed summary in this inventory to move to the strict contract.