$ cat node-template.py

O

Ontology Create Link

// Link two ontology objects with a typed edge. Returns the link id and whether the server refused to downgrade an existing higher-confidence link.

Process
Data
#ontology#knowledge-graph#create#link#write
template.py
1"""ontology-create-link — link two ontology objects with a typed edge.23Creates a ``linkTypeId`` edge from ``sourceObjectId`` to ``targetObjectId`` via4the gais.ontology SDK (``Gais.ontology.create_link``). Both object ids are5UUID-validated here (defense in depth) so ids wired from upstream nodes can't6rewrite the request route. If the server refuses to downgrade an existing7higher-confidence link, ``downgrade_refused`` is True and the id of the8pre-existing link is returned. Privileged IO carries the per-execution9USER_TOKEN inside the SDK — this node imports no HTTP client.10"""1112from __future__ import annotations1314import json15import re16import sys17import traceback18from typing import Any1920from gais import Gais2122_UUID_RE = re.compile(23    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",24    re.IGNORECASE,25)262728def _normalize_uuid(value: Any, field: str) -> str:29    """Accept a bare UUID or a ``mention://.../<uuid>`` URI; validate as UUID."""30    if not isinstance(value, str):31        raise ValueError(f"{field} must be a string, got {type(value).__name__}")32    s = value.strip()33    if "://" in s:34        s = s.rsplit("/", 1)[-1]35    if not _UUID_RE.match(s):36        raise ValueError(f"{field} is not a valid UUID: {value!r}")37    return s383940def _parse_obj(value: Any, field: str) -> dict:41    """Accept a mapping or a JSON-object string; reject anything else."""42    if value in (None, ""):43        return {}44    if isinstance(value, dict):45        return value46    if isinstance(value, str):47        parsed = json.loads(value)48        if not isinstance(parsed, dict):49            raise ValueError(f"{field} must be a JSON object")50        return parsed51    raise ValueError(f"{field} must be a JSON object")525354def _parse_confidence(value: Any) -> float | None:55    if value in (None, ""):56        return None57    return float(value)585960def process(inputs: dict[str, Any]) -> dict[str, Any]:61    source_object_id = _normalize_uuid(inputs.get("sourceObjectId"), "sourceObjectId")62    target_object_id = _normalize_uuid(inputs.get("targetObjectId"), "targetObjectId")6364    link_type_id = str(inputs.get("linkTypeId") or "").strip()65    if not link_type_id:66        raise ValueError("Required input 'linkTypeId' not provided")6768    # SDK default for properties is None (no edge props); pass None when empty.69    properties = _parse_obj(inputs.get("properties"), "properties") or None70    confidence = _parse_confidence(inputs.get("confidence"))7172    result = Gais.ontology.create_link(73        source_object_id=source_object_id,74        target_object_id=target_object_id,75        link_type_id=link_type_id,76        properties=properties,77        confidence=confidence,78    )79    if not isinstance(result, dict):80        result = {}8182    link_id = result.get("id", "")83    downgrade_refused = bool(result.get("downgrade_refused", False))8485    print(86        f"[ontology-create-link] {source_object_id} -[{link_type_id}]-> "87        f"{target_object_id} id={link_id} downgrade_refused={downgrade_refused}",88        file=sys.stderr,89    )9091    return {"id": link_id, "downgrade_refused": downgrade_refused}929394def main() -> None:95    try:96        envelope = json.loads(sys.stdin.read() or "{}")97        if not isinstance(envelope, dict):98            envelope = {}99        inputs = envelope.get("inputs", {}) or {}100        json.dump(process(inputs), sys.stdout)101    except Exception as e:102        print(103            json.dumps({104                "error": str(e),105                "errorType": type(e).__name__,106                "traceback": traceback.format_exc(),107            }),108            file=sys.stderr,109        )110        sys.exit(1)111112113if __name__ == "__main__":114    main()

$ git log --oneline

v1.0.0
HEAD
2026-08-18