| 1 | manifest_yaml_parses | static | File parses as a YAML mapping (not null, not a sequence at top level) | re-emit valid YAML — likely truncated mid-write or hand-edit broke quoting |
| 2 | required_keys_present | static | Top-level required keys present: opportunity_id, opportunity_name, random_seed, timeline, flw_personas, beneficiary_cohorts, kpi_config | re-emit the manifest with the missing top-level keys (default values OK; eval grades quality separately) |
| 3 | flw_personas_well_formed | static | flw_personas is a non-empty array; each item has id (REQUIRED, snake_case per upstream) + archetype; archetype value is in {rockstar, steady, struggling, new_hire} | regenerate flw_personas with required fields + valid archetypes; default mix is 1 rockstar / 2 steady / 1 struggling / 1 new_hire |
| 4 | beneficiary_cohorts_well_formed | static | beneficiary_cohorts is a non-empty array; each cohort has id; when present, size is a positive int, progression is in {improvement_curve, flat, regression}, field_distributions is a mapping keyed by field path (dict[str, FieldDistribution], NOT a list — a list is rejected at the labs boundary, jjackson/ace#806), and every entry is discriminated on distribution ∈ {normal, uniform, binary}. Mirrors upstream manifest.py Progression + FieldDistribution discriminated union (jjackson/ace#713). | fix progression to a valid enum value and each field_distributions entry to use distribution: normal|uniform|binary (not a type:/categorical tag) — these are rejected at the labs boundary otherwise |
| 5 | kpi_field_paths_resolvable | static (with optional context) | If deliver-app summary is in ctx.deliver_summary, each kpi_config[].field_path appears in the summary text. If summary absent, returns INFO-style pass with detail noting the skip. | fix the field_path to reference a real form question path from the deliver app; common paths are listed in the deliver-app summary |
| 6 | anomalies_traceable | static | anomalies (when present) is an array; each anomaly has id, type (in {field_outlier, missing_visits, duplicate_submission}), flw_ids (non-empty list — plural per upstream Pydantic), a week reference (week or weeks), and one of field_path / detection_path | populate id + type + flw_ids (list, not singular flw_id) + week + detection_path per anomaly; an anomaly without detection is reviewer-invisible downstream |
| 7 | coaching_arcs_match_personas | static | Every coaching_arcs[].flw_id is in flw_personas[].id. Empty coaching_arcs passes trivially. | fix the flw_id to match a persona id, or add the persona |
| 8 | random_seed_present | static | random_seed is a non-negative integer (upstream allows 0; deterministic generation requirement) | set random_seed: to today's date as YYYYMMDD |
| 9 | timeline_dates_consistent | static | timeline.start_date < timeline.end_date (ISO date strings) and timeline.weeks ≥ 1 | fix the timeline so start_date precedes end_date and weeks ≥ 1 |