affaan-m/ECC · error · ContractError

genre numbers must be distinct

Error message

genre numbers must be distinct

What it means

validate_genre_specs requires each genre spec to have a unique 'number'. Duplicate numbers would make downstream per-genre outputs ambiguous (two specs targeting the same numbered slot), so duplicates are rejected.

Solutions

  1. Renumber the specs so each has a distinct 'number': 1, 2, 3, ...
  2. Deduplicate specs that are true copies rather than renumbering them
  3. Renumber programmatically: [dict(s, number=i+1) for i, s in enumerate(specs)] before validation
  4. Fix the merging logic that produced duplicate numbers

Example fix

# before
specs = [{"number": 1, ...}, {"number": 1, ...}]
# after
specs = [{"number": 1, ...}, {"number": 2, ...}]
Defensive patterns

Strategy: validation

Validate before calling

numbers = [s.get("number") for s in specs]
if len(numbers) != len(set(numbers)):
    raise SystemExit(f'duplicate genre numbers: {[n for n in numbers if numbers.count(n) > 1]}')

Type guard

def has_unique_numbers(specs) -> bool:
    numbers = [s.get("number") for s in specs]
    return len(numbers) == len(set(numbers))

Try / catch

try:
    validate_bundle(root)
except ContractError as e:
    if 'distinct' in str(e) and 'numbers' in str(e):
        print('Renumber genre specs so each number is unique')
    else:
        raise

Prevention

When it happens

Trigger: Two or more genre specs share the same 'number' value — e.g. copy-pasting a spec block and forgetting to bump its number, or programmatically appending specs that default to the same number.

Common situations: Editing a multi-genre config by duplicating an existing entry, template configs where numbers were placeholders (all 1), or merging two spec files without renumbering.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/b443f8249de9c755. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:123

    while pending:
        directory = pending.pop()
        with os.scandir(directory) as entries:
            for entry in entries:
                if entry.is_symlink():
                    raise ContractError(f"output bundle contains a symlink: {entry.path}")
                if entry.is_dir(follow_symlinks=False):
                    pending.append(Path(entry.path))
                elif not entry.is_file(follow_symlinks=False):
                    raise ContractError(f"output bundle contains a special file: {entry.path}")


def validate_genre_specs(specs: list[dict[str, Any]]) -> None:
    """Require complete, semantically distinct numbered genre specs."""
    if not specs:
        raise ContractError("at least one genre spec is required")
    numbers = [spec.get("number") for spec in specs]
    if len(numbers) != len(set(numbers)):
        raise ContractError("genre numbers must be distinct")

    fingerprints = [spec.get("style_fingerprint") for spec in specs]
    signatures = [_semantic_signature(spec) for spec in specs]
    if len(fingerprints) != len(set(fingerprints)) or len(signatures) != len(set(signatures)):
        raise ContractError("genre references collapsed into a generic style; distinct specs required")

    for spec in specs:
        if spec.get("dry_run") is not True:
            raise ContractError(f"genre {spec.get('number')} crosses the dry-run boundary")
        measured = spec.get("measured_features")
        if measured is not None:
            if not isinstance(measured, dict):
                raise ContractError(f"genre {spec.get('number')} has invalid measured evidence")
            _validate_numeric_evidence(measured, label=f"genre {spec.get('number')} evidence")
            total_duration = measured.get("total_duration")
            if not _is_finite_real(total_duration):
                raise ContractError(f"genre {spec.get('number')} has invalid total duration")
            total_duration = cast(float, total_duration)

View on GitHub (pinned to 8321021c54)