ontology: Ontologie als Quelle im Repo, Abgleich mit n8n/KNOW in der Service-Routine
ci / test (push) Successful in 27s

- ontology/know.yaml: 49 Klassen, 66 Beziehungen, 12 Vokabulare (72 Konzepte);
  erzeugt: know.ttl (OWL + SKOS), Ansicht aus viewer.html.tmpl
- knaster.ontology: Modell + Prüfung, Export, Momentaufnahme der n8n-Workflows
  (nur GET; KNOW-Spezifikation aus dem Workflow-Code, Aufrufe inkl. Laufzeit-
  Operationen, direktes SQL), Abgleich, Übernahme von Entscheidungen
- Service-Routine Schritt 8: Verstöße als Befunde, Ontologie-Änderungen als
  Vorschläge ontology_change (immer Freigabe); „ordne P-x <konzept> zu“;
  Entscheidungen in ontology_decisions, `knaster ontology apply` schreibt sie
  in die YAML (Version +1)
- CLI: knaster ontology validate|export|snapshot|check|apply; knaster validate
  prüft Modell und Aktualität der Turtle-Datei
- Gegen echten n8n-Export geprüft: neue Befunde B31–B33 (verified/confirmed,
  KAN ruft unbekannte Operation, MPA ordnet Projektereignisse nie ein)
- SPEC-ontology, BETRIEB §6, Router-Image enthält die Ontologie

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 02:54:49 +02:00
co-authored by Claude Opus 5.5
parent 525b29aa6d
commit 9442cc49d1
29 changed files with 3675 additions and 96 deletions
+35 -1
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import json
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
@@ -18,6 +19,7 @@ from .llm import ChatClient, OpenAIChatClient
from .maintenance.actions import ActionExecutor
from .maintenance.agent import IntentMapper, LLMIntentMapper, MaintenanceAgent, MaintenanceConfig
from .metrics import Metrics
from .ontology import Ontology, OntologyFinding, build_snapshot, check, fetch_workflows
from .policy import Policy
from .registry import Registry
from .router import Router
@@ -60,6 +62,35 @@ def _optional(path: Path, loader: Callable, default): # type: ignore[no-untyped
return loader(path) if path.exists() else default
def ontology_source_for(settings: Settings) -> Callable[[], list[dict]] | None:
"""Woher die n8n-Workflows für den Ontologie-Abgleich kommen: Datei (offline) oder n8n-API (nur lesend)."""
if settings.ontology_snapshot:
path = Path(settings.ontology_snapshot)
def from_file() -> list[dict]:
data = json.loads(path.read_text(encoding="utf-8"))
return data.get("data", data) if isinstance(data, dict) else data
return from_file
if settings.n8n_host and settings.n8n_api_key:
return lambda: fetch_workflows(settings.n8n_host, settings.n8n_api_key, timeout=settings.request_timeout)
return None
def ontology_checker(settings: Settings, store: Store,
source: Callable[[], list[dict]] | None) -> Callable[[], list[OntologyFinding]] | None:
source = source or ontology_source_for(settings)
if source is None or not settings.ontology_path.exists():
return None
def run() -> list[OntologyFinding]:
onto = Ontology.from_file(settings.ontology_path) # bei jedem Lauf neu: Änderungen im Repo zählen sofort
decided = {d["key"] for d in store.list_ontology_decisions()}
return check(onto, build_snapshot(source()), decided=decided)
return run
def build_system(
settings: Settings,
*,
@@ -70,6 +101,7 @@ def build_system(
notifier: Notifier | None = None,
jobs: JobRunner | None = None,
intent_mapper: IntentMapper | None = None,
ontology_source: Callable[[], list[dict]] | None = None,
clock: Callable[[], float] = now,
wallclock: Callable[[], datetime] = datetime.now,
) -> System:
@@ -117,7 +149,9 @@ def build_system(
intent_mapper = LLMIntentMapper(chat, registry.slot("tier1-general").spec.litellm_model)
maintenance = MaintenanceAgent(store=store, registry=registry, library=library, index=router.index,
policy=policy, actions=actions, lifecycle=lifecycle, config=maint_cfg,
intent_mapper=intent_mapper, clock=clock, wallclock=wallclock)
intent_mapper=intent_mapper,
ontology_check=ontology_checker(settings, store, ontology_source),
clock=clock, wallclock=wallclock)
healer = Healer(healer_cfg, store, actions, policy, propose=maintenance.propose, clock=clock)
prober = CanaryProber(registry, chat, embedder_for={settings.embed_model: embedder},
metrics_registry=metrics.registry)