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
- Add a complete placement dict to every event: {"safe_area": ..., "max_coverage": ..., "occlusion_policy": ...}.
- Copy a known-good placement block from an existing valid recipe and adjust values.
- Extend your generator so it always emits all three placement keys with sensible defaults.
- 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
- Give every event a placement dict via a defaulting constructor
- Use snake_case keys exactly: safe_area, max_coverage, occlusion_policy
- Snapshot-test generated events for complete placement blocks
- Reject partially filled placement dicts at authoring time
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
- effect recipe must explicitly declare periodic=false
- anchor evidence source duration is not bound to its receipt…
- Canonical session snapshot requires session.sourceTarget to…
- Canonical session snapshot requires session to be an object
- Canonical session snapshot requires workers to be an array
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)