kin: KIN-071 Добавить тип проекта: development / operations / research. Для operations: вместо path к локальной папке — ssh-доступ (host, user, key, proxy or jump). При создании operations-проекта запускается sysadmin-агент который подключается по SSH, обходит сервер, составляет карту: какие сервисы запущены (docker ps, systemctl), какие конфиги где лежат, какие порты открыты, какие версии. Результат сохраняется в decisions и modules как база знаний по серверу. Код не хранится локально — агенты работают через SSH. PM для operations вызывает sysadmin/debugger, не architect/frontend_dev.

This commit is contained in:
Gros Frumos 2026-03-16 09:17:42 +02:00
parent d9172fc17c
commit 75fee86110
4 changed files with 371 additions and 0 deletions

View file

@ -1916,3 +1916,113 @@ class TestSaveSysadminOutput:
from agents.runner import _save_sysadmin_output
result = _save_sysadmin_output(ops_conn, "srv", "SRV-001", {"raw_output": ""})
assert result["decisions_added"] == 0
def test_full_sysadmin_output_format_saves_docker_and_systemctl_as_decisions(self, ops_conn):
"""KIN-071: полный формат вывода sysadmin (docker ps + systemctl) → decisions + modules."""
from agents.runner import _save_sysadmin_output
# Симуляция реального вывода sysadmin-агента после docker ps и systemctl
output = {
"status": "done",
"summary": "Ubuntu 22.04, nginx + postgres + app in docker",
"os": "Ubuntu 22.04 LTS, kernel 5.15.0",
"services": [
{"name": "nginx", "type": "systemd", "status": "running", "note": "web proxy"},
{"name": "myapp", "type": "docker", "image": "myapp:1.2.3", "ports": ["80:8080"]},
{"name": "postgres", "type": "docker", "image": "postgres:15", "ports": ["5432:5432"]},
],
"open_ports": [
{"port": 80, "proto": "tcp", "process": "nginx"},
{"port": 5432, "proto": "tcp", "process": "postgres"},
],
"decisions": [
{
"type": "gotcha",
"title": "nginx proxies to docker app on 8080",
"description": "nginx.conf proxy_pass http://localhost:8080",
"tags": ["nginx", "docker"],
},
{
"type": "decision",
"title": "postgres data on /var/lib/postgresql",
"description": "Volume mount /var/lib/postgresql/data persists DB",
"tags": ["postgres", "storage"],
},
],
"modules": [
{
"name": "nginx",
"type": "service",
"path": "/etc/nginx",
"description": "Reverse proxy",
"owner_role": "sysadmin",
},
{
"name": "myapp",
"type": "docker",
"path": "/opt/myapp",
"description": "Main application container",
},
{
"name": "postgres",
"type": "docker",
"path": "/var/lib/postgresql",
"description": "Database",
},
],
}
result = _save_sysadmin_output(ops_conn, "srv", "SRV-001", {"raw_output": json.dumps(output)})
assert result["decisions_added"] == 2
assert result["modules_added"] == 3
decisions = models.get_decisions(ops_conn, "srv")
d_titles = {d["title"] for d in decisions}
assert "nginx proxies to docker app on 8080" in d_titles
assert "postgres data on /var/lib/postgresql" in d_titles
modules = models.get_modules(ops_conn, "srv")
m_names = {m["name"] for m in modules}
assert {"nginx", "myapp", "postgres"} == m_names
def test_invalid_decision_type_normalized_to_decision(self, ops_conn):
"""KIN-071: тип 'workaround' не входит в VALID_DECISION_TYPES → нормализуется в 'decision'."""
from agents.runner import _save_sysadmin_output
output = {
"decisions": [
{
"type": "workaround",
"title": "Use /proc/net for port list",
"description": "ss not installed, fallback to /proc/net/tcp",
"tags": ["networking"],
},
],
"modules": [],
}
_save_sysadmin_output(ops_conn, "srv", "SRV-001", {"raw_output": json.dumps(output)})
decisions = models.get_decisions(ops_conn, "srv")
assert len(decisions) == 1
assert decisions[0]["type"] == "decision"
def test_decision_missing_title_skipped(self, ops_conn):
"""KIN-071: decision без title пропускается."""
from agents.runner import _save_sysadmin_output
output = {
"decisions": [
{"type": "gotcha", "title": "", "description": "Something"},
],
"modules": [],
}
result = _save_sysadmin_output(ops_conn, "srv", "SRV-001", {"raw_output": json.dumps(output)})
assert result["decisions_added"] == 0
def test_module_missing_name_skipped(self, ops_conn):
"""KIN-071: module без name пропускается."""
from agents.runner import _save_sysadmin_output
output = {
"decisions": [],
"modules": [
{"name": "", "type": "service", "path": "/etc/something"},
],
}
result = _save_sysadmin_output(ops_conn, "srv", "SRV-001", {"raw_output": json.dumps(output)})
assert result["modules_added"] == 0