CP2A2.2B: L1 Red Queen Worker Foundation - separate minimal worker runtime (non-root, read-only rootfs, cap-drop ALL, no-new-privileges, internal-net, credential-zero, inert idle, NO autonomy/heartbeat/scheduler)

This commit is contained in:
root 2026-08-29 07:16:28 +00:00
parent 7b58d27fa0
commit 7a4220d86d
11 changed files with 824 additions and 0 deletions

View file

@ -0,0 +1,46 @@
# CP2A2.2B — L1 Red Queen Worker (L1 Worker Foundation)
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
#
# Separate minimale L1 Worker Runtime fuer spaetere Read-only-Autonomie.
# Basis: nousresearch/hermes-agent:latest (Hermes oneshot `hermes -z`).
#
# HAERTUNG:
# - non-root hermes-User (UID 10010 / GID 10000, im Basis-Image definiert)
# - read-only rootfs (docker-compose: read_only: true)
# - cap_drop ALL, no-new-privileges (docker-compose)
# - kein Docker-Socket, keine Host-Root-Mounts, keine Host-Namespaces
# - kein s6-overlay / kein Gateway / kein Telegram / kein API-Server
# - eigener minimaler Entrypoint (inert idle, KEIN autonomer Loop)
#
# CREDENTIAL ZERO POLICY: Dieses Image enthaelt KEINE Credentials.
# Kein FORGEJO_WRITE_TOKEN, NOTION_API_KEY, TELEGRAM_BOT_TOKEN,
# SAVE_TOKEN, DELETE_TOKEN, SSH-Keys, Git-Write-Credentials, .netrc,
# .git-credentials, Cloud/DB/Service-Account/Webhook/Bearer/Docker/
# Broker/Trading-Credentials. ENV wird im Compose NICHT gesetzt.
#
# RED_QUEEN_TRADING_AUTHORITY=NEVER (permanente Invariante, siehe CP1.0S).
FROM nousresearch/hermes-agent:latest
# Kein s6-overlay, kein Gateway. Eigener minimaler Entrypoint.
# Der Worker startet inert (sleep infinity) und fuehrt KEINEN autonomen Tick aus.
# Ein Tick wird ausschliesslich extern getriggert (spaeterer externer Scheduler
# -> Gate-Evaluator /check -> ALLOW -> `hermes -z` oneshot). In CP2A2.2B existiert
# der Tick-Wrapper als Datei, wird aber NICHT automatisch ausgefuehrt.
# Worker-eigene Skripte (read-only im Image, keine Secrets)
COPY worker-entrypoint.sh /opt/l1-worker/worker-entrypoint.sh
COPY worker-tick.sh /opt/l1-worker/worker-tick.sh
COPY worker-config.yaml /opt/l1-worker/worker-config.yaml
# State-Verzeichnis (worker-owned, wird als Volume gemountet)
# Der hermes-User (UID 10010) ist der Worker. Das State-Volume wird im
# Compose als bind-mount von /opt/l1-worker-state gemountet (Host-root-angelegt,
# auf 10010:10000 gechownt) — nur der Worker kann schreiben.
RUN mkdir -p /opt/l1-worker/state && chown -R hermes:hermes /opt/l1-worker
# Non-root: der Worker laeuft als hermes-User (UID 10010 / GID 10000).
USER hermes:hermes
# Inert idle Entrypoint: KEIN autonomer Loop, KEIN Tick-Start.
ENTRYPOINT ["/bin/sh", "/opt/l1-worker/worker-entrypoint.sh"]

View file

