ontology: Ontologie als Quelle im Repo, Abgleich mit n8n/KNOW in der Service-Routine
ci / test (push) Successful in 27s
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:
+107
-1
@@ -1,4 +1,4 @@
|
||||
"""Kommandozeile: serve, validate, inventory, placement, registry export."""
|
||||
"""Kommandozeile: serve, validate, inventory, placement, registry export, ontology."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -47,8 +47,28 @@ def main(argv: list[str] | None = None) -> int:
|
||||
reg_sub = p_reg.add_subparsers(dest="reg_cmd", required=True)
|
||||
reg_sub.add_parser("export", help="effektiven Stand (inkl. Laufzeitzustand) als YAML ausgeben")
|
||||
|
||||
p_onto = sub.add_parser("ontology", help="Ontologie der Datenhaltung (SPEC-ontology)")
|
||||
p_onto.add_argument("--path", default=None, help="Ontologie-Datei (Standard: ontology/know.yaml)")
|
||||
onto_sub = p_onto.add_subparsers(dest="onto_cmd", required=True)
|
||||
onto_sub.add_parser("validate", help="Modell prüfen")
|
||||
p_exp = onto_sub.add_parser("export", help="Turtle, Graph-JSON und/oder HTML-Ansicht erzeugen")
|
||||
p_exp.add_argument("--ttl")
|
||||
p_exp.add_argument("--graph")
|
||||
p_exp.add_argument("--html")
|
||||
p_exp.add_argument("--template", default=None, help="Standard: viewer.html.tmpl neben der Ontologie")
|
||||
p_snap = onto_sub.add_parser("snapshot", help="n8n-Workflows lesen und als JSON speichern (nur GET)")
|
||||
p_snap.add_argument("--out", required=True)
|
||||
p_chk = onto_sub.add_parser("check", help="Ontologie mit der Datenhaltung in n8n abgleichen")
|
||||
p_chk.add_argument("--snapshot", help="Workflows aus Datei statt aus der n8n-API")
|
||||
p_chk.add_argument("--json", action="store_true")
|
||||
p_app = onto_sub.add_parser("apply", help="freigegebene Entscheidungen in die Ontologie übernehmen")
|
||||
p_app.add_argument("--dry-run", action="store_true")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.cmd == "ontology":
|
||||
return _ontology(args)
|
||||
|
||||
if args.cmd == "serve":
|
||||
import uvicorn
|
||||
|
||||
@@ -104,5 +124,91 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return 2
|
||||
|
||||
|
||||
def _ontology(args: argparse.Namespace) -> int:
|
||||
import json
|
||||
|
||||
from .ontology import (
|
||||
Ontology,
|
||||
apply_decisions,
|
||||
build_snapshot,
|
||||
check,
|
||||
fetch_workflows,
|
||||
to_graph,
|
||||
to_html,
|
||||
to_turtle,
|
||||
)
|
||||
from .settings import Settings
|
||||
|
||||
settings = Settings.from_env()
|
||||
path = Path(args.path) if args.path else settings.ontology_path
|
||||
onto = Ontology.from_file(path)
|
||||
|
||||
if args.onto_cmd == "validate":
|
||||
errors = onto.validate_model()
|
||||
for e in errors:
|
||||
print(f"ERROR {e}")
|
||||
print(f"{len(onto.classes)} Klassen, {len(onto.relations)} Beziehungen, {len(onto.vocabularies)} Vokabulare, "
|
||||
f"{len(errors)} Fehler")
|
||||
return 1 if errors else 0
|
||||
|
||||
if args.onto_cmd == "export":
|
||||
if args.ttl:
|
||||
Path(args.ttl).write_text(to_turtle(onto), encoding="utf-8")
|
||||
if args.graph:
|
||||
Path(args.graph).write_text(json.dumps(to_graph(onto), ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
if args.html:
|
||||
template = Path(args.template) if args.template else path.parent / "viewer.html.tmpl"
|
||||
Path(args.html).write_text(to_html(onto, template), encoding="utf-8")
|
||||
return 0
|
||||
|
||||
if args.onto_cmd in {"snapshot", "check"}:
|
||||
if getattr(args, "snapshot", None):
|
||||
data = json.loads(Path(args.snapshot).read_text(encoding="utf-8"))
|
||||
workflows = data.get("data", data) if isinstance(data, dict) else data
|
||||
else:
|
||||
if not (settings.n8n_host and settings.n8n_api_key):
|
||||
print("N8N_HOST und N8N_API_KEY fehlen (oder --snapshot angeben)", file=sys.stderr)
|
||||
return 2
|
||||
workflows = fetch_workflows(settings.n8n_host, settings.n8n_api_key)
|
||||
if args.onto_cmd == "snapshot":
|
||||
Path(args.out).write_text(json.dumps({"data": workflows}, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"{len(workflows)} Workflows gespeichert in {args.out} (enthält Workflow-Code, nicht committen)")
|
||||
return 0
|
||||
from .store import Store
|
||||
|
||||
decided = {d["key"] for d in Store(settings.db_url).list_ontology_decisions()}
|
||||
findings = check(onto, build_snapshot(workflows), decided=decided)
|
||||
if args.json:
|
||||
print(json.dumps([f.model_dump() for f in findings], ensure_ascii=False, indent=1))
|
||||
else:
|
||||
for f in findings:
|
||||
mark = "→ Vorschlag" if f.change else ""
|
||||
print(f"{f.severity.upper():7} {f.summary} {mark}".rstrip())
|
||||
print(f"{len(findings)} Befunde, davon {sum(1 for f in findings if f.change)} Änderungsvorschläge")
|
||||
return 1 if any(f.severity == "high" for f in findings) else 0
|
||||
|
||||
if args.onto_cmd == "apply":
|
||||
from .store import Store
|
||||
|
||||
store = Store(settings.db_url)
|
||||
pending = store.list_ontology_decisions(applied=False)
|
||||
applied, messages = apply_decisions(onto, pending)
|
||||
for m in messages:
|
||||
print(m)
|
||||
errors = onto.validate_model()
|
||||
if errors:
|
||||
print("Ontologie wäre ungültig, nichts gespeichert:\n" + "\n".join(errors), file=sys.stderr)
|
||||
return 1
|
||||
if applied and not args.dry_run:
|
||||
header = "".join(line + "\n" for line in path.read_text(encoding="utf-8").splitlines()
|
||||
if line.startswith("#"))
|
||||
onto.save(path, header)
|
||||
store.mark_ontology_applied(applied)
|
||||
print(f"{len(applied)} Entscheidungen übernommen, Version {onto.version}. "
|
||||
"Danach `knaster ontology export` und committen.")
|
||||
return 0
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user