{
  "edges": {
    "PART_OF": {
      "direction": "(Message)-[:PART_OF]->(Conversation)",
      "note": "Messages belong to exactly one Conversation. Do NOT use :HAS_MESSAGE — that edge does not exist."
    },
    "NEXT": {
      "direction": "(Message)-[:NEXT]->(Message)",
      "note": "Chain order within a Conversation. Each Message has at most one NEXT successor."
    },
    "ADMIN_OF": {
      "direction": "(AdminUser)-[:ADMIN_OF]->(LocalBusiness)",
      "note": "Device-level admin membership (see admin-add / admin-remove)."
    },
    "HAS_PROFILE": {
      "direction": "(AdminUser)-[:HAS_PROFILE]->(UserProfile)",
      "note": "Admin's profile node. Account scope lives on the `accountId` property of each node — do NOT add a redundant edge to LocalBusiness just to satisfy the write doctrine. Every MATCH filters by `{accountId: $accountId}`."
    },
    "AUTHORED_BY": {
      "direction": "(Message)-[:AUTHORED_BY]->(Person)",
      "note": "Message authorship (when the author is an identified Person)."
    },
    "HAS_TOOL_CALL": {
      "direction": "(Message)-[:HAS_TOOL_CALL]->(ToolCall)",
      "note": "Tool calls attached to an assistant message."
    },
    "HAS_RESULT": {
      "direction": "(WorkflowStep|ToolCall)-[:HAS_RESULT]->(StepResult)",
      "note": "Workflow step / tool-call outputs."
    },
    "HAS_STEP": {
      "direction": "(Workflow)-[:HAS_STEP]->(WorkflowStep)",
      "note": "Workflow composition."
    },
    "RUN_OF": {
      "direction": "(WorkflowRun)-[:RUN_OF]->(Workflow)",
      "note": "Workflow execution instance."
    },
    "HAS_SECTION": {
      "direction": "(KnowledgeDocument)-[:HAS_SECTION]->(Section)",
      "note": "Document hierarchy."
    },
    "HAS_CHUNK": {
      "direction": "(Section)-[:HAS_CHUNK]->(Chunk)",
      "note": "Section-to-embeddable-chunk."
    },
    "HAS_PREFERENCE": {
      "direction": "(Person)-[:HAS_PREFERENCE]->(Preference)",
      "note": "Person-scoped preferences (profile memory)."
    },
    "HAS_ACCESS": {
      "direction": "(Person)-[:HAS_ACCESS]->(AccessGrant)",
      "note": "Public agent access grants."
    },
    "HAS_TASK": {
      "direction": "(Person|LocalBusiness)-[:HAS_TASK]->(Task)",
      "note": "Task ownership / assignment."
    },
    "HAS_PRICING": {
      "direction": "(Service)-[:HAS_PRICING]->(PriceSpecification)",
      "note": "Service pricing."
    },
    "HAS_HOURS": {
      "direction": "(LocalBusiness)-[:HAS_HOURS]->(OpeningHoursSpecification)",
      "note": "Opening hours."
    },
    "HAS_FAQ": {
      "direction": "(LocalBusiness)-[:HAS_FAQ]->(Question)",
      "note": "FAQ attached to business."
    },
    "HAS_BRAND_ASSET": {
      "direction": "(LocalBusiness)-[:HAS_BRAND_ASSET]->(DigitalDocument|ImageObject)",
      "note": "Branding assets."
    },
    "HAS_ENCLOSURE": {
      "direction": "(ConversationArchive|KnowledgeDocument)-[:HAS_ENCLOSURE]->(DigitalDocument|KnowledgeDocument|ImageObject)",
      "note": "File enclosure on a thread or document parent. Task 402: email attachments land as `:DigitalDocument` keyed on `attachmentId = sha256(bytes)`, attached from the parent `:ConversationArchive {source:'email'}`. Same bytes from two threads → one `:DigitalDocument`, two `:HAS_ENCLOSURE` edges. Substack/X-import enclose images from their parent `:KnowledgeDocument` post / `:ConversationArchive` DM under the same edge."
    },
    "OFFERS": {
      "direction": "(LocalBusiness)-[:OFFERS]->(Service)",
      "note": "Service catalogue."
    },
    "CONTAINS": {
      "direction": "(KnowledgeDocument)-[:CONTAINS]->(Chunk)",
      "note": "Flat document-to-chunk (alternative to HAS_SECTION then HAS_CHUNK)."
    },
    "REFERENCES": {
      "direction": "(Message|KnowledgeDocument|Task)-[:REFERENCES]->(*)",
      "note": "Soft reference link. `Task` is a valid source: derived-insight tasks created from a `:ConversationArchive` `:Section` chunk (Task 397) record their provenance via (:Task)-[:REFERENCES]->(:Section) with a `contentHash` merge-key for idempotent re-runs."
    },
    "ABOUT": {
      "direction": "(Review|Message)-[:ABOUT]->(*)",
      "note": "Subject pointer."
    },
    "REPLY_TO": {
      "direction": "(Email)-[:REPLY_TO]->(Email)",
      "note": "Email threading."
    },
    "RECEIVED_BY": {
      "direction": "(Email)-[:RECEIVED_BY]->(Person)",
      "note": "Email recipient."
    },
    "BLOCKS": {
      "direction": "(Task)-[:BLOCKS]->(Task)",
      "note": "Task dependency."
    },
    "OBSERVED_IN": {
      "direction": "(*)-[:OBSERVED_IN]->(Conversation)",
      "note": "Observation provenance. `:ConversationArchive` `:Section` chunks (Task 397) are valid OBSERVED_IN targets, so (:Preference)-[:OBSERVED_IN]->(:Section) pattern-matches this annotation."
    },
    "MENTIONS": {
      "direction": "(Section|Message|KnowledgeDocument|ConversationArchive)-[:MENTIONS]->(Person|Organization)",
      "note": "Named entity reference. `Section` (typically a transcript chunk under a `:ConversationArchive` parent — Task 397) is a valid source so chunk-anchored insight derivation can record who a transcript chunk mentions. KnowledgeDocument-source MENTIONS is the document-ingest path; ConversationArchive-source MENTIONS is the conversation-archive path; Message-source MENTIONS is reserved for future per-message extraction."
    },
    "RELATED_TO": {
      "direction": "(Person|Organization)-[:RELATED_TO]->(Person|Organization)",
      "note": "Operator-confirmed relationship between two named entities derived from a transcript chunk. Carries `operatorConfirmed: true` plus `relationshipType` naming the specific bond (`broker`, `colleague`, `referrer`, …). Distinct from typed edges like AUTHORED_BY or PARTICIPANT — RELATED_TO is the generic surface for relationships the operator confirmed at enrich time."
    },
    "HAS_IDENTITY": {
      "direction": "(Agent)-[:HAS_IDENTITY]->(KnowledgeDocument)",
      "note": "public-agent IDENTITY.md projection (KnowledgeDocument with role='identity', namespaced attachmentId='agent:<slug>:identity')."
    },
    "HAS_SOUL": {
      "direction": "(Agent)-[:HAS_SOUL]->(KnowledgeDocument)",
      "note": "public-agent SOUL.md projection (role='soul')."
    },
    "HAS_KNOWLEDGE": {
      "direction": "(Agent)-[:HAS_KNOWLEDGE]->(KnowledgeDocument)",
      "note": "public-agent KNOWLEDGE.md projection (role='knowledge'); KNOWLEDGE-SUMMARY.md uses the same edge with role='knowledge-summary'."
    },
    "USES_KNOWLEDGE": {
      "direction": "(Agent)-[:USES_KNOWLEDGE]->(KnowledgeDocument)",
      "note": "operator-tagged docs (slug ∈ k.agents). Materialised at projection time only; runtime memory-search reads k.agents directly."
    },
    "HANDLED_BY": {
      "direction": "(Conversation)-[:HANDLED_BY]->(Agent)",
      "note": "public conversations only. Written by ensureConversation via OPTIONAL MATCH so orphan slugs do not block conversation creation."
    }
  },
  "sublabels": {
    "AdminConversation": {
      "base": "Conversation",
      "note": "Admin-only conversation (admin chat, not public)."
    },
    "PublicConversation": {
      "base": "Conversation",
      "note": "Public agent conversation (visitor-facing)."
    },
    "UserMessage": {
      "base": "Message",
      "note": "Message authored by the human user."
    },
    "AssistantMessage": {
      "base": "Message",
      "note": "Message authored by the assistant."
    }
  }
}