@ -0,0 +1,94 @@
# CP2A2.2B — L1 Red Queen Worker: Rollback-Dokumentation
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
## Zweck
Rollback muss den L1 Worker vollstaendig entfernen/deaktivieren koennen, OHNE:
- CP1 State zu loeschen
- CP1 Audit zu loeschen
- Gate Evaluator zu veraendern
- interactive RQ zu veraendern
- AUTH4C/4D zu veraendern
- Trading zu veraendern
Worker-internal State bei Rollback zunaechst PRESERVE, nicht blind loeschen.
## Rollback-Schritte
### 1. Worker-Container stoppen und entfernen
```bash
cd /opt/cp2a2_2b
docker compose -f docker-compose.yml down
```
Dies stoppt und entfernt den `l1-red-queen-worker` Container und das
`l1-internal` Netzwerk. Das Worker-State-Volume `l1-worker-state` bleibt
erhalten (PRESERVE).
### 2. Netzwerk-Attachments entfernen (falls angehaengt)
Falls Gate-Evaluator, Ollama oder Tolaria an das `l1-internal` Netz angehaengt
wurden, werden diese Attachments entfernt:
```bash
docker network disconnect l1-internal gate-evaluator 2>/dev/null || true
docker network disconnect l1-internal ollama-nb6d-ollama-1 2>/dev/null || true
docker network disconnect l1-internal tolaria 2>/dev/null || true
```
Hinweis: `docker compose down` entfernt das `l1-internal` Netzwerk automatisch,
wodurch alle Attachments ebenfalls entfernt werden.
### 3. Worker-State-Volume (PRESERVE)
Das Worker-State-Volume `l1-worker-state` wird beim Rollback NICHT geloescht.
Es bleibt fuer spaetere Re-Initialisierung erhalten.
```bash
# Volume-Liste pruefen (nicht loeschen)
docker volume ls | grep l1-worker-state
```
### 4. Image entfernen (optional)
```bash
docker rmi l1-red-queen-worker:cp2a2_2b
```
### 5. SoT-Ruecknahme (optional)
Falls der SoT-Commit zurueckgenommen werden soll:
```bash
cd /opt/cp11_checker
git revert <commit-sha>
git push origin main
```
Hinweis: Der SoT-Commit ist historisch. Ein Revert erstellt einen neuen Commit,
der die Aenderungen zuruecknimmt. Der urspruengliche Commit bleibt im Log.
## Was beim Rollback NICHT veraendert wird
- CP1 State (`/opt/control-plane/state/`) — unveraendert
- CP1 Audit (`/opt/control-plane/audit/`) — unveraendert
- Gate Evaluator (`gate-evaluator` Container) — unveraendert
- Interactive RQ (`hermes-red-queen` Container) — unveraendert
- AUTH4C/4D — unveraendert
- Trading-System — unveraendert
## Verifikation nach Rollback
```bash
# Worker-Container entfernt
docker ps -a | grep l1-red-queen-worker || echo "Worker entfernt"
# Interactive RQ unveraendert
docker inspect hermes-red-queen --format '{{.State.Status}} {{.State.StartedAt}}'
# Gate Evaluator unveraendert
docker ps | grep gate-evaluator
# CP1 State unveraendert
ls -la /opt/control-plane/state/

View file

@ -0,0 +1,145 @@
# CP2A2.2B — L1 Red Queen Worker: Security-Dokumentation
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
## 1. Zweck
Separate minimale L1 Worker Runtime (`l1-red-queen-worker`) als technische
Sandbox/Foundation fuer spaetere Read-only-Autonomie. In CP2A2.2B:
- KEINE Autonomie
- KEINE Mission Execution
- KEIN Heartbeat
- KEIN Scheduler
- KEIN autonomer Tick
Der bestehende interaktive Container `hermes-red-queen` bleibt UNVERAENDERT.
## 2. Security-Invarianten (permanent)
RED_QUEEN_TRADING_AUTHORITY=NEVER
Der L1 Worker darf niemals erhalten:
- Broker Credentials
- Broker APIs
- Trading Tokens
- Trading Execution Authority
- Trading Kill-Switch Authority
Trading ist eine separate Security Domain.
Zusaetzlich:
- MISSION != AUTHORIZATION
- COMMAND != AUTHORIZATION
- RQ_DECISION != AUTHORIZATION
- GATE_CHECK != CAPABILITY_GRANT
## 3. Credential Zero Policy
Im L1 Worker sind ABSENT:
- FORGEJO_WRITE_TOKEN, NOTION_API_KEY, TELEGRAM_BOT_TOKEN
- SAVE_TOKEN, DELETE_TOKEN
- SSH private keys, Git write credentials, .netrc, .git-credentials
- credential helpers mit Secrets
- Cloud Credentials, DB Credentials (produktive externe DBs)
- Service Account Credentials, Webhook Secrets, Generic Bearer Tokens
- Docker Credentials, Broker Credentials, Trading Credentials
Geprueft werden: ENV files, mounted secrets, process environment, config,
home directory, git config, credential helpers.
Report: NUR PRESENT/ABSENT. NIEMALS Secret-Werte ausgeben.
## 4. Container-Hardening
- non-root UID/GID: hermes-User (UID 10010 / GID 10000)
- read-only rootfs
- cap_drop ALL
- no-new-privileges
- kein privileged
- kein Docker-Socket
- keine Host-Root-Mounts
- keine Host-Namespaces
- keine Device-Mounts
- keine unnecessary Linux capabilities
Writable nur: worker-owned internal state volume + technisch zwingende tmpfs-Pfade.
## 5. Tool-Capability-Model
Der Worker darf NICHT haben:
- terminal, generic shell, execute_code
- write_file auf externe/shared Ziele, patch
- git push, SSH, Docker, cronjob, send_message, delegation
- arbitrary command execution
MAX_SUBAGENTS=0. Nur minimal erforderliche Read-only-Faehigkeiten.
Ground-Truth-Verifikation: prueft die tatsaechlich geladenen Toolsets,
nicht nur die Config.
## 6. Network-Model
KEIN allgemeiner Internet-Egress. Worker erreicht nur:
- A. Gate Evaluator (internal)
- B. Ollama (internal)
- C. Tolaria READ/Search (internal)
NICHT erreichbar:
- Telegram API, Notion API, Forgejo public Host
- Internet allgemein, Trading Networks, Broker endpoints
- Docker API, Host management interfaces
- SAVE executor, DELETE executor
Keine generische Proxy-/Relay-Loesung.
## 7. Gate Evaluator
Worker darf den bestehenden Gate Evaluator erreichen. CP1 bleibt unveraendert.
Aktuell: GLOBAL_AUTONOMY=OFF, PRODUCTIVE_MUTATIONS=OFF, SAVE=OFF, DELETE=OFF,
EMERGENCY_STOP=ON. Daher muss jeder produktive AUTONOMOUS_TICK_START weiterhin
DENY ergeben. KEIN State aendern, um ALLOW zu testen.
## 8. Tolaria
Nur READ/Search. Worker darf KEINEN Tolaria SAVE/DELETE Token erhalten.
NICHT produktiv schreiben. Keine Mutation als Test.
## 9. Ollama / LLM
Worker darf Ollama erreichen (fuer Hermes-Start erforderlich). Keine
Credentials noetig. Keine dadurch entstehende externe Mutation Capability.
KEINE autonome Mission ausfuehren.
## 10. Internal State Volume
Worker-eigenes persistentes State-Volume. Noch KEINE produktive A2-A5
Initialisierung. Erlaubt: leeres/verifiziertes State-Verzeichnis bzw.
Foundation-Struktur. Noch NICHT: missions.db produktiv initialisieren,
safety runtime starten, orchestration runtime starten, heartbeat starten,
tick loop starten. Worker State darf ausschliesslich vom Worker beschrieben
werden. Bestehende interaktive RQ darf dieses Volume NICHT schreiben.
## 11. Mission Transfer — NOT YET
In CP2A2.2B: KEIN Shared Mission Volume, KEINE Mission Queue, KEIN Mission
Broker, KEIN Mission Snapshot produktiv anbinden. Mission Transfer kommt
separat danach.
## 12. No Autostart Autonomy
Nach Host-Reboot, Docker-Reboot, Container-Restart, Compose-up darf NICHT
automatisch ein autonomer Tick beginnen. Worker Foundation darf existieren/
starten, aber autonome Mission Execution bleibt unmoeglich. Gate DENY allein
ist nicht die einzige Schutzschicht. Es darf noch gar kein autonomer
Scheduler/Loop existieren.
## 13. Resource Limits
Konservative Foundation-Limits (siehe docker-compose / deploy):
- CPU limit
- memory limit
- pids limit
- bounded tmpfs
- log rotation
Ein fehlerhafter Worker darf den Host nicht durch Ressourcenverbrauch
beeintraechtigen.

View file

@ -0,0 +1,44 @@
#!/bin/bash
# CP2A2.2B — L1 Red Queen Worker: Deploy-Skript
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
#
# Baut und startet den L1 Worker Container (non-root, hardened, internal-Netz).
# Haengt Gate-Evaluator, Ollama und Tolaria an das l1-internal Netz an
# (nur Netzwerk-Attachment, KEINE Config/ENV/Credential-Aenderung).
#
# KEIN AUTOSTART-AUTONOMIE: Der Container startet inert (sleep infinity).
# Kein autonomer Tick, kein Heartbeat, kein Scheduler.
set -eu
cd "$(dirname "$0")"
echo "=== CP2A2.2B L1 WORKER DEPLOY ==="
# 1. Worker-State-Volume vorbereiten (worker-owned, UID 10010)
# Das Volume wird als named volume gemountet; der hermes-User (UID 10010)
# ist der Worker und besitzt das State-Verzeichnis.
echo "[1/4] Worker-State-Volume vorbereiten..."
docker volume create l1-worker-state >/dev/null 2>&1 || true
# 2. Image bauen (aus SoT, non-root, read-only, cap-drop)
echo "[2/4] Image bauen (l1-red-queen-worker:cp2a2_2b)..."
docker build -t l1-red-queen-worker:cp2a2_2b .
# 3. Container starten (internal-Netz, non-root, hardened)
echo "[3/4] Container starten (l1-red-queen-worker)..."
docker compose -f docker-compose.yml up -d
# 4. Interne Ziele an l1-internal Netz anhaengen (nur Netzwerk-Attachment)
echo "[4/4] Interne Ziele an l1-internal Netz anhaengen..."
docker network connect l1-internal gate-evaluator 2>/dev/null || echo " (gate-evaluator bereits verbunden oder nicht verfuegbar)"
docker network connect l1-internal ollama-nb6d-ollama-1 2>/dev/null || echo " (ollama bereits verbunden oder nicht verfuegbar)"
docker network connect l1-internal tolaria 2>/dev/null || echo " (tolaria bereits verbunden oder nicht verfuegbar)"
echo ""
echo "=== DEPLOY ABGESCHLOSSEN ==="
echo "Container: l1-red-queen-worker (inert idle, NO AUTONOMY)"
echo "Netzwerk: l1-internal (internal=true, keine Egress)"
echo "State-Volume: l1-worker-state (worker-owned)"
echo ""
echo "Hinweis: Der Worker startet inert. Kein autonomer Tick wird gestartet."

View file

@ -0,0 +1,57 @@
# CP2A2.2B — L1 Red Queen Worker: docker-compose
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
#
# Separate minimale L1 Worker Runtime. Der bestehende interaktive Container
# `hermes-red-queen` bleibt UNVERAENDERT (wird hier NICHT referenziert).
#
# HAERTUNG:
# - non-root hermes-User (UID 10010 / GID 10000, im Basis-Image definiert)
# - read-only rootfs
# - cap_drop ALL, no-new-privileges
# - kein Docker-Socket, keine Host-Root-Mounts, keine Host-Namespaces
# - eigenes internal-Netz (keine Egress, kein Internet)
# - KEINE Credentials in ENV (Credential Zero Policy)
#
# NETZWERK-MODELL:
# Der Worker haengt an einem eigenen internal-Netz `l1-internal`.
# Gate-Evaluator, Ollama und Tolaria werden per `docker network connect`
# zusaetzlich an dieses Netz angehaengt (nur Netzwerk-Attachment, KEINE
# Config/ENV/Credential-Aenderung an diesen Containern).
# Kein genereller Internet-Egress, kein Telegram/Notion/Forgejo-public.
#
# KEIN AUTOSTART-AUTONOMIE:
# Der Container startet inert (sleep infinity). Kein autonomer Tick, kein
# Heartbeat, kein Scheduler. Ein Tick wird ausschliesslich extern getriggert
# (spaetere Phase) und ist in CP2A2.2B verboten.
services:
l1-red-queen-worker:
build: .
image: l1-red-queen-worker:cp2a2_2b
container_name: l1-red-queen-worker
restart: unless-stopped
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
volumes:
# Worker-eigenes persistentes State-Volume (worker-owned, UID 10010).
# Nur der Worker kann schreiben. Bestehende interaktive RQ darf dieses
# Volume NICHT schreiben (separate Security Domain).
- l1-worker-state:/opt/l1-worker/state
networks:
- l1-internal
environment: []
# KEINE Credentials. KEIN Docker-Socket. KEINE Host-Root-Mounts.
# KEIN Gateway, KEIN Telegram, KEIN API-Server, KEIN Cron.
networks:
l1-internal:
driver: bridge
internal: true
volumes:
l1-worker-state:

View file

@ -0,0 +1,157 @@
# CP2A2.2B — L1 Red Queen Worker: Credential-Zero-Test
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
#
# Verifiziert, dass der Worker KEINE mutierenden Credentials besitzt.
# Report NUR PRESENT/ABSENT — NIEMALS Secret-Werte ausgeben.
#
# Geprueft werden:
# - ENV-Variablen (mutierende Credential-Namen)
# - config.yaml (worker-config.yaml)
# - Home-Verzeichnis (keine .netrc, .git-credentials, SSH-Keys)
# - git config (keine credential helpers)
# - mounted secrets
import os
import re
import sys
import subprocess
# Mutierende Credential-Namen (nur Namen, keine Werte)
MUTATING_CREDENTIAL_NAMES = [
"FORGEJO_WRITE_TOKEN",
"NOTION_API_KEY",
"TELEGRAM_BOT_TOKEN",
"SAVE_TOKEN",
"DELETE_TOKEN",
"TOLARIA_SAVE_TOKEN",
"TOLARIA_DELETE_TOKEN",
"GITHUB_TOKEN",
"GITLAB_TOKEN",
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AZURE_CLIENT_SECRET",
"GCP_SA_KEY",
"DOCKER_AUTH_CONFIG",
"DOCKER_CONFIG",
"IG_API_KEY",
"IG_ACCESS_TOKEN",
"BROKER_API_KEY",
"BROKER_TOKEN",
"TRADING_TOKEN",
"WEBHOOK_SECRET",
"SERVICE_ACCOUNT_KEY",
"DB_PASSWORD",
"POSTGRES_PASSWORD",
"MYSQL_PASSWORD",
"REDIS_PASSWORD",
"MONGO_PASSWORD",
]
# Dateien, die Credentials enthalten koennten (im Home/Config)
CREDENTIAL_FILE_PATTERNS = [
".netrc",
".git-credentials",
".pgpass",
".my.cnf",
".aws/credentials",
".config/gcloud/application_default_credentials.json",
".docker/config.json",
"id_rsa",
"id_ed25519",
"id_dsa",
"id_ecdsa",
]
def check_env() -> list[str]:
"""Prueft ENV auf mutierende Credential-Namen. Gibt gefundene Namen zurueck."""
found = []
for name in MUTATING_CREDENTIAL_NAMES:
if name in os.environ:
found.append(name)
return found
def check_config(config_path: str) -> list[str]:
"""Prueft die Worker-Config auf mutierende Credential-Namen.
Ignoriert Kommentare (Zeilen mit #) — Kommentare sind Dokumentation,
keine tatsaechlichen Credentials. Prueft nur echte YAML-Keys/Werte.
"""
found = []
if not os.path.exists(config_path):
return found
try:
with open(config_path, "r", encoding="utf-8") as f:
lines = f.readlines()
# Nur Nicht-Kommentar-Zeilen pruefen
content = "\n".join(
line for line in lines if not line.lstrip().startswith("#")
)
for name in MUTATING_CREDENTIAL_NAMES:
if name in content:
found.append(name)
# Generische Secret-Schluessel in YAML (nur echte Keys, nicht Kommentare)
for key in ["token", "api_key", "secret", "password", "bearer"]:
if re.search(rf"^\s*{key}\s*:", content, re.IGNORECASE | re.MULTILINE):
found.append(f"config-key:{key}")
except Exception:
pass
return found
def check_home(home: str) -> list[str]:
"""Prueft Home-Verzeichnis auf Credential-Dateien/SSH-Keys."""
found = []
for pattern in CREDENTIAL_FILE_PATTERNS:
path = os.path.join(home, pattern)
if os.path.exists(path):
found.append(pattern)
# SSH-Verzeichnis
ssh_dir = os.path.join(home, ".ssh")
if os.path.isdir(ssh_dir):
for entry in os.listdir(ssh_dir):
if entry not in ("known_hosts", "config"):
found.append(f".ssh/{entry}")
return found
def check_git_config(home: str) -> list[str]:
"""Prueft git config auf credential helpers."""
found = []
git_config = os.path.join(home, ".gitconfig")
if os.path.exists(git_config):
try:
with open(git_config, "r", encoding="utf-8") as f:
content = f.read()
if "credential" in content.lower() and "helper" in content.lower():
found.append("git-credential-helper")
except Exception:
pass
return found
def main() -> int:
home = os.environ.get("HOME", "/opt/data")
config_path = os.environ.get("WORKER_CONFIG", "/opt/l1-worker/worker-config.yaml")
env_found = check_env()
config_found = check_config(config_path)
home_found = check_home(home)
git_found = check_git_config(home)
all_found = sorted(set(env_found + config_found + home_found + git_found))
print("=== CP2A2.2B CREDENTIAL-ZERO TEST ===")
print(f"ENV mutating credentials: {env_found if env_found else 'ABSENT'}")
print(f"Config mutating credentials: {config_found if config_found else 'ABSENT'}")
print(f"Home credential files: {home_found if home_found else 'ABSENT'}")
print(f"Git credential helpers: {git_found if git_found else 'ABSENT'}")
print(f"TOTAL MUTATING CREDENTIALS: {len(all_found)}")
print(f"RESULT: {'PASS' if not all_found else 'FAIL'}")
return 0 if not all_found else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,65 @@
# CP2A2.2B — L1 Red Queen Worker: Network-Negative-Test
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
#
# Verifiziert die Netzwerk-Isolation des Workers:
# REACHABLE: Gate-Evaluator, Ollama, Tolaria-READ
# BLOCKED: Internet generisch, api.telegram.org, api.notion.com,
# Forgejo public, SAVE/DELETE executor, Trading/Broker
#
# Keine sensitiven Daten senden. Nur Connectivity-Checks (read-only).
import socket
import sys
# Erreichbare interne Ziele (im l1-internal Netz)
REACHABLE_TARGETS = [
("gate-evaluator", 8080),
("ollama-nb6d-ollama-1", 11434),
("tolaria", 8325),
]
# Blockierte Ziele (duerfen NICHT erreichbar sein)
BLOCKED_TARGETS = [
("api.telegram.org", 443),
("api.notion.com", 443),
("187.124.31.123", 3000), # Forgejo public
("8.8.8.8", 53), # generischer Internet-Egress
("1.1.1.1", 443), # generischer Internet-Egress
]
def check_reachable(host: str, port: int, timeout: float = 3.0) -> bool:
"""Prueft TCP-Erreichbarkeit (read-only, keine Daten gesendet)."""
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except Exception:
return False
def main() -> int:
print("=== CP2A2.2B NETWORK-NEGATIVE TEST ===")
reachable_results = {}
for host, port in REACHABLE_TARGETS:
ok = check_reachable(host, port)
reachable_results[f"{host}:{port}"] = ok
print(f"REACHABLE {host}:{port} = {'OK' if ok else 'UNREACHABLE'}")
blocked_results = {}
for host, port in BLOCKED_TARGETS:
ok = check_reachable(host, port)
blocked_results[f"{host}:{port}"] = ok
print(f"BLOCKED {host}:{port} = {'BLOCKED' if not ok else 'REACHABLE(!)'}")
# PASS: alle REACHABLE erreichbar, alle BLOCKED blockiert
all_reachable_ok = all(reachable_results.values())
all_blocked_ok = all(not v for v in blocked_results.values())
result = all_reachable_ok and all_blocked_ok
print(f"RESULT: {'PASS' if result else 'FAIL'}")
return 0 if result else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,97 @@
# CP2A2.2B — L1 Red Queen Worker: Tool-Capability-Test
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
#
# Verifiziert, dass der Worker KEINE verbotenen Tools/Faehigkeiten besitzt.
# Verboten: terminal, generic shell, execute_code, write_file auf externe/
# shared Ziele, patch, git push, SSH, Docker, cronjob, send_message,
# delegation, arbitrary command execution. MAX_SUBAGENTS=0.
#
# Ground-Truth-Check: prueft die tatsaechlich geladenen Toolsets, nicht nur
# die Config. Der Worker nutzt ein minimales read-only Toolset.
import os
import sys
# Verbotene Tool-Namen (Ground-Truth-Check gegen geladene Toolsets)
FORBIDDEN_TOOLS = [
"terminal",
"process",
"execute_code",
"write_file",
"patch",
"git_push",
"ssh",
"docker",
"cronjob",
"send_message",
"delegate_task",
"browser_navigate",
"browser_click",
"browser_type",
"computer_use",
]
# Erlaubte (read-only) Tools
ALLOWED_TOOLS = [
"web_extract",
"web_search",
"vision_analyze",
"read_file",
"search_files",
"session_search",
"memory",
"todo",
"clarify",
"skills_list",
"skill_view",
]
def get_loaded_toolsets() -> list[str]:
"""Ermittelt die tatsaechlich geladenen Toolsets (Ground Truth)."""
# Aus ENV: HERMES_TUI_TOOLSETS oder via hermes CLI
env_toolsets = os.environ.get("HERMES_TUI_TOOLSETS", "")
if env_toolsets:
return [t.strip() for t in env_toolsets.split(",") if t.strip()]
return []
def check_forbidden_tools(toolsets: list[str]) -> list[str]:
"""Prueft, ob verbotene Tools in den geladenen Toolsets enthalten sind."""
found = []
# Mapping Toolset -> Tools (statisch, aus toolsets.py Ground Truth)
toolset_tools = {
"web": ["web_search", "web_extract"],
"vision": ["vision_analyze"],
"file": ["read_file", "write_file", "patch", "search_files"],
"terminal": ["terminal", "process"],
"code_execution": ["execute_code"],
"delegation": ["delegate_task"],
"cronjob": ["cronjob"],
"browser": ["browser_navigate", "browser_snapshot", "browser_click",
"browser_type", "browser_scroll", "browser_back",
"browser_press", "browser_get_images", "browser_console",
"web_search"],
"computer_use": ["computer_use"],
}
for ts in toolsets:
for tool in toolset_tools.get(ts, []):
if tool in FORBIDDEN_TOOLS:
found.append(f"{ts}:{tool}")
return found
def main() -> int:
toolsets = get_loaded_toolsets()
forbidden = check_forbidden_tools(toolsets)
print("=== CP2A2.2B TOOL-CAPABILITY TEST ===")
print(f"Loaded toolsets: {toolsets if toolsets else 'NONE (default)'}")
print(f"Forbidden tools found: {forbidden if forbidden else 'ABSENT'}")
print(f"RESULT: {'PASS' if not forbidden else 'FAIL'}")
return 0 if not forbidden else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,37 @@
# CP2A2.2B — L1 Red Queen Worker: minimale Hermes-Config (ohne Secrets)
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
#
# Diese Config ist die minimale Hermes-Config fuer den Worker.
# Sie enthaelt KEINE Credentials (kein Telegram, kein Notion, kein Forgejo,
# kein SAVE/DELETE-Token). Provider = Ollama (intern, keine Credentials noetig).
#
# CREDENTIAL ZERO POLICY: Kein FORGEJO_WRITE_TOKEN, NOTION_API_KEY,
# TELEGRAM_BOT_TOKEN, SAVE_TOKEN, DELETE_TOKEN, SSH-Keys, Git-Write-Credentials,
# .netrc, .git-credentials, Cloud/DB/Service-Account/Webhook/Bearer/Docker/
# Broker/Trading-Credentials.
model:
provider: ollama
default: deepseek-v4-flash:cloud
context_length: 131072
providers:
ollama:
# Interner Ollama-Endpoint (kein Internet-Egress). Keine Credentials noetig.
base_url: http://ollama-nb6d-ollama-1:11434/v1
context_length: 131072
discover_models: true
agent:
verify_on_stop: false
# KEIN Gateway, KEIN Telegram, KEIN API-Server, KEIN Cron.
gateway:
telegram:
enabled: false
# KEINE Plugins (keine externen Faehigkeiten).
plugins:
enabled: []
_config_version: 33

View file

@ -0,0 +1,22 @@
#!/bin/sh
# CP2A2.2B — L1 Red Queen Worker: inert idle Entrypoint
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
#
# Dieser Entrypoint startet den Worker als INERTEN idle-Prozess.
# Er fuehrt KEINEN autonomen Tick aus, KEINEN Heartbeat, KEINEN Scheduler.
# Ein Tick wird ausschliesslich extern getriggert (spaeterer externer Scheduler
# -> Gate-Evaluator /check -> ALLOW -> `hermes -z` oneshot via worker-tick.sh).
#
# In CP2A2.2B existiert der Tick-Wrapper als Datei, wird aber NICHT automatisch
# ausgefuehrt. Der Container bleibt inert, bis ein externer Trigger ihn startet.
set -eu
echo "[l1-worker] CP2A2.2B L1 Worker Foundation — inert idle (NO AUTONOMY, NO HEARTBEAT, NO SCHEDULER)"
echo "[l1-worker] UID=$(id -u) GID=$(id -g) USER=$(id -un)"
echo "[l1-worker] Warte auf externen Trigger. Kein autonomer Tick wird gestartet."
# Inert idle: blockiere, ohne einen autonomen Loop zu starten.
# Ein externer Scheduler (spaetere Phase) wuerde `worker-tick.sh` aufrufen.
# In CP2A2.2B passiert das NICHT automatisch.
exec sleep infinity

View file

@ -0,0 +1,60 @@
#!/bin/sh
# CP2A2.2B — L1 Red Queen Worker: Tick-Wrapper (DEFINIERT, NICHT AUTOMATISCH AUSGEFUEHRT)
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
#
# Dieser Wrapper fuehrt EINEN einzelnen `hermes -z` oneshot aus.
# Er wird in CP2A2.2B NICHT automatisch gestartet (kein Scheduler, kein Loop).
# Er dient als technische Foundation fuer spaetere Read-only-Autonomie:
# externer Scheduler -> Gate-Evaluator /check -> ALLOW -> worker-tick.sh
#
# WICHTIG: In CP2A2.2B wird dieser Wrapper NUR als Datei bereitgestellt.
# Er wird NICHT vom Entrypoint, NICHT von cron, NICHT von einem Loop aufgerufen.
# Kein autonomer Tick darf in dieser Phase laufen.
set -eu
# --- Gate-Evaluator /check (read-only, keine Mutation) ---
# Der Gate-Evaluator (CP2A1) entscheidet fail-closed. Aktuell:
# GLOBAL_AUTONOMY=OFF, PRODUCTIVE_MUTATIONS=OFF, SAVE=OFF, DELETE=OFF,
# EMERGENCY_STOP=ON -> AUTONOMOUS_TICK_START = DENY
# Daher muss jeder produktive Tick-Start weiterhin DENY ergeben.
# KEIN State aendern, um ALLOW zu testen.
GATE_URL="${GATE_EVALUATOR_URL:-http://gate-evaluator:8080}"
ACTION_CLASS="AUTONOMOUS_TICK_START"
echo "[l1-worker] Gate-Check (read-only): ${GATE_URL}/check action_class=${ACTION_CLASS}"
# Read-only HTTP-Check via Python (kein curl im Image noetig, kein Shell-Egress).
# Der Worker hat KEINE mutierenden Credentials; der Check ist rein lesend.
GATE_RESPONSE=$(python3 - <<PYEOF
import json, urllib.request
try:
req = urllib.request.Request(
"${GATE_URL}/check",
data=json.dumps({"action_class": "${ACTION_CLASS}"}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=10) as resp:
body = resp.read().decode()
print(body)
except Exception as exc:
print(json.dumps({"error": str(exc), "decision": "DENY"}))
PYEOF
)
echo "[l1-worker] Gate-Response: ${GATE_RESPONSE}"
# Fail-closed: Nur bei explizitem ALLOW wuerde ein Tick laufen.
# In CP2A2.2B ist das Gate DENY (EMERGENCY_STOP=ON), daher KEIN Tick.
DECISION=$(echo "${GATE_RESPONSE}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('decision','DENY'))" 2>/dev/null || echo "DENY")
if [ "${DECISION}" = "ALLOW" ]; then
echo "[l1-worker] WARNUNG: Gate ALLOW — aber CP2A2.2B ist NON-AUTONOMOUS. Tick wird NICHT ausgefuehrt."
echo "[l1-worker] Autonome Mission Execution ist in dieser Phase verboten (HARD STOP)."
exit 0
fi
echo "[l1-worker] Gate-DECISION=${DECISION} (fail-closed). Kein autonomer Tick in CP2A2.2B."
exit 0