affaan-m/ECC · error · ContractError

artifact whole-file provenance must not invent times

Error message

artifact {relative} whole-file provenance must not invent times

What it means

A whole_file provenance source means the artifact derives from the entire reference, so no per-timestamp references may be invented. This error is raised when time_basis is 'whole_file' but reference_times is a non-empty list. It prevents receipts from claiming both full-file coverage and specific time ranges.

Solutions

  1. Set reference_times to [] for whole_file provenance
  2. If specific times are actually needed, change time_basis to 'media_seconds' and keep the times list
  3. Regenerate the receipt via tasteforge so basis and times stay consistent
  4. Remove auto-injected default timestamps from receipt post-processing scripts

Example fix

# before
{'time_basis': 'whole_file', 'reference_times': [0.0, 45.0]}
# after
{'time_basis': 'whole_file', 'reference_times': []}
Defensive patterns

Strategy: validation

Validate before calling

if src.get('time_basis') == 'whole_file':
    assert src.get('reference_times') == [], 'whole_file provenance must have empty reference_times'

Type guard

def whole_file_times_clean(src: dict) -> bool:
    return src.get('time_basis') != 'whole_file' or not src.get('reference_times')

Try / catch

try:
    validate_artifact_receipt(out_dir)
except ContractError as e:
    if 'must not invent times' in str(e):
        clear_times_for_whole_file_sources(receipt)
    else:
        raise

Prevention

When it happens

Trigger: Setting time_basis='whole_file' while leaving reference_times populated from a previous media_seconds configuration, or generators that always emit default timestamps regardless of basis.

Common situations: Switching a receipt entry's basis without clearing times, copy-pasted template provenance, tools that auto-annotate times for all sources.

Related errors


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

Appendix: source

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

        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:
        raise ContractError("receipt SHA-256 does not match its canonical content")

View on GitHub (pinned to 8321021c54)