#1830·graphiti

Possible regression: registered edge types retain names despite disallowed endpoint signatures after #1191

Author: meetwudiCreated Sep 3, 2026Updated Sep 17, 2026

Question / suspected regression

Should a registered custom relationship type still be checked against edge_type_map before persistence, or is that mapping now intentionally only an extraction/attribute-schema hint?

At commit 9debb86a5523a3d702252090d39751f04a9b830d (0.30.1), a registered MAKES relationship with Product -> Company endpoints survives resolve_extracted_edges even when the only allowed signature is Company -> Product. This is not a request to prohibit newly discovered, unregistered relationship names.

History

  • #948 added runtime signature validation.
  • #950 explicitly preserved discovered labels while resetting disallowed registered types to RELATES_TO.
  • #1191 moved classification into extraction. Its diff also removes the registered-type validation loop and fallback logic (c36723c71337c0c12788f0d558bb93dd015af476).
  • Current code calculates the permitted custom types for the endpoint labels, but a non-matching name survives; the candidates only control custom attribute extraction.

I understand avoiding a second LLM classification. Is removing the deterministic signature check also intended? The custom-types documentation still describes endpoint constraints and generic relationships for unmatched pairs. Related broader question: #1789.

Offline reproduction

This invokes the real resolution function with synthetic nodes and an intentionally reversed extraction. Database/search/embedding calls are mocked; no LLM call, credentials, or database are needed. It is a function-level reproduction, not an end-to-end model benchmark.

python
import asyncio
from datetime import datetime, timezone
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch

from pydantic import BaseModel
from graphiti_core.edges import EntityEdge
from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode
from graphiti_core.search.search_config import SearchResults
from graphiti_core.utils.maintenance import edge_operations as ops


class Makes(BaseModel):
    """A Company makes a Product."""


async def main():
    company = EntityNode(name="ExampleCo", group_id="repro", labels=["Company"])
    product = EntityNode(name="ExampleApp", group_id="repro", labels=["Product"])
    episode = EpisodicNode(
        name="example", group_id="repro", source=EpisodeType.text,
        source_description="synthetic reproduction", content="ExampleCo makes ExampleApp.",
        valid_at=datetime.now(timezone.utc),
    )
    edge = EntityEdge(
        source_node_uuid=product.uuid, target_node_uuid=company.uuid,
        name="MAKES", fact="ExampleApp makes ExampleCo.", group_id="repro",
        valid_at=episode.valid_at, created_at=episode.valid_at,
    )
    clients = SimpleNamespace(driver=object(), embedder=object(), llm_client=object())
    with (
        patch.object(ops, "create_entity_edge_embeddings", AsyncMock()),
        patch.object(EntityEdge, "get_between_nodes", AsyncMock(return_value=[])),
        patch.object(ops, "search", AsyncMock(return_value=SearchResults())),
    ):
        resolved, _, _ = await ops.resolve_extracted_edges(
            clients, [edge], episode, [company, product],
            {"MAKES": Makes}, {("Company", "Product"): ["MAKES"]},
        )
    print([(item.name, item.source_node_uuid == product.uuid,
            item.target_node_uuid == company.uuid) for item in resolved])


asyncio.run(main())

Observed (Python 3.10): [('MAKES', True, True)].

Expected under the policy in #950: this edge must not retain the registered MAKES name with disallowed endpoints; the historical behavior was generic RELATES_TO, not dropping the fact or failing ingestion.

If the current behavior is intentional, is there a supported strict-validation hook or option we should use? If it is a regression, would restoring the narrow registered-type check be appropriate?