affaan-m/ECC · error · ContractError

artifact lacks media reference times

Error message

artifact {relative} lacks media reference times

What it means

When a provenance source declares time_basis 'media_seconds', its reference_times list must be non-empty — citing specific media timestamps is the whole point of that basis. This error is raised when the times list is empty or missing for a media_seconds source.

Solutions

  1. Populate reference_times with the actual media timestamps (in seconds) the artifact derives from
  2. If the artifact uses the entire reference, change time_basis to 'whole_file' and keep times empty instead
  3. Regenerate the artifact through the pipeline so times are captured automatically
  4. Validate each timestamp is within the source duration to also avoid downstream media-time errors

Example fix

# before
{'time_basis': 'media_seconds', 'reference_times': []}
# after
{'time_basis': 'media_seconds', 'reference_times': [12.5, 30.0]}
Defensive patterns

Strategy: validation

Validate before calling

if src.get('time_basis') == 'media_seconds':
    times = src.get('reference_times')
    assert isinstance(times, list) and len(times) > 0, 'media_seconds requires non-empty reference_times'

Type guard

def has_media_times(src: dict) -> bool:
    return src.get('time_basis') != 'media_seconds' or bool(src.get('reference_times'))

Try / catch

try:
    validate_artifact_receipt(out_dir)
except ContractError as e:
    if 'lacks media reference times' in str(e):
        capture_timestamps_or_switch_to_whole_file(src)
    else:
        raise

Prevention

When it happens

Trigger: A source with time_basis='media_seconds' and reference_times=[] (or the key omitted); switching a whole_file source to media_seconds without adding timestamps.

Common situations: Template receipts copied from a whole_file example, generators that fail to capture timestamps for time-ranged edits, developers switching basis values without updating the times list.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

        if entry.get("sha256") != _sha256(path):
            raise ContractError(f"artifact {relative} SHA-256 does not match receipt")
        provenance = entry.get("provenance")
        if not isinstance(provenance, list) or not provenance:
            raise ContractError(f"artifact {relative} lacks exact reference/time provenance")
        for source in provenance:
            if not isinstance(source.get("reference_path"), str) or not source["reference_path"]:
                raise ContractError(f"artifact {relative} has invalid reference path")
            digest = source.get("reference_sha256")
            if not isinstance(digest, str) or len(digest) != 64:
                raise ContractError(f"artifact {relative} has invalid reference SHA-256")
            if (source["reference_path"], digest) not in known_sources:
                raise ContractError(f"artifact {relative} cites an unknown provenance source")
            times = source.get("reference_times")
            basis = source.get("time_basis")
            if not isinstance(times, list) or basis not in {"media_seconds", "whole_file"}:
                raise ContractError(f"artifact {relative} has invalid reference/time provenance")
            if basis == "media_seconds" and not times:
                raise ContractError(f"artifact {relative} lacks media reference times")
            if basis == "whole_file" and times:
                raise ContractError(f"artifact {relative} whole-file provenance must not invent times")
            if basis == "media_seconds":
                expected_duration = source_durations.get((source["reference_path"], digest))
                if expected_duration is None or source.get("source_duration") != expected_duration:
                    raise ContractError(f"artifact {relative} has an unbound source duration")
                for time in times:
                    _validate_media_time(
                        time, expected_duration,
                        label=f"artifact {relative} media reference time",
                    )

    digest_payload = dict(receipt)
    claimed_digest = digest_payload.pop("receipt_sha256", None)
    actual_digest = hashlib.sha256(
        json.dumps(digest_payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
    ).hexdigest()
    if claimed_digest != actual_digest:

View on GitHub (pinned to 8321021c54)