Initial commit: a0_software_orchestrator v1.0
- Auto-Registration-Bug behoben (register_project/get_project_id/resolve_project Trennung) - 25 Tests gruen (Pytest) - block_compactor-Tool refactored (Option B: Soft-Check statt Hard-Block) - 4 Restbaustellen gefixt - DB-Schema: plugin_settings-Tabelle hinzugefuegt - 3 Schattenprojekte aus DB geloescht - Plan v3 + Refactor-Plan + Worklog dokumentiert
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
"""Tool: create/read/update/validate project manifests (DB-backed, replaces .a0/manifests/*.json)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
from helpers.tool import Tool, Response
|
||||
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import get_project_id, get_kv, set_kv, list_kv_keys
|
||||
from usr.plugins.a0_software_orchestrator.helpers.manifest_schema import PROJECT_MANIFEST_SCHEMA, DEPLOYMENT_MANIFEST_SCHEMA
|
||||
from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError
|
||||
|
||||
|
||||
def _resolve_name(project_name: str = "", project_root: str = "") -> str:
|
||||
if project_name:
|
||||
return project_name.strip()
|
||||
if project_root:
|
||||
return os.path.basename(project_root.rstrip("/"))
|
||||
return os.path.basename(os.getcwd())
|
||||
|
||||
|
||||
def _schema_for(manifest_type: str) -> Dict[str, Any]:
|
||||
if manifest_type == "project":
|
||||
return PROJECT_MANIFEST_SCHEMA
|
||||
if manifest_type == "deployment":
|
||||
return DEPLOYMENT_MANIFEST_SCHEMA
|
||||
return {}
|
||||
|
||||
|
||||
def _validate(data: Dict[str, Any], schema: Dict[str, Any]) -> list[str]:
|
||||
"""Lightweight required-keys validator (subset of JSON-Schema)."""
|
||||
errors = []
|
||||
if not isinstance(data, dict):
|
||||
return [f"manifest must be a JSON object, got {type(data).__name__}"]
|
||||
required = schema.get("required", [])
|
||||
for key in required:
|
||||
if key not in data:
|
||||
errors.append(f"missing required field: {key!r}")
|
||||
return errors
|
||||
|
||||
|
||||
class RepoManifest(Tool):
|
||||
"""Create, read, update, list or validate project/deployment/quality manifests.
|
||||
|
||||
Args (kwargs):
|
||||
project_name (str): Projektname (bevorzugt).
|
||||
project_root (str): Legacy – wird zu basename() aufgelöst.
|
||||
manifest_type (str): "project" (default), "deployment", "quality".
|
||||
action (str): "create" (default), "read", "update", "list", "validate".
|
||||
data (str|dict): JSON-string oder dict (für create/update).
|
||||
"""
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
project_name: str = "",
|
||||
project_root: str = "",
|
||||
manifest_type: str = "project",
|
||||
action: str = "create",
|
||||
data: str = "",
|
||||
**kwargs,
|
||||
):
|
||||
name = _resolve_name(project_name, project_root)
|
||||
try:
|
||||
pid = get_project_id(name)
|
||||
if pid is None:
|
||||
raise LookupError(f"project '{name}' is not registered")
|
||||
except (ValueError, LookupError) as e:
|
||||
return Response(message=f"ERROR: {e}", break_loop=False)
|
||||
|
||||
except PluginDBError as e:
|
||||
return Response(message=f"Database error: {e}", break_loop=False)
|
||||
key = f"manifest_{manifest_type}"
|
||||
schema = _schema_for(manifest_type)
|
||||
|
||||
if action == "list":
|
||||
keys = [k for k in list_kv_keys(pid) if k.startswith("manifest_")]
|
||||
if not keys:
|
||||
return Response(
|
||||
message=f"No manifests for project {name!r}.",
|
||||
break_loop=False,
|
||||
)
|
||||
return Response(
|
||||
message=f"Manifests for {name!r}: {', '.join(keys)}",
|
||||
break_loop=False,
|
||||
)
|
||||
|
||||
if action == "read":
|
||||
manifest = get_kv(pid, key, default=None)
|
||||
if manifest is None:
|
||||
return Response(
|
||||
message=f"No {manifest_type!r} manifest for project {name!r}.",
|
||||
break_loop=False,
|
||||
)
|
||||
return Response(
|
||||
message=json.dumps(manifest, indent=2, ensure_ascii=False),
|
||||
break_loop=False,
|
||||
)
|
||||
|
||||
if action in ("create", "update"):
|
||||
# Parse data
|
||||
if not data:
|
||||
return Response(
|
||||
message="ERROR: 'data' required for create/update (JSON string or dict)",
|
||||
break_loop=False,
|
||||
)
|
||||
if isinstance(data, str):
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
except json.JSONDecodeError as e:
|
||||
return Response(
|
||||
message=f"ERROR: 'data' is not valid JSON: {e}",
|
||||
break_loop=False,
|
||||
)
|
||||
else:
|
||||
parsed = data
|
||||
|
||||
# Validate against schema (if known)
|
||||
if schema:
|
||||
errors = _validate(parsed, schema)
|
||||
if errors:
|
||||
return Response(
|
||||
message=f"ERROR: manifest validation failed: {errors}",
|
||||
break_loop=False,
|
||||
)
|
||||
|
||||
set_kv(pid, key, parsed)
|
||||
return Response(
|
||||
message=f"{manifest_type!r} manifest saved for project {name!r} (key={key!r}).",
|
||||
break_loop=False,
|
||||
)
|
||||
|
||||
if action == "validate":
|
||||
manifest = get_kv(pid, key, default=None)
|
||||
if manifest is None:
|
||||
return Response(
|
||||
message=f"No {manifest_type!r} manifest to validate for project {name!r}.",
|
||||
break_loop=False,
|
||||
)
|
||||
if not schema:
|
||||
return Response(
|
||||
message=f"No schema defined for {manifest_type!r}; cannot validate.",
|
||||
break_loop=False,
|
||||
)
|
||||
errors = _validate(manifest, schema)
|
||||
if errors:
|
||||
return Response(
|
||||
message=f"Validation FAILED: {errors}",
|
||||
break_loop=False,
|
||||
)
|
||||
return Response(
|
||||
message=f"Validation PASSED for {manifest_type!r} manifest of project {name!r}.",
|
||||
break_loop=False,
|
||||
)
|
||||
|
||||
return Response(
|
||||
message=f"Unknown action {action!r}. Supported: create, read, update, list, validate",
|
||||
break_loop=False,
|
||||
)
|
||||
Reference in New Issue
Block a user