{"record":{"id":"a6e40ec773a25e0d","repo":"affaan-m/ECC","slug":"genre-spec-get-number-crosses-the-dry-run-boundary","errorCode":null,"errorMessage":"genre {spec.get('number')} crosses the dry-run boundary","messagePattern":"genre (.+?) crosses the dry-run boundary","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":132,"sourceCode":"                    raise ContractError(f\"output bundle contains a special file: {entry.path}\")\n\n\ndef validate_genre_specs(specs: list[dict[str, Any]]) -> None:\n    \"\"\"Require complete, semantically distinct numbered genre specs.\"\"\"\n    if not specs:\n        raise ContractError(\"at least one genre spec is required\")\n    numbers = [spec.get(\"number\") for spec in specs]\n    if len(numbers) != len(set(numbers)):\n        raise ContractError(\"genre numbers must be distinct\")\n\n    fingerprints = [spec.get(\"style_fingerprint\") for spec in specs]\n    signatures = [_semantic_signature(spec) for spec in specs]\n    if len(fingerprints) != len(set(fingerprints)) or len(signatures) != len(set(signatures)):\n        raise ContractError(\"genre references collapsed into a generic style; distinct specs required\")\n\n    for spec in specs:\n        if spec.get(\"dry_run\") is not True:\n            raise ContractError(f\"genre {spec.get('number')} crosses the dry-run boundary\")\n        measured = spec.get(\"measured_features\")\n        if measured is not None:\n            if not isinstance(measured, dict):\n                raise ContractError(f\"genre {spec.get('number')} has invalid measured evidence\")\n            _validate_numeric_evidence(measured, label=f\"genre {spec.get('number')} evidence\")\n            total_duration = measured.get(\"total_duration\")\n            if not _is_finite_real(total_duration):\n                raise ContractError(f\"genre {spec.get('number')} has invalid total duration\")\n            total_duration = cast(float, total_duration)\n            if float(total_duration) <= 0:\n                raise ContractError(f\"genre {spec.get('number')} has invalid total duration\")\n            for group_name in (\"sample_times\",):\n                groups = measured.get(group_name, [])\n                if not isinstance(groups, list):\n                    raise ContractError(f\"genre {spec.get('number')} has invalid time evidence\")\n                for group in groups:\n                    if not isinstance(group, dict):\n                        raise ContractError(f\"genre {spec.get('number')} has invalid time evidence\")","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L114-L150","documentation":"Every genre spec must set dry_run to exactly True (identity check with `is not True`, not just truthy). This is a dry-run boundary: the validator refuses specs that have not explicitly opted into non-destructive measurement mode, so accidental live generation can never pass contract validation. Strings like \"true\" or 1 do not satisfy the check.","triggerScenarios":"Passing a spec with dry_run missing, set to False, set to 1, or set to the string \"true\" into validate_genre_specs (directly or through validate_bundle).","commonSituations":"Hand-authored YAML/JSON configs where dry_run is written as \"true\" (a string); deserialized configs where JSON has no native bool and the value arrives as 1/0; templates that omit the field entirely; older bundle formats predating the dry-run requirement.","solutions":["Set dry_run: true (a real boolean) on every genre spec in the bundle.","Coerce loaded config values explicitly: spec[\"dry_run\"] = spec.get(\"dry_run\") is True or bool(...) with strict parsing.","Check your config parser/loader for values arriving as strings or ints and normalize them at load time.","Search the bundle for any specs missing the dry_run key and add it."],"exampleFix":"// before\n{\"number\": 3, \"dry_run\": \"true\", ...}\n// after\n{\"number\": 3, \"dry_run\": true, ...}","handlingStrategy":"validation","validationCode":"def ensure_dry_run(specs):\n    for s in specs:\n        if s.get(\"dry_run\") is not True:\n            raise ValueError(f\"spec {s.get('number')} must set dry_run: true (boolean)\")","typeGuard":"def is_explicit_dry_run(spec: dict) -> bool:\n    return spec.get(\"dry_run\") is True","tryCatchPattern":"try:\n    validate_genre_specs(specs)\nexcept ContractError as e:\n    if \"crosses the dry-run boundary\" in str(e):\n        for s in specs:\n            s[\"dry_run\"] = (s.get(\"dry_run\") is True)\n    raise","preventionTips":["Always write dry_run: true as a boolean in configs, never \"true\" or 1.","Normalize boolean config fields at load time with strict bool parsing.","Include dry_run in the spec template so it is never omitted.","Reject non-bool dry_run values at config-load time."],"tags":["validation","dry-run","boolean-flag"],"backgroundTag":"invalid-flag-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}