affaan-m/ECC · error · ContractError

effect lacks placement constraints

Error message

effect {event.get('effect')} lacks placement constraints

What it means

ContractError (f-string) raised when an effect event's 'placement' is missing, not a dict, or lacks any of the required keys: safe_area, max_coverage, occlusion_policy. The contract requires every effect event to declare placement constraints so generated overlays respect composition and occlusion rules.

Solutions

  1. Add a complete placement dict to every event: {"safe_area": ..., "max_coverage": ..., "occlusion_policy": ...}.
  2. Copy a known-good placement block from an existing valid recipe and adjust values.
  3. Extend your generator so it always emits all three placement keys with sensible defaults.
  4. Catch ContractError and report which of the three keys is missing from the event named in the message.

Example fix

// before
{"time": 0.5, "effect": "logo_overlay"}
// after
{"time": 0.5, "effect": "logo_overlay", "placement": {"safe_area": "center_80pct", "max_coverage": 0.15, "occlusion_policy": "behind_subject"}}
Defensive patterns

Strategy: validation

Validate before calling

PLACEMENT_KEYS = {"safe_area", "max_coverage", "occlusion_policy"}
for event in recipe["events"]:
    placement = event.get("placement")
    if not isinstance(placement, dict) or not PLACEMENT_KEYS.issubset(placement):
        raise ValueError(f"{event.get('effect')}: placement constraints incomplete")

Type guard

def has_complete_placement(event) -> bool:
    required = {"safe_area", "max_coverage", "occlusion_policy"}
    return isinstance(event.get("placement"), dict) and required.issubset(event["placement"])

Try / catch

try:
    validate_effect_recipe(recipe, reference_durations)
except ContractError as exc:
    if "lacks placement constraints" in str(exc):
        event["placement"] = default_placement()
        validate_effect_recipe(recipe, reference_durations)
    else:
        raise

Prevention

When it happens

Trigger: Calling validate_effect_recipe with an event lacking the placement key entirely; placement set to null, a list, or a string; a placement dict missing one of safe_area, max_coverage, or occlusion_policy (e.g. only {"safe_area": {...}}).

Common situations: Minimal hand-authored events that skip placement; generators that emit placement only for overlay-type effects; key typos like maxCoverage or occlusionPolicy; merging event dicts that dropped the placement subtree.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

            if not isinstance(anchor, dict) or not required.issubset(anchor):
                raise ContractError(f"CV effect {event.get('effect')} lacks a valid subject anchor")
            if anchor.get("mode") not in {"object_track", "point_track", "segmentation_track"}:
                raise ContractError(f"CV effect {event.get('effect')} has an invalid subject anchor")
            if anchor.get("lost_policy") != "disable_effect_until_track_recovers":
                raise ContractError(f"CV effect {event.get('effect')} must fail closed on anchor loss")
            _validate_media_time(
                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")

View on GitHub (pinned to 8321021c54)