affaan-m/ECC · error · ContractError

provenance must contain derived rules

Error message

provenance must contain derived rules

What it means

validate_provenance enforces that a Tasteforge provenance payload declares at least one derived rule. If the 'rules' key is absent, not a list, or an empty list, this ContractError is raised to guarantee every generated artifact carries non-empty provenance rules.

Solutions

  1. Add a non-empty 'rules' list to the provenance payload, each entry with 'rule_id' and non-empty 'evidence'.
  2. Verify the producer that derives rules actually ran and wrote its output (check for upstream failures that yield zero rules).
  3. Confirm the correct provenance file is being loaded (not a stub/placeholder file).
  4. If zero rules is legitimately valid for your use case, bypass/extend the validator rather than passing an empty payload.

Example fix

// before
payload = {"rules": []}
// after
payload = {"rules": [{"rule_id": "r1", "evidence": [{"reference_sha256": "<64-hex>", "times": [0.0, 1.5], "source_duration": 10.0}]}]}
Defensive patterns

Strategy: validation

Validate before calling

def has_rules(payload):
    rules = payload.get("rules")
    return isinstance(rules, list) and len(rules) > 0
if not has_rules(payload):
    raise ValueError("provenance payload must declare a non-empty 'rules' list before validation")

Type guard

def is_provenance_payload(p: object) -> bool:
    return isinstance(p, dict) and isinstance(p.get("rules"), list) and len(p["rules"]) > 0

Try / catch

try:
    validate_provenance(payload)
except ContractError as e:
    logger.error("provenance rejected: %s", e)
    payload = regenerate_provenance()  # re-derive rules from source
    validate_provenance(payload)

Prevention

When it happens

Trigger: Calling validate_provenance(payload) or validate_bundle() with payload = {} , payload = {'rules': None}, {'rules': 'x'} (non-list), or {'rules': []} (empty list).

Common situations: Hand-authoring a provenance.json by hand and forgetting the rules array; a pipeline stage that failed to derive any rules still emitting a provenance file; serializing an empty rules list after a filter step removed all rules; renaming the 'rules' key in a producer without updating the consumer.

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/34443e1f1692e2d0. Report an issue: GitHub.

Appendix: source

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

                anchor.get("evidence_time"), anchor.get("source_duration"),
                label="anchor evidence time",
            )
            if reference_durations is not None:
                digest = anchor.get("source_ref_sha256")
                expected_duration = reference_durations.get(digest) if isinstance(digest, str) else None
                if expected_duration is None or anchor.get("source_duration") != expected_duration:
                    raise ContractError("anchor evidence source duration is not bound to its receipt reference")
    for event in events:
        placement = event.get("placement")
        if not isinstance(placement, dict) or not {"safe_area", "max_coverage", "occlusion_policy"}.issubset(placement):
            raise ContractError(f"effect {event.get('effect')} lacks placement constraints")


def validate_provenance(payload: dict[str, Any]) -> None:
    """Require every declared rule to cite immutable, timestamped evidence."""
    rules = payload.get("rules")
    if not isinstance(rules, list) or not rules:
        raise ContractError("provenance must contain derived rules")
    for rule in rules:
        evidence = rule.get("evidence")
        if not isinstance(evidence, list) or not evidence:
            raise ContractError(f"rule {rule.get('rule_id')} lacks reference evidence")
        for item in evidence:
            digest = item.get("reference_sha256")
            if not isinstance(digest, str) or len(digest) != 64:
                raise ContractError(f"rule {rule.get('rule_id')} lacks immutable reference evidence")
            times = item.get("times")
            if not isinstance(times, list) or not times:
                raise ContractError(f"rule {rule.get('rule_id')} lacks time evidence")
            source_duration = item.get("source_duration")
            for time in times:
                _validate_media_time(
                    time, source_duration, label=f"rule {rule.get('rule_id')} time evidence"
                )

View on GitHub (pinned to 8321021c54)