{"record":{"id":"d008606f43857645","repo":"cocoindex-io/cocoindex","slug":"resolve-pair-returned-matched-decision-matched-r","errorCode":null,"errorMessage":"resolve_pair returned matched={decision.matched!r}, which is not in candidates={candidates!r}. This is a contract violation (see requirement.md).","messagePattern":"resolve_pair returned matched=(.+?), which is not in candidates=(.+?)\\. This is a contract violation \\(see requirement\\.md\\)\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/ops/entity_resolution/__init__.py","lineNumber":282,"sourceCode":"def _chain_walk(dedup: dict[str, str | None], name: str) -> str:\n    current = name\n    while True:\n        target = dedup.get(current)\n        if target is None:\n            return current\n        current = target\n\n\ndef _validate_pair_decision(\n    *,\n    entity: str,\n    candidates: list[str],\n    decision: PairDecision,\n) -> None:\n    if decision.matched is not None and (\n        decision.matched not in candidates or decision.matched == entity\n    ):\n        raise ValueError(\n            f\"resolve_pair returned matched={decision.matched!r}, \"\n            f\"which is not in candidates={candidates!r}. This is a \"\n            f\"contract violation (see requirement.md).\"\n        )\n\n\ndef _apply_pair_decision(\n    *,\n    info: _EntityInfo,\n    decision: PairDecision,\n    entity_map: dict[str, _EntityInfo],\n    dedup: dict[str, str | None],\n    existing_policy: ExistingCanonicalPolicy,\n) -> _DecisionApplication:\n    if decision.matched is None:\n        dedup[info.name] = None\n        return _DecisionApplication(canonical=info.name)\n","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/ops/entity_resolution/__init__.py#L264-L300","documentation":"This error is raised when a user-supplied `resolve_pair` callback for entity resolution returns a `PairDecision` whose `matched` value is not one of the candidate entity names (or equals the entity itself). It enforces the library contract documented in requirement.md: the callback may only merge an entity into one of the candidates it was offered. It is an internal contract-validation guard, not a library bug.","triggerScenarios":"A custom `resolve_pair` function returns a `PairDecision` whose `matched` field is set to a string that is not in the `candidates` list passed to it, or sets `matched` equal to the entity being resolved (self-match), instead of `None` for no match.","commonSituations":"Hand-rolling a resolve_pair that fuzzy-matches against an external dictionary or LLM and returns the matched term verbatim from the dictionary rather than the candidate string; typos in candidate names; case-normalizing candidates but returning the original-cased external value; confusing 'matched' semantics and echoing the entity itself.","solutions":["In your resolve_pair, only return matched values taken directly from the `candidates` argument (exact string identity), or None for no match.","If matching an entity to a normalized/external form, map it back to the corresponding candidate string before constructing PairDecision.","Never set matched to the entity itself; use matched=None when no candidate matches."],"exampleFix":"// before\ndef resolve_pair(entity, candidates):\n    best = fuzzy_lookup(entity)  # returns an external canonical name\n    return PairDecision(matched=best)\n// after\ndef resolve_pair(entity, candidates):\n    best = fuzzy_lookup(entity)\n    if best is None or best not in candidates or best == entity:\n        return PairDecision(matched=None)\n    return PairDecision(matched=best)","handlingStrategy":"validation","validationCode":"def make_decision(entity, candidates, key):\n    m = key(entity, candidates)\n    assert m is None or (m in candidates and m != entity), f\"matched={m!r} invalid for candidates={candidates!r}\"\n    return PairDecision(matched=m)","typeGuard":"def is_valid_matched(m, candidates):\n    return m is None or (isinstance(m, str) and m in candidates)","tryCatchPattern":"try:\n    handle = await coco.use_mount(resolve_component, ...)\nexcept ValueError as e:\n    if 'contract violation' in str(e):\n        logging.error('resolve_pair returned an invalid matched value: %s', e)\n    else:\n        raise","preventionTips":["Return matched values only by identity from the candidates list, never from an external dictionary.","Return None (not the entity itself) when no candidate matches.","Unit-test resolve_pair against synthetic candidate lists, asserting the matched-in-candidates invariant."],"tags":["python","contract-violation","entity-resolution","callback"],"backgroundTag":"invalid-argument-value","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}