Compare commits

..
31 changed files with 6153 additions and 3104 deletions
+315
View File
@@ -0,0 +1,315 @@
#!/usr/bin/env python3
"""HTTP API for AIOS external-agent online task distribution.
The service is intended to bind to loopback and be published through the
existing TLS reverse proxy. Control operations use a server-managed control
Bearer token. Agents receive individual Bearer tokens at registration; only
token hashes are persisted.
"""
from __future__ import annotations
from dataclasses import dataclass
import hmac
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import re
from typing import Any, Mapping
from urllib.parse import urlparse
from aios_agent_task_protocol import TaskProtocolError, TaskStore
MAX_BODY = 1024 * 1024
TASK_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
@dataclass(frozen=True, slots=True)
class APIConfig:
db_path: Path
control_token_path: Path
host: str = "127.0.0.1"
port: int = 18940
token_pepper: str = ""
@classmethod
def from_env(cls) -> "APIConfig":
return cls(
db_path=Path(os.getenv(
"AIOS_AGENT_TASK_DB",
"/var/lib/aios-agent-task/state.sqlite3",
)),
control_token_path=Path(os.getenv(
"AIOS_AGENT_TASK_CONTROL_TOKEN_FILE",
"/run/credentials/aios-agent-task-api.service/control_token",
)),
host=os.getenv("AIOS_AGENT_TASK_HOST", "127.0.0.1"),
port=int(os.getenv("AIOS_AGENT_TASK_PORT", "18940")),
token_pepper=os.getenv("AIOS_AGENT_TASK_TOKEN_PEPPER", ""),
)
class AgentTaskApplication:
def __init__(self, config: APIConfig) -> None:
self.config = config
self.store = TaskStore(
config.db_path,
token_pepper=config.token_pepper,
)
def _control_token(self) -> str:
path = self.config.control_token_path
if path.is_symlink() or not path.is_file():
raise TaskProtocolError("CONTROL_CREDENTIAL_UNAVAILABLE", 503)
value = path.read_text(encoding="utf-8").strip()
if len(value) < 32:
raise TaskProtocolError("CONTROL_CREDENTIAL_INVALID", 503)
return value
@staticmethod
def _bearer(headers: Mapping[str, str]) -> str:
value = str(headers.get("Authorization") or "")
if not value.startswith("Bearer "):
return ""
return value[7:].strip()
def authenticate_control(self, headers: Mapping[str, str]) -> None:
supplied = self._bearer(headers)
expected = self._control_token()
if not supplied or not hmac.compare_digest(supplied, expected):
raise TaskProtocolError("UNAUTHORIZED", 401)
def authenticate_agent(self, headers: Mapping[str, str]) -> dict[str, Any]:
return self.store.authenticate_agent(self._bearer(headers))
def principal(self, headers: Mapping[str, str]) -> tuple[str, dict[str, Any] | None]:
supplied = self._bearer(headers)
if not supplied:
raise TaskProtocolError("UNAUTHORIZED", 401)
try:
expected = self._control_token()
except TaskProtocolError:
expected = ""
if expected and hmac.compare_digest(supplied, expected):
return "control", None
return "agent", self.store.authenticate_agent(supplied)
def health(self) -> dict[str, Any]:
return {
"status": "ok",
"schema": "aios.agent-task.v1",
"task_states": [
"QUEUED", "CLAIMED", "RUNNING", "VERIFYING",
"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED",
],
"lease_recovery": True,
"atomic_claim": True,
"idempotent_result_submission": True,
"server_side_verification_required": True,
"plaintext_credentials_persisted": False,
}
def register_agent(self, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
self.authenticate_control(headers)
return self.store.register_agent(
agent_id=str(body.get("agent_id") or ""),
agent_type=str(body.get("agent_type") or ""),
agent_version=str(body.get("agent_version") or ""),
capabilities=body.get("capabilities") or [],
metadata=body.get("metadata") if isinstance(body.get("metadata"), Mapping) else {},
)
def create_task(self, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
self.authenticate_control(headers)
return self.store.create_task(
task_id=str(body.get("task_id") or "") or None,
goal=str(body.get("goal") or ""),
instructions=str(body.get("instructions") or ""),
success_criteria=body.get("success_criteria"),
required_capabilities=body.get("required_capabilities") or [],
constraints=body.get("constraints") if isinstance(body.get("constraints"), Mapping) else {},
metadata=body.get("metadata") if isinstance(body.get("metadata"), Mapping) else {},
deadline=str(body.get("deadline") or "") or None,
priority=body.get("priority", 0),
max_attempts=body.get("max_attempts", 3),
idempotency_key=str(body.get("idempotency_key") or ""),
)
def poll(self, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
agent = self.authenticate_agent(headers)
tasks = self.store.poll_tasks(
agent_id=agent["agent_id"],
limit=int(body.get("limit", 20)),
)
return {"agent_id": agent["agent_id"], "tasks": tasks}
def claim(self, task_id: str, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
agent = self.authenticate_agent(headers)
return self.store.claim_task(
task_id=task_id,
agent_id=agent["agent_id"],
lease_seconds=int(body.get("lease_seconds", 300)),
)
def heartbeat(self, task_id: str, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
agent = self.authenticate_agent(headers)
return self.store.heartbeat(
task_id=task_id,
agent_id=agent["agent_id"],
lease_token=str(body.get("lease_token") or ""),
progress=body.get("progress") if isinstance(body.get("progress"), Mapping) else {},
lease_seconds=int(body.get("lease_seconds", 300)),
)
def result(self, task_id: str, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
agent = self.authenticate_agent(headers)
result = body.get("result")
if not isinstance(result, Mapping):
raise TaskProtocolError("RESULT_INVALID", 400)
return self.store.submit_result(
task_id=task_id,
agent_id=agent["agent_id"],
lease_token=str(body.get("lease_token") or ""),
idempotency_key=str(body.get("idempotency_key") or ""),
result=result,
)
def verify(self, task_id: str, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
self.authenticate_control(headers)
return self.store.verify_task(
task_id=task_id,
decision=str(body.get("decision") or ""),
verification=body.get("verification") if isinstance(body.get("verification"), Mapping) else {},
)
def cancel(self, task_id: str, headers: Mapping[str, str], body: Mapping[str, Any]) -> dict[str, Any]:
self.authenticate_control(headers)
return self.store.cancel_task(task_id=task_id, reason=str(body.get("reason") or ""))
def get_task(self, task_id: str, headers: Mapping[str, str]) -> dict[str, Any]:
kind, agent = self.principal(headers)
task = self.store.get_task(task_id)
if kind == "agent" and task.get("claimed_by") != agent["agent_id"]:
raise TaskProtocolError("FORBIDDEN", 403)
return task
def events(self, task_id: str, headers: Mapping[str, str]) -> dict[str, Any]:
self.authenticate_control(headers)
return {"task_id": task_id, "events": self.store.get_events(task_id)}
class Handler(BaseHTTPRequestHandler):
app: AgentTaskApplication | None = None
server_version = "AIOSAgentTaskAPI/1"
def log_message(self, _format: str, *_args: Any) -> None:
return
def _headers(self) -> dict[str, str]:
return {key: value for key, value in self.headers.items()}
def _json(self, value: Any, status: int = 200) -> None:
payload = json.dumps(value, ensure_ascii=False, sort_keys=True).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Cache-Control", "no-store")
self.send_header("Content-Length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
def _body(self) -> dict[str, Any]:
try:
size = int(self.headers.get("Content-Length", "0"))
except ValueError as exc:
raise TaskProtocolError("CONTENT_LENGTH_INVALID", 400) from exc
if size < 0 or size > MAX_BODY:
raise TaskProtocolError("REQUEST_TOO_LARGE", 413)
raw = self.rfile.read(size) if size else b"{}"
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise TaskProtocolError("JSON_INVALID", 400) from exc
if not isinstance(value, dict):
raise TaskProtocolError("JSON_OBJECT_REQUIRED", 400)
return value
@staticmethod
def _task_route(path: str) -> tuple[str, str | None]:
match = re.fullmatch(
r"/v1/tasks/([A-Za-z0-9][A-Za-z0-9._:-]{2,179})(?:/(claim|heartbeat|result|verify|cancel|events))?",
path,
)
if match:
return match.group(1), match.group(2)
return "", None
def _run(self, callback) -> None:
try:
callback()
except TaskProtocolError as exc:
self._json({"error": exc.code}, exc.http_status)
except Exception as exc:
self._json({"error": "INTERNAL_ERROR", "detail": type(exc).__name__}, 500)
def do_GET(self) -> None:
def action() -> None:
if self.app is None:
raise TaskProtocolError("SERVICE_UNAVAILABLE", 503)
path = urlparse(self.path).path.rstrip("/") or "/"
if path == "/health":
self._json(self.app.health())
return
task_id, suffix = self._task_route(path)
if task_id and suffix is None:
self._json(self.app.get_task(task_id, self._headers()))
return
if task_id and suffix == "events":
self._json(self.app.events(task_id, self._headers()))
return
self._json({"error": "NOT_FOUND"}, 404)
self._run(action)
def do_POST(self) -> None:
def action() -> None:
if self.app is None:
raise TaskProtocolError("SERVICE_UNAVAILABLE", 503)
path = urlparse(self.path).path.rstrip("/") or "/"
body = self._body()
if path == "/v1/agents/register":
self._json(self.app.register_agent(self._headers(), body), 201)
return
if path == "/v1/tasks":
self._json(self.app.create_task(self._headers(), body), 201)
return
if path == "/v1/tasks/poll":
self._json(self.app.poll(self._headers(), body))
return
task_id, suffix = self._task_route(path)
if not task_id:
self._json({"error": "NOT_FOUND"}, 404)
return
if suffix == "claim":
self._json(self.app.claim(task_id, self._headers(), body))
elif suffix == "heartbeat":
self._json(self.app.heartbeat(task_id, self._headers(), body))
elif suffix == "result":
self._json(self.app.result(task_id, self._headers(), body))
elif suffix == "verify":
self._json(self.app.verify(task_id, self._headers(), body))
elif suffix == "cancel":
self._json(self.app.cancel(task_id, self._headers(), body))
else:
self._json({"error": "NOT_FOUND"}, 404)
self._run(action)
def serve(config: APIConfig | None = None) -> None:
config = config or APIConfig.from_env()
application = AgentTaskApplication(config)
Handler.app = application
ThreadingHTTPServer((config.host, config.port), Handler).serve_forever()
if __name__ == "__main__":
serve()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,870 @@
#!/usr/bin/env python3
"""Durable online task protocol for external AIOS agents.
The store is deliberately independent from Git/Gitea mutation. It provides a
small durable state machine for publishing work, atomic claim/lease ownership,
heartbeat, idempotent result submission, explicit server-side verification,
lease recovery, and audit events.
No plaintext agent credential or task lease token is persisted.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import hashlib
import hmac
import json
from pathlib import Path
import secrets
import sqlite3
from typing import Any, Callable, Iterable, Mapping
import uuid
from aios_capability_contract import has_capabilities
from aios_online_agent_execution import (
ExecutionProjectionError,
validate_execution_envelope,
)
TERMINAL_STATES = frozenset({"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"})
ACTIVE_LEASE_STATES = frozenset({"CLAIMED", "RUNNING"})
TASK_STATES = frozenset({
"QUEUED", "CLAIMED", "RUNNING", "VERIFYING",
"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED",
})
VERIFY_DECISIONS = frozenset({"SUCCEEDED", "NEEDS_REWORK", "FAILED"})
GOVERNED_WRITE_CAPABILITIES = frozenset({
"governed.write.branch",
"governed.write.file",
})
EXECUTION_PROJECTION_SCHEMA = "aios.online-agent.task-projection.v1"
def _validate_write_execution_binding(
task_id: str,
required_capabilities: list[str],
metadata: Mapping[str, Any],
) -> None:
required = set(required_capabilities)
if not required.intersection(GOVERNED_WRITE_CAPABILITIES):
return
execution = metadata.get("execution")
projection = metadata.get("execution_projection")
if not isinstance(execution, Mapping) or not isinstance(projection, Mapping):
raise TaskProtocolError("GOVERNED_EXECUTION_BINDING_REQUIRED", 409)
try:
canonical = validate_execution_envelope(dict(execution))
except (ExecutionProjectionError, TypeError, ValueError, KeyError) as exc:
raise TaskProtocolError("EXECUTION_ENVELOPE_INVALID", 409) from exc
if canonical.get("task_id") != task_id:
raise TaskProtocolError("TASK_BINDING_CONFLICT", 409)
if set(canonical.get("capability_scope") or ()) != required:
raise TaskProtocolError("EXECUTION_CAPABILITY_SCOPE_CONFLICT", 409)
if (
projection.get("schema") != EXECUTION_PROJECTION_SCHEMA
or projection.get("task_id") != task_id
or projection.get("job_id") != canonical.get("job_id")
or projection.get("execution_digest") != canonical.get("execution_digest")
):
raise TaskProtocolError("EXECUTION_PROJECTION_INVALID", 409)
class TaskProtocolError(RuntimeError):
def __init__(self, code: str, http_status: int = 409) -> None:
super().__init__(code)
self.code = code
self.http_status = http_status
@dataclass(frozen=True, slots=True)
class Principal:
kind: str
identifier: str
def utcnow() -> datetime:
return datetime.now(timezone.utc)
def iso(value: datetime) -> str:
return value.astimezone(timezone.utc).isoformat()
def parse_time(value: str | None) -> datetime | None:
if not value:
return None
parsed = datetime.fromisoformat(value)
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed.astimezone(timezone.utc)
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def _string_list(value: Iterable[object] | None, field: str, *, max_items: int = 128) -> list[str]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, Iterable):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result: list[str] = []
for item in value:
text = str(item).strip()
if not text or len(text) > 160:
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result.append(text)
if len(result) > max_items:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 400)
return sorted(set(result))
def _json_object(value: object | None, field: str, *, max_bytes: int = 128 * 1024) -> dict[str, Any]:
if value is None:
return {}
if not isinstance(value, Mapping):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result = dict(value)
if len(canonical_json(result).encode("utf-8")) > max_bytes:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 413)
return result
def _criteria(value: object | None) -> list[Any]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, list):
raise TaskProtocolError("SUCCESS_CRITERIA_INVALID", 400)
if len(value) > 128:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
if len(canonical_json(value).encode("utf-8")) > 128 * 1024:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
return list(value)
class TaskStore:
"""SQLite-backed durable task store.
SQLite BEGIN IMMEDIATE serializes claims so two agents cannot successfully
claim the same queued task. WAL mode allows concurrent readers.
"""
def __init__(
self,
path: str | Path,
*,
now: Callable[[], datetime] = utcnow,
token_pepper: str = "",
) -> None:
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.now = now
self.token_pepper = token_pepper
self._initialize()
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.path, timeout=10, isolation_level=None)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA journal_mode=WAL")
connection.execute("PRAGMA synchronous=FULL")
connection.execute("PRAGMA busy_timeout=10000")
return connection
def _initialize(self) -> None:
with self._connect() as connection:
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY,
agent_type TEXT NOT NULL,
agent_version TEXT NOT NULL,
capabilities_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
token_hash TEXT NOT NULL UNIQUE,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY,
goal TEXT NOT NULL,
instructions TEXT NOT NULL,
success_criteria_json TEXT NOT NULL,
required_capabilities_json TEXT NOT NULL,
constraints_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
deadline TEXT,
priority INTEGER NOT NULL,
max_attempts INTEGER NOT NULL,
attempt INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL,
claimed_by TEXT,
lease_token_hash TEXT,
lease_expires_at TEXT,
result_json TEXT,
result_digest TEXT,
result_idempotency_key TEXT,
verification_json TEXT,
revision INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(claimed_by) REFERENCES agents(agent_id)
);
CREATE TABLE IF NOT EXISTS idempotency (
scope TEXT NOT NULL,
key TEXT NOT NULL,
payload_digest TEXT NOT NULL,
response_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY(scope, key)
);
CREATE TABLE IF NOT EXISTS events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT,
agent_id TEXT,
event_type TEXT NOT NULL,
payload_json TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_tasks_queue
ON tasks(status, priority DESC, created_at);
CREATE INDEX IF NOT EXISTS idx_tasks_lease
ON tasks(status, lease_expires_at);
CREATE INDEX IF NOT EXISTS idx_events_task
ON events(task_id, event_id);
"""
)
def _token_hash(self, token: str) -> str:
return hashlib.sha256((self.token_pepper + "\0" + token).encode("utf-8")).hexdigest()
@staticmethod
def _row_task(row: sqlite3.Row, *, include_result: bool = True) -> dict[str, Any]:
value: dict[str, Any] = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"deadline": row["deadline"],
"priority": row["priority"],
"max_attempts": row["max_attempts"],
"attempt": row["attempt"],
"status": row["status"],
"claimed_by": row["claimed_by"],
"lease_expires_at": row["lease_expires_at"],
"revision": row["revision"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
}
if include_result:
value["result"] = json.loads(row["result_json"]) if row["result_json"] else None
value["verification"] = (
json.loads(row["verification_json"]) if row["verification_json"] else None
)
return value
def _event(
self,
connection: sqlite3.Connection,
event_type: str,
*,
task_id: str | None = None,
agent_id: str | None = None,
payload: Mapping[str, Any] | None = None,
) -> None:
connection.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at)"
" VALUES(?,?,?,?,?)",
(task_id, agent_id, event_type, canonical_json(dict(payload or {})), iso(self.now())),
)
def register_agent(
self,
*,
agent_id: str,
agent_type: str,
agent_version: str,
capabilities: Iterable[object],
metadata: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
agent_id = agent_id.strip()
agent_type = agent_type.strip()
agent_version = agent_version.strip()
if not agent_id or len(agent_id) > 160 or not agent_type or not agent_version:
raise TaskProtocolError("AGENT_IDENTITY_INVALID", 400)
caps = _string_list(capabilities, "capabilities")
meta = _json_object(metadata, "metadata")
token = "agt_" + secrets.token_urlsafe(32)
now = iso(self.now())
try:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
connection.execute(
"INSERT INTO agents(agent_id,agent_type,agent_version,capabilities_json,"
"metadata_json,token_hash,status,created_at,last_seen_at)"
" VALUES(?,?,?,?,?,?,?,?,?)",
(
agent_id,
agent_type,
agent_version,
canonical_json(caps),
canonical_json(meta),
self._token_hash(token),
"ACTIVE",
now,
now,
),
)
self._event(
connection,
"AGENT_REGISTERED",
agent_id=agent_id,
payload={"agent_type": agent_type, "agent_version": agent_version},
)
connection.commit()
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("AGENT_ALREADY_EXISTS", 409) from exc
return {
"agent_id": agent_id,
"agent_type": agent_type,
"agent_version": agent_version,
"capabilities": caps,
"agent_token": token,
"credential_returned_once": True,
}
def authenticate_agent(self, token: str) -> dict[str, Any]:
if not token:
raise TaskProtocolError("UNAUTHORIZED", 401)
token_hash = self._token_hash(token)
with self._connect() as connection:
row = connection.execute(
"SELECT * FROM agents WHERE token_hash=? AND status='ACTIVE'",
(token_hash,),
).fetchone()
if row is None:
raise TaskProtocolError("UNAUTHORIZED", 401)
connection.execute(
"UPDATE agents SET last_seen_at=? WHERE agent_id=?",
(iso(self.now()), row["agent_id"]),
)
return {
"agent_id": row["agent_id"],
"agent_type": row["agent_type"],
"agent_version": row["agent_version"],
"capabilities": json.loads(row["capabilities_json"]),
"metadata": json.loads(row["metadata_json"]),
}
def _idempotent_lookup(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
) -> dict[str, Any] | None:
row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(scope, key),
).fetchone()
if row is None:
return None
if not hmac.compare_digest(row["payload_digest"], payload_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
return json.loads(row["response_json"])
def _idempotent_store(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
response: Mapping[str, Any],
) -> None:
connection.execute(
"INSERT INTO idempotency(scope,key,payload_digest,response_json,created_at)"
" VALUES(?,?,?,?,?)",
(scope, key, payload_digest, canonical_json(dict(response)), iso(self.now())),
)
def create_task(
self,
*,
task_id: str | None,
goal: str,
instructions: str,
success_criteria: object | None,
required_capabilities: Iterable[object] | None = None,
constraints: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
deadline: str | None = None,
priority: int = 0,
max_attempts: int = 3,
idempotency_key: str,
) -> dict[str, Any]:
task_id = (task_id or ("task-" + uuid.uuid4().hex)).strip()
goal = goal.strip()
instructions = instructions.strip()
idempotency_key = idempotency_key.strip()
if not task_id or len(task_id) > 180 or not goal or not instructions:
raise TaskProtocolError("TASK_BINDING_INVALID", 400)
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
if not isinstance(priority, int) or isinstance(priority, bool) or not -100 <= priority <= 100:
raise TaskProtocolError("PRIORITY_INVALID", 400)
if not isinstance(max_attempts, int) or isinstance(max_attempts, bool) or not 1 <= max_attempts <= 20:
raise TaskProtocolError("MAX_ATTEMPTS_INVALID", 400)
parsed_deadline = parse_time(deadline)
if parsed_deadline and parsed_deadline <= self.now():
raise TaskProtocolError("DEADLINE_INVALID", 400)
criteria = _criteria(success_criteria)
required = _string_list(required_capabilities, "required_capabilities")
constraints_value = _json_object(constraints, "constraints")
metadata_value = _json_object(metadata, "metadata")
_validate_write_execution_binding(task_id, required, metadata_value)
payload = {
"task_id": task_id,
"goal": goal,
"instructions": instructions,
"success_criteria": criteria,
"required_capabilities": required,
"constraints": constraints_value,
"metadata": metadata_value,
"deadline": iso(parsed_deadline) if parsed_deadline else None,
"priority": priority,
"max_attempts": max_attempts,
}
payload_digest = digest_json(payload)
now = iso(self.now())
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
replay = self._idempotent_lookup(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
)
if replay is not None:
connection.commit()
return {**replay, "idempotent": True}
try:
connection.execute(
"INSERT INTO tasks(task_id,goal,instructions,success_criteria_json,"
"required_capabilities_json,constraints_json,metadata_json,deadline,priority,"
"max_attempts,attempt,status,revision,created_at,updated_at)"
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
task_id,
goal,
instructions,
canonical_json(criteria),
canonical_json(required),
canonical_json(constraints_value),
canonical_json(metadata_value),
payload["deadline"],
priority,
max_attempts,
0,
"QUEUED",
1,
now,
now,
),
)
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("TASK_ID_CONFLICT", 409) from exc
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
result = self._row_task(row)
self._idempotent_store(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
response=result,
)
self._event(connection, "TASK_CREATED", task_id=task_id, payload={"priority": priority})
connection.commit()
return result
def _expire_locked(self, connection: sqlite3.Connection) -> None:
now_dt = self.now()
now = iso(now_dt)
rows = connection.execute(
"SELECT * FROM tasks WHERE status IN ('QUEUED','CLAIMED','RUNNING')"
).fetchall()
for row in rows:
deadline = parse_time(row["deadline"])
if deadline is not None and deadline <= now_dt:
connection.execute(
"UPDATE tasks SET status='EXPIRED',lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(now, row["task_id"]),
)
self._event(connection, "TASK_EXPIRED", task_id=row["task_id"])
continue
if row["status"] in ACTIVE_LEASE_STATES:
lease_expires = parse_time(row["lease_expires_at"])
if lease_expires is not None and lease_expires <= now_dt:
if row["attempt"] >= row["max_attempts"]:
next_state = "FAILED"
else:
next_state = "QUEUED"
if next_state == "FAILED":
connection.execute(
"UPDATE tasks SET status=?,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
self._event(
connection,
"LEASE_EXPIRED",
task_id=row["task_id"],
agent_id=row["claimed_by"],
payload={"next_state": next_state, "attempt": row["attempt"]},
)
def expire_leases(self) -> int:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
before = connection.total_changes
self._expire_locked(connection)
changed = connection.total_changes - before
connection.commit()
return changed
def poll_tasks(self, *, agent_id: str, limit: int = 20) -> list[dict[str, Any]]:
if not 1 <= int(limit) <= 100:
raise TaskProtocolError("LIMIT_INVALID", 400)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._expire_locked(connection)
agent = connection.execute(
"SELECT capabilities_json FROM agents WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if agent is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
capabilities = json.loads(agent["capabilities_json"])
rows = connection.execute(
"SELECT * FROM tasks WHERE status='QUEUED' ORDER BY priority DESC,created_at ASC"
).fetchall()
result = []
for row in rows:
required = json.loads(row["required_capabilities_json"])
if has_capabilities(capabilities, required):
result.append(self._row_task(row, include_result=False))
if len(result) >= int(limit):
break
connection.commit()
return result
def _agent_capabilities(self, connection: sqlite3.Connection, agent_id: str) -> set[str]:
row = connection.execute(
"SELECT capabilities_json FROM agents WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if row is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
return set(json.loads(row["capabilities_json"]))
def claim_task(
self,
*,
task_id: str,
agent_id: str,
lease_seconds: int = 300,
) -> dict[str, Any]:
if not isinstance(lease_seconds, int) or isinstance(lease_seconds, bool) or not 30 <= lease_seconds <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
lease_token = "lease_" + secrets.token_urlsafe(32)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] != "QUEUED":
raise TaskProtocolError("TASK_NOT_CLAIMABLE", 409)
capabilities = self._agent_capabilities(connection, agent_id)
required = json.loads(row["required_capabilities_json"])
if not has_capabilities(capabilities, required):
raise TaskProtocolError("AGENT_CAPABILITY_MISMATCH", 403)
attempt = int(row["attempt"]) + 1
if attempt > int(row["max_attempts"]):
raise TaskProtocolError("TASK_ATTEMPTS_EXHAUSTED", 409)
expires = self.now() + timedelta(seconds=lease_seconds)
now = iso(self.now())
updated = connection.execute(
"UPDATE tasks SET status='CLAIMED',claimed_by=?,lease_token_hash=?,"
"lease_expires_at=?,attempt=?,revision=revision+1,updated_at=? "
"WHERE task_id=? AND status='QUEUED'",
(
agent_id,
self._token_hash(lease_token),
iso(expires),
attempt,
now,
task_id,
),
)
if updated.rowcount != 1:
raise TaskProtocolError("TASK_CLAIM_CONFLICT", 409)
self._event(
connection,
"TASK_CLAIMED",
task_id=task_id,
agent_id=agent_id,
payload={"attempt": attempt, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return {
**self._row_task(row, include_result=False),
"lease_token": lease_token,
"lease_token_returned_once": True,
}
def _validate_lease(
self,
connection: sqlite3.Connection,
*,
task_id: str,
agent_id: str,
lease_token: str,
allowed_states: frozenset[str] = ACTIVE_LEASE_STATES,
) -> sqlite3.Row:
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] not in allowed_states:
raise TaskProtocolError("TASK_LEASE_NOT_ACTIVE", 409)
if row["claimed_by"] != agent_id:
raise TaskProtocolError("TASK_LEASE_OWNER_MISMATCH", 403)
if not row["lease_token_hash"] or not hmac.compare_digest(
row["lease_token_hash"], self._token_hash(lease_token)
):
raise TaskProtocolError("TASK_LEASE_TOKEN_INVALID", 403)
expires = parse_time(row["lease_expires_at"])
if expires is None or expires <= self.now():
raise TaskProtocolError("TASK_LEASE_EXPIRED", 409)
return row
def heartbeat(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
progress: Mapping[str, Any] | None = None,
lease_seconds: int = 300,
) -> dict[str, Any]:
if not 30 <= int(lease_seconds) <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
progress_value = _json_object(progress, "progress", max_bytes=64 * 1024)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
expires = self.now() + timedelta(seconds=int(lease_seconds))
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='RUNNING',lease_expires_at=?,revision=revision+1,"
"updated_at=? WHERE task_id=?",
(iso(expires), now, task_id),
)
self._event(
connection,
"TASK_HEARTBEAT",
task_id=task_id,
agent_id=agent_id,
payload={"progress": progress_value, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row, include_result=False)
def submit_result(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
idempotency_key: str,
result: Mapping[str, Any],
) -> dict[str, Any]:
idempotency_key = idempotency_key.strip()
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
result_value = _json_object(result, "result", max_bytes=1024 * 1024)
result_digest = digest_json(result_value)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["result_idempotency_key"] == idempotency_key:
if not row["result_digest"] or not hmac.compare_digest(row["result_digest"], result_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
connection.commit()
return {**self._row_task(row), "idempotent": True}
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='VERIFYING',result_json=?,result_digest=?,"
"result_idempotency_key=?,lease_token_hash=NULL,lease_expires_at=NULL,"
"revision=revision+1,updated_at=? WHERE task_id=?",
(
canonical_json(result_value),
result_digest,
idempotency_key,
now,
task_id,
),
)
self._event(
connection,
"RESULT_SUBMITTED",
task_id=task_id,
agent_id=agent_id,
payload={"result_digest": result_digest, "idempotency_key": idempotency_key},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row)
def verify_task(
self,
*,
task_id: str,
decision: str,
verification: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
decision = decision.strip().upper()
if decision not in VERIFY_DECISIONS:
raise TaskProtocolError("VERIFICATION_DECISION_INVALID", 400)
verification_value = _json_object(verification, "verification")
verification_value = {"decision": decision, **verification_value}
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] != "VERIFYING":
raise TaskProtocolError("TASK_NOT_VERIFYING", 409)
if decision == "SUCCEEDED":
next_state = "SUCCEEDED"
elif decision == "FAILED":
next_state = "FAILED"
elif int(row["attempt"]) >= int(row["max_attempts"]):
next_state = "FAILED"
verification_value["reason"] = verification_value.get(
"reason", "MAX_ATTEMPTS_EXHAUSTED"
)
else:
next_state = "QUEUED"
now = iso(self.now())
if next_state in {"SUCCEEDED", "FAILED"}:
connection.execute(
"UPDATE tasks SET status=?,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
self._event(
connection,
"TASK_VERIFIED",
task_id=task_id,
agent_id=row["claimed_by"],
payload={"decision": decision, "next_state": next_state},
)
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def cancel_task(self, *, task_id: str, reason: str = "") -> dict[str, Any]:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] in TERMINAL_STATES:
connection.commit()
return self._row_task(row)
connection.execute(
"UPDATE tasks SET status='CANCELLED',lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(iso(self.now()), task_id),
)
self._event(connection, "TASK_CANCELLED", task_id=task_id, payload={"reason": reason})
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def get_task(self, task_id: str) -> dict[str, Any]:
self.expire_leases()
with self._connect() as connection:
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
return self._row_task(row)
def get_events(self, task_id: str, *, limit: int = 200) -> list[dict[str, Any]]:
with self._connect() as connection:
rows = connection.execute(
"SELECT * FROM events WHERE task_id=? ORDER BY event_id DESC LIMIT ?",
(task_id, max(1, min(int(limit), 1000))),
).fetchall()
return [
{
"event_id": row["event_id"],
"task_id": row["task_id"],
"agent_id": row["agent_id"],
"event_type": row["event_type"],
"payload": json.loads(row["payload_json"]),
"created_at": row["created_at"],
}
for row in reversed(rows)
]
@@ -0,0 +1,655 @@
"""CAS-bound projection from an unprivileged planner to a host coordinator."""
from __future__ import annotations
from dataclasses import dataclass
import hmac
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import os
from pathlib import Path
import re
import sqlite3
from typing import Any, Callable, Mapping, Sequence
import urllib.error
import urllib.request
from aios_agent_task_initial_execution_projection import (
InitialExecutionProjectionError,
InitialExecutionProjectionStore,
JOB_RE,
KEY_RE,
MAX_BODY,
SHA_RE,
TASK_RE,
_canonical,
_now,
_required_authorization_id,
_require_string,
_sha,
load_control_token,
)
from aios_online_agent_execution import (
GovernedExecutionBinding,
validate_execution_envelope,
)
RUNTIME_CAPABILITY = "agent.runtime.openclaw"
COORDINATOR_CAPABILITY = "engineering.autonomous"
COORDINATOR_CAPABILITIES = [RUNTIME_CAPABILITY, COORDINATOR_CAPABILITY]
@dataclass(frozen=True)
class SupervisorCoordinatorProjectionRequest:
expected_status: str
expected_revision: int
expected_attempt: int
expected_metadata_sha256: str
expected_required_capabilities_sha256: str
previous_verification_sha256: str
expected_planner_result_sha256: str
expected_old_execution_digest: str
expected_new_execution_digest: str
expected_job_id: str
authorization_id: str
idempotency_key: str
reason: str
@classmethod
def from_mapping(
cls, value: Mapping[str, Any]
) -> "SupervisorCoordinatorProjectionRequest":
allowed = {
"expected_status",
"expected_revision",
"expected_attempt",
"expected_metadata_sha256",
"expected_required_capabilities_sha256",
"previous_verification_sha256",
"expected_planner_result_sha256",
"expected_old_execution_digest",
"expected_new_execution_digest",
"expected_job_id",
"authorization_id",
"idempotency_key",
"reason",
}
if set(value) != allowed:
raise InitialExecutionProjectionError(
"SUPERVISOR_COORDINATOR_PROJECTION_FIELDS_INVALID", 400
)
status = _require_string(value["expected_status"], "EXPECTED_STATUS_INVALID")
revision = value["expected_revision"]
attempt = value["expected_attempt"]
if (
status != "QUEUED"
or not isinstance(revision, int)
or isinstance(revision, bool)
or revision < 2
or not isinstance(attempt, int)
or isinstance(attempt, bool)
or attempt < 1
):
raise InitialExecutionProjectionError(
"SUPERVISOR_COORDINATOR_PROJECTION_STATE_INVALID", 400
)
names = (
"expected_metadata_sha256",
"expected_required_capabilities_sha256",
"previous_verification_sha256",
"expected_planner_result_sha256",
"expected_old_execution_digest",
"expected_new_execution_digest",
)
digests = {
name: _require_string(value[name], name.upper() + "_INVALID", SHA_RE)
for name in names
}
job_id = _require_string(value["expected_job_id"], "JOB_ID_INVALID", JOB_RE)
authorization_id = _require_string(
value["authorization_id"], "AUTHORIZATION_ID_INVALID", KEY_RE
)
idempotency_key = _require_string(
value["idempotency_key"], "IDEMPOTENCY_KEY_INVALID", KEY_RE
)
reason = _require_string(value["reason"], "PROJECTION_REASON_INVALID")
if len(reason) > 512:
raise InitialExecutionProjectionError("PROJECTION_REASON_INVALID", 400)
return cls(
status,
revision,
attempt,
digests["expected_metadata_sha256"],
digests["expected_required_capabilities_sha256"],
digests["previous_verification_sha256"],
digests["expected_planner_result_sha256"],
digests["expected_old_execution_digest"],
digests["expected_new_execution_digest"],
job_id,
authorization_id,
idempotency_key,
reason,
)
def public(self) -> dict[str, Any]:
return {
"expected_status": self.expected_status,
"expected_revision": self.expected_revision,
"expected_attempt": self.expected_attempt,
"expected_metadata_sha256": self.expected_metadata_sha256,
"expected_required_capabilities_sha256": self.expected_required_capabilities_sha256,
"previous_verification_sha256": self.previous_verification_sha256,
"expected_planner_result_sha256": self.expected_planner_result_sha256,
"expected_old_execution_digest": self.expected_old_execution_digest,
"expected_new_execution_digest": self.expected_new_execution_digest,
"expected_job_id": self.expected_job_id,
"authorization_id": self.authorization_id,
"idempotency_key": self.idempotency_key,
"reason": self.reason,
}
class CanonicalCoordinatorExecutionResolver:
def __init__(self, base_url: str, control_token: str) -> None:
self.base_url = base_url.rstrip("/")
self.control_token = control_token
def __call__(
self,
task_id: str,
expected_job_id: str,
runtime_input: Mapping[str, Any],
) -> dict[str, Any]:
body = {
"task_id": task_id,
"job_id": expected_job_id,
"required_capabilities": COORDINATOR_CAPABILITIES,
"execution": {
"action": "agent_runtime",
"adapter": "openclaw",
"capability_scope": COORDINATOR_CAPABILITIES,
"runtime_input": dict(runtime_input),
},
}
request = urllib.request.Request(
self.base_url + "/v2/execution-envelope",
method="POST",
data=_canonical(body),
headers={
"Authorization": "Bearer " + self.control_token,
"Content-Type": "application/json",
"Accept": "application/json",
},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
raw, status = response.read(), response.status
except urllib.error.HTTPError as exc:
raw, status = exc.read(), exc.code
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise InitialExecutionProjectionError(
"CANONICAL_COORDINATOR_EXECUTION_RESPONSE_INVALID", 502
) from exc
if not isinstance(value, Mapping):
raise InitialExecutionProjectionError(
"CANONICAL_COORDINATOR_EXECUTION_RESPONSE_INVALID", 502
)
if status != 200:
raise InitialExecutionProjectionError(
str(value.get("error") or "CANONICAL_COORDINATOR_EXECUTION_FAILED"),
502 if status >= 500 else 409,
)
return dict(value)
class SupervisorCoordinatorProjectionStore(InitialExecutionProjectionStore):
def __init__(
self,
db_path: Path,
resolver: Callable[[str, str, Mapping[str, Any]], Mapping[str, Any]],
) -> None:
super().__init__(db_path, resolver) # type: ignore[arg-type]
self.coordinator_resolver = resolver
@staticmethod
def _ensure_table(connection: sqlite3.Connection) -> None:
connection.execute(
"""
CREATE TABLE IF NOT EXISTS task_supervisor_coordinator_projections (
task_id TEXT NOT NULL,
idempotency_key TEXT NOT NULL,
request_sha256 TEXT NOT NULL,
authorization_id TEXT NOT NULL,
expected_job_id TEXT NOT NULL,
planner_result_sha256 TEXT NOT NULL,
old_execution_digest TEXT NOT NULL,
new_execution_digest TEXT NOT NULL,
expected_revision INTEGER NOT NULL,
applied_revision INTEGER NOT NULL,
attempt INTEGER NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY(task_id,idempotency_key)
)
"""
)
@classmethod
def _planner_event_chain(
cls, connection: sqlite3.Connection, task_id: str
) -> None:
table, columns = cls._event_table(connection)
ordering = "event_id" if "event_id" in columns else "rowid"
rows = connection.execute(
f'SELECT event_type,payload_json FROM "{table}" '
f'WHERE task_id=? ORDER BY {ordering}',
(task_id,),
).fetchall()
events = [str(row[0]) for row in rows]
try:
projection = len(events) - 1 - events[::-1].index(
"TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZED"
)
except ValueError as exc:
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_PROJECTION_EVENT_MISSING", 409
) from exc
tail = events[projection + 1 :]
if (
len(tail) < 4
or tail[0] != "TASK_CLAIMED"
or tail.count("TASK_CLAIMED") != 1
or tail.count("RESULT_SUBMITTED") != 1
or tail.count("TASK_VERIFIED") != 1
or tail[-1] != "TASK_VERIFIED"
):
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_EVENT_CHAIN_INVALID", 409
)
result = tail.index("RESULT_SUBMITTED")
if (
not any(event == "TASK_HEARTBEAT" for event in tail[1:result])
or any(
event not in {
"TASK_CLAIMED",
"TASK_HEARTBEAT",
"RESULT_SUBMITTED",
"TASK_VERIFIED",
}
for event in tail
)
):
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_EVENT_CHAIN_INVALID", 409
)
verification_event = json.loads(rows[-1][1])
if (
verification_event.get("decision") != "NEEDS_REWORK"
or verification_event.get("next_state") != "QUEUED"
):
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_NEEDS_REWORK_EVENT_REQUIRED", 409
)
def _load(
self,
connection: sqlite3.Connection,
task_id: str,
request: SupervisorCoordinatorProjectionRequest,
) -> tuple[sqlite3.Row, dict[str, Any], dict[str, Any]]:
task = connection.execute(
"SELECT * FROM tasks WHERE task_id=?", (task_id,)
).fetchone()
if task is None:
raise InitialExecutionProjectionError("TASK_NOT_FOUND", 404)
if (
task["status"] != request.expected_status
or int(task["revision"]) != request.expected_revision
or int(task["attempt"]) != request.expected_attempt
):
raise InitialExecutionProjectionError(
"SUPERVISOR_COORDINATOR_PROJECTION_CAS_MISMATCH", 409
)
if (
task["claimed_by"] not in (None, "")
or task["lease_token_hash"] not in (None, "")
or task["lease_expires_at"] not in (None, "")
):
raise InitialExecutionProjectionError(
"SUPERVISOR_COORDINATOR_ACTIVE_LEASE_DENY", 409
)
metadata = self._json_value(task["metadata_json"], "TASK_METADATA_INVALID")
capabilities = self._json_value(
task["required_capabilities_json"], "TASK_CAPABILITIES_INVALID"
)
result = self._json_value(task["result_json"], "PLANNER_RESULT_INVALID")
verification = self._json_value(
task["verification_json"], "PLANNER_VERIFICATION_INVALID"
)
if (
not isinstance(metadata, Mapping)
or not isinstance(capabilities, list)
or not isinstance(result, Mapping)
or not isinstance(verification, Mapping)
):
raise InitialExecutionProjectionError(
"SUPERVISOR_COORDINATOR_STATE_INVALID", 409
)
metadata, result, verification = dict(metadata), dict(result), dict(verification)
if _sha(metadata) != request.expected_metadata_sha256:
raise InitialExecutionProjectionError("TASK_METADATA_DRIFT", 409)
if _sha(capabilities) != request.expected_required_capabilities_sha256:
raise InitialExecutionProjectionError("TASK_CAPABILITIES_DRIFT", 409)
if _sha(result) != request.expected_planner_result_sha256:
raise InitialExecutionProjectionError("PLANNER_RESULT_DRIFT", 409)
if _sha(verification) != request.previous_verification_sha256:
raise InitialExecutionProjectionError("PLANNER_VERIFICATION_DRIFT", 409)
old = metadata.get("execution")
if not isinstance(old, Mapping):
raise InitialExecutionProjectionError("TASK_EXECUTION_METADATA_MISSING", 409)
old_execution = validate_execution_envelope(dict(old))
if (
old_execution.get("action") != "agent_runtime"
or old_execution.get("adapter") != "openclaw"
or old_execution.get("capability_scope") != [RUNTIME_CAPABILITY]
or old_execution.get("execution_digest")
!= request.expected_old_execution_digest
or capabilities != [RUNTIME_CAPABILITY]
):
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_EXECUTION_CONTRACT_INVALID", 409
)
expected_result = {
"task_id": task_id,
"job_id": request.expected_job_id,
"status": "SUCCEEDED",
"execution_mode": "agent_runtime",
"agent_runtime": "openclaw",
"execution_digest": request.expected_old_execution_digest,
"control_plane_access_granted_to_runtime": False,
"credential_material_exposed": False,
"secret_material_exposed": False,
"changed_files": [],
"writer_lease": "NONE",
"direct_main_write": False,
"production_write": False,
"force_push": False,
"protected_ref_bypass": False,
}
if any(result.get(name) != expected for name, expected in expected_result.items()):
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_RESULT_CONTRACT_INVALID", 409
)
if verification.get("decision") != "NEEDS_REWORK":
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_NEEDS_REWORK_REQUIRED", 409
)
self._planner_event_chain(connection, task_id)
return task, metadata, result
def project(
self,
task_id: str,
request: SupervisorCoordinatorProjectionRequest,
) -> dict[str, Any]:
task_id = _require_string(task_id, "TASK_ID_INVALID", TASK_RE)
if request.authorization_id != _required_authorization_id():
raise InitialExecutionProjectionError("AUTHORIZATION_ID_MISMATCH", 403)
request_sha = _sha(request.public())
with self._connect() as connection:
self._ensure_table(connection)
replay = connection.execute(
"SELECT * FROM task_supervisor_coordinator_projections "
"WHERE task_id=? AND idempotency_key=?",
(task_id, request.idempotency_key),
).fetchone()
if replay is not None:
if replay["request_sha256"] != request_sha:
raise InitialExecutionProjectionError("IDEMPOTENCY_CONFLICT", 409)
return {
"task_id": task_id,
"status": "QUEUED",
"attempt": replay["attempt"],
"revision": replay["applied_revision"],
"execution_digest": replay["new_execution_digest"],
"required_capabilities": COORDINATOR_CAPABILITIES,
"idempotent": True,
}
with self._connect() as connection:
task, _, result = self._load(connection, task_id, request)
runtime_input = self._runtime_input(task)
resolved = dict(
self.coordinator_resolver(task_id, request.expected_job_id, runtime_input)
)
binding = GovernedExecutionBinding.from_mapping(resolved)
if binding.task_id != task_id or binding.job_id != request.expected_job_id:
raise InitialExecutionProjectionError("GOVERNED_BINDING_CONFLICT", 409)
result_binding = {
name: result.get(name)
for name in (
"task_id",
"job_id",
"repository",
"base_oid",
"expected_branch_oid",
"target_branch",
"generation",
)
}
expected_binding = {
"task_id": binding.task_id,
"job_id": binding.job_id,
"repository": binding.repository,
"base_oid": binding.base_oid,
"expected_branch_oid": binding.expected_branch_oid,
"target_branch": binding.target_branch,
"generation": binding.generation,
}
if result_binding != expected_binding:
raise InitialExecutionProjectionError(
"SUPERVISOR_PLANNER_BINDING_CONFLICT", 409
)
execution = validate_execution_envelope(resolved, binding=binding)
if (
execution.get("action") != "agent_runtime"
or execution.get("adapter") != "openclaw"
or execution.get("capability_scope") != COORDINATOR_CAPABILITIES
or execution.get("change_set") != []
or execution.get("verification") != []
or execution.get("governance", {}).get("writer_lease") is not False
or execution.get("execution_digest") != request.expected_new_execution_digest
):
raise InitialExecutionProjectionError(
"SUPERVISOR_COORDINATOR_ENVELOPE_INVALID", 409
)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._ensure_table(connection)
task, metadata, _ = self._load(connection, task_id, request)
new_metadata = dict(metadata)
new_metadata["execution"] = execution
new_metadata["execution_projection"] = {
"schema": "aios.online-agent.task-projection.v1",
"task_id": task_id,
"job_id": request.expected_job_id,
"execution_digest": execution["execution_digest"],
}
applied_revision = request.expected_revision + 1
cursor = connection.execute(
"""
UPDATE tasks SET metadata_json=?,required_capabilities_json=?,
revision=?,updated_at=?
WHERE task_id=? AND status=? AND revision=? AND attempt=?
AND claimed_by IS NULL AND lease_token_hash IS NULL
AND lease_expires_at IS NULL AND metadata_json=?
AND required_capabilities_json=? AND result_json=?
AND verification_json=?
""",
(
json.dumps(new_metadata, sort_keys=True, separators=(",", ":")),
json.dumps(COORDINATOR_CAPABILITIES, separators=(",", ":")),
applied_revision,
_now(),
task_id,
request.expected_status,
request.expected_revision,
request.expected_attempt,
task["metadata_json"],
task["required_capabilities_json"],
task["result_json"],
task["verification_json"],
),
)
if cursor.rowcount != 1:
raise InitialExecutionProjectionError(
"SUPERVISOR_COORDINATOR_PROJECTION_CAS_MISMATCH", 409
)
event = {
"schema": "aios.supervisor-coordinator-projection.event.v1",
"task_id": task_id,
"job_id": request.expected_job_id,
"authorization_id": request.authorization_id,
"idempotency_key": request.idempotency_key,
"reason": request.reason,
"old_execution_digest": request.expected_old_execution_digest,
"new_execution_digest": execution["execution_digest"],
"required_capabilities": COORDINATOR_CAPABILITIES,
"previous_revision": request.expected_revision,
"applied_revision": applied_revision,
"attempt": request.expected_attempt,
"request_sha256": request_sha,
"credential_forwarded_to_planner": False,
"writer_lease": False,
"main_write": False,
"production_write": False,
}
table, columns = self._event_table(connection)
values = {
"task_id": task_id,
"event_type": "TASK_SUPERVISOR_COORDINATOR_PROJECTION_AUTHORIZED",
"payload_json": json.dumps(event, sort_keys=True, separators=(",", ":")),
"created_at": _now(),
"agent_id": "owner-control",
}
names = [name for name in values if name in columns]
connection.execute(
f'INSERT INTO "{table}" ({",".join(chr(34)+name+chr(34) for name in names)}) '
f'VALUES ({",".join("?" for _ in names)})',
tuple(values[name] for name in names),
)
connection.execute(
"""
INSERT INTO task_supervisor_coordinator_projections(
task_id,idempotency_key,request_sha256,authorization_id,
expected_job_id,planner_result_sha256,old_execution_digest,
new_execution_digest,expected_revision,applied_revision,
attempt,created_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
task_id,
request.idempotency_key,
request_sha,
request.authorization_id,
request.expected_job_id,
request.expected_planner_result_sha256,
request.expected_old_execution_digest,
execution["execution_digest"],
request.expected_revision,
applied_revision,
request.expected_attempt,
_now(),
),
)
connection.commit()
return {
"task_id": task_id,
"status": "QUEUED",
"attempt": request.expected_attempt,
"revision": applied_revision,
"execution_digest": execution["execution_digest"],
"required_capabilities": COORDINATOR_CAPABILITIES,
"credential_forwarded_to_planner": False,
"idempotent": False,
}
class Handler(BaseHTTPRequestHandler):
store: SupervisorCoordinatorProjectionStore | None = None
control_token: str = ""
def log_message(self, *_args: Any) -> None:
return
def send_json(self, value: Mapping[str, Any], status: int) -> None:
raw = _canonical(dict(value))
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_GET(self) -> None:
if self.path == "/health":
self.send_json(
{"status": "ok", "service": "aios-supervisor-coordinator-projection"},
200,
)
else:
self.send_json({"error": "NOT_FOUND"}, 404)
def do_POST(self) -> None:
match = re.fullmatch(
r"/v1/tasks/([^/]+)/supervisor-coordinator-projection", self.path
)
header = self.headers.get("Authorization", "")
if match is None:
self.send_json({"error": "NOT_FOUND"}, 404)
return
if (
not header.startswith("Bearer ")
or not hmac.compare_digest(header[7:], self.control_token)
):
self.send_json({"error": "UNAUTHORIZED"}, 401)
return
try:
length = int(self.headers.get("Content-Length", "0"))
if length < 1 or length > MAX_BODY:
raise InitialExecutionProjectionError("REQUEST_SIZE_INVALID", 413)
value = json.loads(self.rfile.read(length).decode("utf-8"))
if not isinstance(value, Mapping) or self.store is None:
raise InitialExecutionProjectionError("REQUEST_INVALID", 400)
request = SupervisorCoordinatorProjectionRequest.from_mapping(value)
result = self.store.project(match.group(1), request)
self.send_json(result, 200 if result["idempotent"] else 201)
except InitialExecutionProjectionError as exc:
self.send_json({"error": exc.code}, exc.status)
except (ValueError, UnicodeDecodeError, json.JSONDecodeError):
self.send_json({"error": "REQUEST_INVALID"}, 400)
def serve() -> None:
credential = Path(
os.environ.get(
"AIOS_AGENT_TASK_CONTROL_TOKEN_FILE",
str(
Path(os.environ.get("CREDENTIALS_DIRECTORY", "/run/credentials"))
/ "agent_task_control_token"
),
)
)
token = load_control_token(credential)
bridge = os.environ.get("AIOS_GOVERNED_WRITE_API_URL", "http://127.0.0.1:18892")
db = Path(os.environ.get("AIOS_AGENT_TASK_DB", "/var/lib/aios-agent-task/state.sqlite3"))
Handler.control_token = token
Handler.store = SupervisorCoordinatorProjectionStore(
db, CanonicalCoordinatorExecutionResolver(bridge, token)
)
host = os.environ.get("AIOS_SUPERVISOR_COORDINATOR_PROJECTION_HOST", "127.0.0.1")
port = int(os.environ.get("AIOS_SUPERVISOR_COORDINATOR_PROJECTION_PORT", "18944"))
ThreadingHTTPServer((host, port), Handler).serve_forever()
if __name__ == "__main__":
serve()
@@ -0,0 +1,142 @@
"""Private Unix-socket caller projection and rehydration client.
The broker response body is deliberately public metadata only. The short-lived
credential projection is delivered in a private response header, consumed
inside the model API process, and never returned by an HTTP API or persisted.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import http.client
import json
from pathlib import Path
import socket
from typing import Any, Mapping
CALLER = "chatgpt-governed-implementer-singlehost"
SCOPE = "release:candidate"
GOVERNED_TASK_SCOPE = "governed-task:project"
PROJECTED_SCOPES = frozenset({GOVERNED_TASK_SCOPE, SCOPE})
CREDENTIAL_HEADER = "X-AIOS-Caller-Projection-Credential"
class CallerProjectionError(RuntimeError):
def __init__(self, code: str, status: int = 503) -> None:
super().__init__(code)
self.code = code
self.status = status
@dataclass(frozen=True)
class ProjectionResult:
public: dict[str, Any]
credential_loaded: bool
class _UnixHTTPConnection(http.client.HTTPConnection):
def __init__(self, socket_path: Path, timeout: float) -> None:
super().__init__("localhost", timeout=timeout)
self.socket_path = socket_path
def connect(self) -> None:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
try:
sock.connect(str(self.socket_path))
except Exception:
sock.close()
raise
self.sock = sock
class CallerProjectionClient:
def __init__(self, socket_path: Path, *, timeout: float = 2.0) -> None:
self.socket_path = Path(socket_path)
self.timeout = timeout
@staticmethod
def _idempotency_key(caller: str, scope: str) -> str:
digest = hashlib.sha256((caller + "\0" + scope).encode()).hexdigest()
return "caller-projection-" + digest[:32] + "-v1"
def rehydrate(
self,
caller: str,
scope: str,
request: Mapping[str, Any] | None = None,
) -> ProjectionResult:
if caller != CALLER:
raise CallerProjectionError("CALLER_MISMATCH", 403)
if scope not in PROJECTED_SCOPES:
raise CallerProjectionError("SCOPE_MISMATCH", 403)
body = {
"caller": caller,
"scope": scope,
"idempotency_key": self._idempotency_key(caller, scope),
"request": dict(request or {}),
}
connection = _UnixHTTPConnection(self.socket_path, self.timeout)
try:
connection.request(
"POST",
"/v1/project-caller",
body=json.dumps(body, sort_keys=True, separators=(",", ":")).encode(),
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"X-AIOS-Account": caller,
},
)
response = connection.getresponse()
status = response.status
raw = response.read()
credential = response.getheader(CREDENTIAL_HEADER, "")
except (OSError, TimeoutError, http.client.HTTPException) as exc:
raise CallerProjectionError("PROJECTION_BROKER_UNAVAILABLE", 503) from exc
finally:
connection.close()
try:
value = json.loads(raw.decode("utf-8"))
except Exception as exc:
raise CallerProjectionError("PROJECTION_RESPONSE_INVALID", 503) from exc
if not isinstance(value, Mapping):
raise CallerProjectionError("PROJECTION_RESPONSE_INVALID", 503)
if status != 200:
code = str(value.get("error") or "PROJECTION_DENIED")
raise CallerProjectionError(code, 403 if status < 500 else 503)
if any(
str(key).lower() in {
"credential",
"credential_material",
"token",
"bearer",
"secret",
"password",
}
for key in value
):
raise CallerProjectionError("PROJECTION_SECRET_FIELD_REJECTED", 503)
if value.get("caller") != CALLER or value.get("scope") != scope:
raise CallerProjectionError("PROJECTION_BINDING_INVALID", 503)
if value.get("projection_state") != "ACKED":
raise CallerProjectionError("PROJECTION_STATE_INVALID", 503)
if value.get("credential_state") != "REHYDRATED":
raise CallerProjectionError("CREDENTIAL_REHYDRATION_INVALID", 503)
if value.get("rehydration_state") not in {"GENERATED", "RECOVERED"}:
raise CallerProjectionError("REHYDRATION_STATE_INVALID", 503)
effective_scopes = value.get("effective_scopes")
if (
not isinstance(effective_scopes, list)
or len(effective_scopes) != len(set(effective_scopes))
or not all(isinstance(item, str) for item in effective_scopes)
or scope not in effective_scopes
or not set(effective_scopes).issubset(PROJECTED_SCOPES)
):
raise CallerProjectionError("PROJECTION_SCOPE_INVALID", 503)
if value.get("secret_exposed") is not False:
raise CallerProjectionError("PROJECTION_SECRET_EXPOSURE", 503)
if not isinstance(credential, str) or len(credential) < 32:
raise CallerProjectionError("CREDENTIAL_REHYDRATION_MISSING", 503)
return ProjectionResult(public=dict(value), credential_loaded=True)
@@ -39,6 +39,7 @@ _TASK_ALIASES: dict[str, frozenset[str]] = {
"gpu.train": frozenset({"legacy.gpu.train"}),
"agent.runtime.hermes": frozenset({"agent.runtime.hermes"}),
"agent.runtime.openclaw": frozenset({"agent.runtime.openclaw"}),
"engineering.autonomous": frozenset({"engineering.autonomous"}),
"release:candidate": frozenset({"release:candidate"}),
}
@@ -74,6 +75,7 @@ _WORKER_ALIASES: dict[str, frozenset[str]] = {
"gpu.train": frozenset({"legacy.gpu.train"}),
"agent.runtime.hermes": frozenset({"agent.runtime.hermes"}),
"agent.runtime.openclaw": frozenset({"agent.runtime.openclaw"}),
"engineering.autonomous": frozenset({"engineering.autonomous"}),
"release:candidate": frozenset({"release:candidate"}),
}
@@ -154,6 +156,11 @@ _CAPABILITY_CONTRACTS: dict[str, CapabilityContract] = {
dependencies=("openclaw_runtime",),
health_requirements=("worker_liveness",),
),
"engineering.autonomous": CapabilityContract(
"engineering.autonomous",
dependencies=("agent_task_control_plane",),
health_requirements=("worker_liveness",),
),
"release:candidate": CapabilityContract(
"release:candidate",
min_version=1,
@@ -1,9 +1,10 @@
"""Loopback-only governed Bridge v2 HTTP gateway with durable handlers."""
from __future__ import annotations
from dataclasses import dataclass, replace
from dataclasses import asdict, dataclass, replace
from datetime import datetime, timedelta, timezone
import base64
import hmac
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@@ -12,6 +13,8 @@ import re
import sqlite3
import threading
from typing import Any, Mapping
import urllib.error
import urllib.request
from aios_authority_git_executor import AuthorityGitExecutor
from aios_chatgpt_bridge_v2 import BridgeV2Adapter, BridgeV2Error, OPERATION_REQUIRED_FIELDS
@@ -19,7 +22,9 @@ from aios_continuation_executor import AuthorityGitAdapter, ContinuationCommit,
from aios_continuation_lease import WriterLeaseRequestV2
from aios_control_plane_snapshot import build_control_plane_snapshot
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore
from aios_governed_task_bootstrap import BootstrapError, GovernedTaskBootstrap
from aios_governed_task_bootstrap import BootstrapError, GovernedTaskBinding, GovernedTaskBootstrap, _legacy_safe, _task_key
from aios_online_agent_execution import ExecutionProjectionError, OnlineTaskProjectionClient
from aios_online_agent_plan_store import ExecutionPlanError, ExecutionPlanStore
from governed_production_release import build_executor_config
MAX_BODY = 2 * 1024 * 1024
@@ -27,6 +32,8 @@ ROUTES = {
"/v2/runtime": "getRuntimeStatus",
"/v2/capabilities": "getGovernedWriteCapabilities",
"/v2/governed-task": "createGovernedTask",
"/v2/execution-envelope": "projectOnlineAgentExecution",
"/v2/execution-plan": "registerOnlineAgentExecutionPlan",
"/v2/writer-lease": "acquireWriterLease",
"/v2/writer-lease/bootstrap": "bootstrapWriterLease",
"/v2/writer-lease/renew": "renewWriterLease",
@@ -62,6 +69,9 @@ class RuntimeConfig:
registry_root: Path = Path("/var/lib/aios-gitops/runtime/registry-projection")
baseline_path: Path = Path("/var/lib/aios-gitops-runtime-anchor/baseline/current.json")
health_url: str = "http://127.0.0.1:18927/health"
agent_task_api_url: str = "http://127.0.0.1:18940"
agent_task_control_token_path: Path = Path("/run/credentials/aios-chatgpt-bridge-v2-staging.service/agent_task_control_token")
online_task_projection_enabled: bool = False
def _safe(value: str) -> str:
@@ -74,6 +84,16 @@ class GovernedRuntime:
config.state_root.mkdir(parents=True, exist_ok=True)
self.authority = AuthorityGitExecutor(config.authoritative_git_dir, config.state_root / "worktrees")
self.bootstrap = GovernedTaskBootstrap(self.authority, config.state_root, remote_name=config.remote_name, token_path=config.token_path, ssh_key_path=config.ssh_key_path, api_base=config.api_base)
self.online_task_projector = (
OnlineTaskProjectionClient(config.agent_task_api_url, config.agent_task_control_token_path)
if config.online_task_projection_enabled
else None
)
self.projection_outbox_root = config.state_root / "online-task-projection-outbox"
self.projection_outbox_root.mkdir(parents=True, exist_ok=True)
self.execution_plan_store = ExecutionPlanStore(config.state_root / "online-agent-execution-plans")
self._projection_locks_guard = threading.Lock()
self._projection_locks: dict[str, threading.RLock] = {}
self.runtimes: dict[tuple[str, int], tuple[Any, GitOpsJobController, ContinuationExecutor]] = {}
self.controllers: dict[str, GitOpsJobController] = {}
self._recovery_lock = threading.RLock()
@@ -93,6 +113,54 @@ class GovernedRuntime:
askpass.write_text('#!/bin/sh\ncase "$1" in *Username*) printf "%s\\n" "$GITEA_MACHINE_ACTOR" ;; *) cat "$CREDENTIAL_FILE" ;; esac\n', encoding="utf-8")
askpass.chmod(0o700)
os.environ.setdefault("GIT_ASKPASS", str(askpass)); os.environ.setdefault("GIT_ASKPASS_REQUIRE", "force"); os.environ.setdefault("GIT_TERMINAL_PROMPT", "0"); os.environ.setdefault("GITEA_MACHINE_ACTOR", "aios-autonomy-agent")
if self.online_task_projector is not None:
threading.Thread(target=self._projection_reconciler, name="online-task-projection-reconciler", daemon=True).start()
def _projection_lock(self, task_id: str) -> threading.RLock:
guard = getattr(self, "_projection_locks_guard", None)
if guard is None:
guard = threading.Lock()
self._projection_locks_guard = guard
self._projection_locks = {}
with guard:
return self._projection_locks.setdefault(task_id, threading.RLock())
def _projection_outbox_path(self, task_id: str) -> Path:
return self.projection_outbox_root / (_task_key(task_id) + ".json")
def _write_projection_outbox(self, task_id: str, projection: Mapping[str, Any], binding: Any) -> Path:
path = self._projection_outbox_path(task_id)
temporary = path.with_name(path.name + f".{os.getpid()}.{threading.get_ident()}.tmp")
temporary.write_text(json.dumps({"task": dict(projection), "binding": asdict(binding)}, sort_keys=True, separators=(",", ":")), encoding="utf-8")
temporary.replace(path)
return path
def _deliver_projection_outbox(self, path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, Mapping) or not isinstance(value.get("task"), Mapping) or not isinstance(value.get("binding"), Mapping):
raise RuntimeError("ONLINE_TASK_PROJECTION_OUTBOX_INVALID")
binding = self.bootstrap.execution_binding(GovernedTaskBinding(**dict(value["binding"])))
if self.online_task_projector is None:
raise RuntimeError("ONLINE_TASK_PROJECTION_CONTROL_UNAVAILABLE")
created = self.online_task_projector.create(dict(value["task"]), binding=binding)
path.unlink(missing_ok=True)
return created
def _projection_reconciler(self) -> None:
while True:
for path in sorted(self.projection_outbox_root.glob("*.json")):
try:
value = json.loads(path.read_text(encoding="utf-8"))
binding = value.get("binding") if isinstance(value, Mapping) else None
task_id = str(binding.get("task_id") or "") if isinstance(binding, Mapping) else ""
if not task_id:
continue
with self._projection_lock(task_id):
if path.exists():
self._deliver_projection_outbox(path)
except Exception:
continue
threading.Event().wait(5)
def runtime_status(self) -> dict[str, Any]:
return {
@@ -113,7 +181,10 @@ class GovernedRuntime:
}
def _job_root(self, task_id: str) -> Path:
return self.config.state_root / "jobs" / _safe(task_id)
parent = self.config.state_root / "jobs"
keyed = parent / _task_key(task_id)
legacy = parent / _legacy_safe(task_id)
return keyed if keyed.exists() or not legacy.exists() else legacy
def _build_runtime(
self,
@@ -443,7 +514,85 @@ class GovernedRuntime:
return result
def create_task(self, request: Mapping[str, Any]) -> dict[str, Any]:
result = self.bootstrap.create(request); self.bindings[result["task_id"]] = result; return result
online_request = request.get("online_task")
if online_request is not None and not isinstance(online_request, Mapping):
raise ValueError("ONLINE_TASK_REQUEST_INVALID")
execution_request = request.get("execution")
if execution_request is not None and not isinstance(execution_request, Mapping):
raise ValueError("EXECUTION_REQUEST_INVALID")
online_task_projector = getattr(self, "online_task_projector", None)
projection_required = (
online_request is not None
or execution_request is not None
or request.get("require_online_task_projection") is True
)
if online_task_projector is None:
if projection_required:
raise RuntimeError("ONLINE_TASK_PROJECTION_CONTROL_UNAVAILABLE")
return self.bootstrap.create(request)
online = dict(online_request or {})
online.setdefault("goal", str(request.get("description") or "Governed engineering task"))
online.setdefault("instructions", "Execute only the exact governed binding and return server-verifiable evidence.")
online.setdefault("success_criteria", [{"id": "governed", "description": "Complete the exact governed task lifecycle."}])
online.setdefault("required_capabilities", ["git.read", "git.test", "governed.write.branch", "governed.write.file"])
online.setdefault("priority", int(request.get("priority") or 0))
online.setdefault("max_attempts", int(request.get("max_attempts") or 10))
normalized = dict(request)
normalized["online_task"] = online
normalized["execution"] = dict(execution_request or {})
normalized["require_online_task_projection"] = True
result = self.bootstrap.create(normalized)
self.bindings[result["task_id"]] = result
projection = self.bootstrap.online_task_projection(
result["task_id"],
online,
normalized["execution"],
expected_job_id=str(result.get("job_id") or "") or None,
)
durable_binding = self.bootstrap.get_binding(result["task_id"])
with self._projection_lock(result["task_id"]):
path = self._write_projection_outbox(result["task_id"], projection, durable_binding)
created = self._deliver_projection_outbox(path)
return {
**result,
"online_task_projection": {
"status": "PROJECTED",
"task_id": created["task_id"],
"job_id": result["job_id"],
"task_status": created["status"],
"execution_digest": projection["metadata"]["execution"]["execution_digest"],
},
}
def project_online_agent_execution(self, request: Mapping[str, Any]) -> dict[str, Any]:
capabilities = request.get("required_capabilities") or []
if not isinstance(capabilities, list) or not all(isinstance(item, str) for item in capabilities):
raise ValueError("CAPABILITY_SCOPE_INVALID")
execution = request.get("execution")
if execution is not None and not isinstance(execution, Mapping):
raise ValueError("EXECUTION_REQUEST_INVALID")
task_id = str(request["task_id"])
durable = self.bootstrap.get_binding(task_id)
binding = self.bootstrap.execution_binding(durable)
expected_job_id = str(request.get("job_id") or "") or None
if expected_job_id and expected_job_id != binding.job_id:
raise BootstrapError("JOB_BINDING_CONFLICT")
if execution is None:
planned = self.execution_plan_store.resolve(binding)
if planned is not None:
execution = planned["execution"]
return self.bootstrap.execution_envelope(
task_id,
execution,
required_capabilities=tuple(capabilities),
expected_job_id=expected_job_id,
)
def register_online_agent_execution_plan(self, request: Mapping[str, Any]) -> dict[str, Any]:
task_id = str(request["task_id"])
durable = self.bootstrap.get_binding(task_id)
binding = self.bootstrap.execution_binding(durable)
return self.execution_plan_store.register(binding, request)
def acquire_lease(self, request: Mapping[str, Any]) -> dict[str, Any]:
binding, controller, _ = self._runtime(request)
@@ -1340,7 +1489,19 @@ class GovernedRuntime:
controller = self._controller_for_job(job_id)
row = controller.store.read(job_id)
if action == "get":
return controller.store._receipt(row, duplicate=True).as_dict()
response = controller.store._receipt(row, duplicate=True).as_dict()
try:
with controller.store._connect() as connection:
connection.row_factory = sqlite3.Row
leases = connection.execute(
"SELECT lease_id,job_id,branch,lineage_anchor_oid,current_head_oid,generation,"
"lease_expires_at,state,revision FROM branch_leases WHERE job_id=? ORDER BY revision DESC",
(job_id,),
).fetchall()
except sqlite3.Error as exc:
raise RuntimeError("JOB_DB_UNAVAILABLE") from exc
response["branch_leases"] = [dict(item) for item in leases]
return response
if action == "resume":
return controller.resume(job_id).as_dict()
return self._executor_from_row(row, controller).reconcile(job_id)
@@ -1405,6 +1566,8 @@ class GovernedRuntime:
def handlers(self) -> dict[str, Any]:
return {
"createGovernedTask": self.create_task,
"projectOnlineAgentExecution": self.project_online_agent_execution,
"registerOnlineAgentExecutionPlan": self.register_online_agent_execution_plan,
"acquireWriterLease": self.acquire_lease,
"bootstrapWriterLease": self.bootstrap_writer_lease,
"renewWriterLease": self.renew_writer_lease,
@@ -1434,6 +1597,8 @@ class _HTTPError(RuntimeError): pass
class BridgeHandler(BaseHTTPRequestHandler):
adapter: BridgeV2Adapter | None = None
control_token: str | None = None
agent_task_api_url: str = "http://127.0.0.1:18940"
def log_message(self, *_args: Any) -> None: return
def _json(self, value: Any, status: int = 200) -> None:
raw = json.dumps(value, sort_keys=True, separators=(",", ":")).encode(); self.send_response(status); self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(raw))); self.end_headers(); self.wfile.write(raw)
@@ -1451,7 +1616,50 @@ class BridgeHandler(BaseHTTPRequestHandler):
allowed = set(OPERATION_REQUIRED_FIELDS[operation]) | {"schema", "job_id", "lease_seconds", "create_target_branch", "worktree_mode", "cas_ref_update_required", "expected_branch_oid", "generation", "branch_mode"}
if operation == "commitFilesGoverned": allowed.add("files")
allowed.update({"owner_approval", "execute"})
if operation == "projectOnlineAgentExecution":
allowed.update({"execution", "required_capabilities"})
if operation == "registerOnlineAgentExecutionPlan":
allowed.update({"execution", "source_head_oid", "expected_previous_plan_digest"})
if operation == "createGovernedTask":
allowed.update({"online_task", "execution", "require_online_task_projection", "priority", "max_attempts", "parent_task_id", "parent_job_id"})
if set(body) - allowed: raise _HTTPError("UNKNOWN_FIELDS")
def _authorized(self, operation: str, body: Mapping[str, Any]) -> bool:
header = self.headers.get("Authorization", "")
if not header.startswith("Bearer "):
return False
supplied = header[7:]
if self.control_token and hmac.compare_digest(supplied, self.control_token):
return True
poll = urllib.request.Request(
self.agent_task_api_url.rstrip("/") + "/v1/tasks/poll",
method="POST",
data=b'{"limit":1}',
headers={"Authorization": header, "Content-Type": "application/json", "Accept": "application/json"},
)
try:
with urllib.request.urlopen(poll, timeout=5) as response:
principal = json.loads(response.read().decode("utf-8"))
except (OSError, urllib.error.URLError, urllib.error.HTTPError, ValueError):
return False
agent_id = str(principal.get("agent_id") or "") if isinstance(principal, Mapping) else ""
if not agent_id:
return False
claim_bound = {"acquireWriterLease", "bootstrapWriterLease", "renewWriterLease", "commitFileGoverned", "commitFilesGoverned", "projectOnlineAgentExecution", "getGitOpsJob"}
if operation not in claim_bound:
return False
task_id = str(body.get("task_id") or "")
if not task_id:
return False
task_request = urllib.request.Request(
self.agent_task_api_url.rstrip("/") + "/v1/tasks/" + task_id,
headers={"Authorization": header, "Accept": "application/json"},
)
try:
with urllib.request.urlopen(task_request, timeout=5) as response:
task = json.loads(response.read().decode("utf-8"))
except (OSError, urllib.error.URLError, urllib.error.HTTPError, ValueError):
return False
return isinstance(task, Mapping) and task.get("task_id") == task_id and task.get("claimed_by") == agent_id and task.get("status") in {"CLAIMED", "RUNNING"}
def do_GET(self) -> None:
if self.adapter is None: self._json({"error": "RUNTIME_UNAVAILABLE"}, 503); return
if self.path == "/v2/runtime": self._json(self.adapter.getRuntimeStatus()); return
@@ -1462,14 +1670,23 @@ class BridgeHandler(BaseHTTPRequestHandler):
operation = ROUTES.get(self.path)
if operation is None or operation in ("getRuntimeStatus", "getGovernedWriteCapabilities") or self.adapter is None: self._json({"error": "NOT_FOUND"}, 404); return
try:
body = self._body(); self._allowed(operation, body); self._json(getattr(self.adapter, operation)(body))
body = self._body()
except _HTTPError as exc: self._json({"error": str(exc)}, 413 if str(exc) == "REQUEST_TOO_LARGE" else 400); return
if not self._authorized(operation, body): self._json({"error": "BRIDGE_CALLER_UNAUTHORIZED"}, 401); return
try:
self._allowed(operation, body); self._json(getattr(self.adapter, operation)(body))
except _HTTPError as exc: self._json({"error": str(exc)}, 413 if str(exc) == "REQUEST_TOO_LARGE" else 400)
except (BootstrapError, ValueError, BridgeV2Error) as exc: self._json({"error": str(exc)}, 409 if "CONFLICT" in str(exc) else 400)
except (BootstrapError, ExecutionProjectionError, ExecutionPlanError, ValueError, BridgeV2Error) as exc: self._json({"error": str(exc)}, 409 if any(token in str(exc) for token in ("CONFLICT", "DRIFT", "CAS")) else 400)
except Exception as exc: self._json({"error": str(exc) or type(exc).__name__}, 409 if any(token in str(exc) for token in ("CONFLICT", "EXPECTATION", "MONOTONIC")) else 503)
def serve(adapter: BridgeV2Adapter, host: str = "127.0.0.1", port: int = 18892) -> None:
BridgeHandler.adapter = adapter; ThreadingHTTPServer((host, port), BridgeHandler).serve_forever()
def serve(adapter: BridgeV2Adapter, host: str = "127.0.0.1", port: int = 18892, *, control_token_path: Path, agent_task_api_url: str) -> None:
if not control_token_path.is_file() or control_token_path.is_symlink():
raise RuntimeError("BRIDGE_CONTROL_CREDENTIAL_UNAVAILABLE")
control_token = control_token_path.read_text(encoding="utf-8").strip()
if len(control_token) < 32:
raise RuntimeError("BRIDGE_CONTROL_CREDENTIAL_INVALID")
BridgeHandler.adapter = adapter; BridgeHandler.control_token = control_token; BridgeHandler.agent_task_api_url = agent_task_api_url; ThreadingHTTPServer((host, port), BridgeHandler).serve_forever()
def main() -> None:
@@ -1488,11 +1705,21 @@ def main() -> None:
remote_name=os.environ.get("AIOS_GITEA_REMOTE", "gitea-http"),
api_base=os.environ.get("AIOS_GITEA_API_BASE", "https://g.cnnetmkt.top/git/api/v1"),
target_version=os.environ.get("AIOS_CONTROL_PLANE_TARGET_VERSION", "V2R2C1"),
agent_task_api_url=os.environ.get("AIOS_AGENT_TASK_API_URL", "http://127.0.0.1:18940"),
agent_task_control_token_path=Path(
os.environ.get(
"AIOS_AGENT_TASK_CONTROL_TOKEN_FILE",
str(Path(os.environ.get("CREDENTIALS_DIRECTORY", "/run/credentials/aios-chatgpt-bridge-v2-staging.service")) / "agent_task_control_token"),
)
),
online_task_projection_enabled=os.environ.get("AIOS_ONLINE_TASK_PROJECTION_ENABLED", "1") == "1",
)
runtime = GovernedRuntime(config)
serve(
BridgeV2Adapter(runtime_status=runtime.runtime_status(), handlers=runtime.handlers()),
port=int(os.environ.get("AIOS_BRIDGE_V2_PORT", "18892")),
control_token_path=config.agent_task_control_token_path,
agent_task_api_url=config.agent_task_api_url,
)
@@ -157,7 +157,7 @@ class DetachedGitWorkspace:
def materialize_files(self, files: Mapping[str, bytes]) -> None:
dirty = self._dirty_paths()
expected_paths = set(files)
if dirty and dirty != expected_paths:
if dirty and not dirty.issubset(expected_paths):
raise ContinuationExecutionError("WORKTREE_DIRTY_SCOPE_MISMATCH")
for relative, content in files.items():
target = (self.path / relative).resolve()
@@ -11,19 +11,24 @@ or runtime switching.
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from datetime import datetime, timezone, timedelta
import argparse
import hashlib
import grp
import hmac
from http.server import BaseHTTPRequestHandler
import json
import os
import pwd
from pathlib import Path
import re
import secrets
import sqlite3
import socket
import socketserver
import stat
import tempfile
import struct
import threading
import urllib.error
import urllib.request
@@ -34,6 +39,14 @@ RECEIPT_SCHEMA = "aios.owner-durable-authorization-receipt.v1"
DECISION_SCHEMA = "aios.owner-durable-authorization-decision.v1"
PROJECTION_SCHEMA = "aios.external-capability-projection.request.v1"
PROJECTION_LIST_SCHEMA = "aios.external-capability-projection.list.v1"
CALLER_PROJECTION_SCHEMA = "aios.caller-admission-projection.v1"
CALLER_PROJECTION_TTL_DEFAULT = 300
CALLER_PROJECTION_SCOPES = frozenset({
"governed-task:project",
"release:candidate",
})
GATE_AMENDMENT_SCHEMA = "aios.release-preparation.gate-amendment.v1"
GATE_AMENDMENT_CLASSIFICATION = "SUPERVISOR_GLOBAL_EXTERNAL_ACK_GATE_REMEDIATED"
ALLOWED_PERSISTENT_CAPABILITIES = frozenset({
"governed-task:project",
@@ -64,6 +77,80 @@ TASK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
JOB_RE = re.compile(r"^job-[0-9a-f]{32}$")
MAX_BODY = 512 * 1024
SO_PEERCRED_SIZE = struct.calcsize("3i")
def _peer_process_status(pid: int) -> tuple[int, int, set[int]]:
status_path = Path(f"/proc/{pid}/status")
try:
lines = status_path.read_text(encoding="utf-8").splitlines()
except (OSError, UnicodeError) as exc:
raise AuthorizationError("SOCKPEER_CREDENTIALS_INVALID", 403) from exc
fields: dict[str, str] = {}
for line in lines:
if ":" in line:
key, value = line.split(":", 1)
if key in {"Uid", "Gid", "Groups"}:
fields[key] = value.strip()
try:
uid = int(fields["Uid"].split()[0])
gid = int(fields["Gid"].split()[0])
groups = {int(item) for item in fields["Groups"].split()}
except (KeyError, IndexError, ValueError) as exc:
raise AuthorizationError("SOCKPEER_CREDENTIALS_INVALID", 403) from exc
return uid, gid, groups
def _peer_process_groups(pid: int) -> set[int]:
return _peer_process_status(pid)[2]
def peer_credentials_from_connection(connection: object) -> tuple[int, int, int, set[int]]:
if not hasattr(socket, "SO_PEERCRED"):
raise AuthorizationError("SO_PEERCRED_UNAVAILABLE", 500)
try:
raw = connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, SO_PEERCRED_SIZE)
except OSError as exc:
raise AuthorizationError("SO_PEERCRED_UNAVAILABLE", 403) from exc
if len(raw) != SO_PEERCRED_SIZE:
raise AuthorizationError("SOCKPEER_CREDENTIALS_INVALID", 403)
try:
pid, uid, gid = struct.unpack("3i", raw)
except struct.error as exc:
raise AuthorizationError("SOCKPEER_CREDENTIALS_INVALID", 403) from exc
if pid <= 1 or uid < 0 or gid < 0:
raise AuthorizationError("SOCKPEER_CREDENTIALS_INVALID", 403)
process_uid, process_gid, groups = _peer_process_status(pid)
if process_uid != uid or process_gid != gid:
raise AuthorizationError("SOCKPEER_CREDENTIALS_INVALID", 403)
return pid, uid, gid, groups | {gid}
def peer_user_from_connection(connection: object) -> str:
_pid, uid, _gid, _groups = peer_credentials_from_connection(connection)
try:
return pwd.getpwuid(uid).pw_name
except (KeyError, OSError) as exc:
raise AuthorizationError("SO_PEERCRED_UID_UNKNOWN", 403) from exc
CANONICAL_BROKER_GROUP = os.environ.get(
"AIOS_OWNER_AUTH_BROKER_ALLOWED_GROUP",
"aios-autonomous-agent-worker",
).strip()
def authorize_broker_peer(connection: object) -> bool:
_pid, _uid, _gid, groups = peer_credentials_from_connection(connection)
if not CANONICAL_BROKER_GROUP:
raise AuthorizationError("SOCKGROUP_CONFIGURATION_INVALID", 503)
try:
canonical_gid = grp.getgrnam(CANONICAL_BROKER_GROUP).gr_gid
except KeyError as exc:
raise AuthorizationError("SOCKGROUP_CONFIGURATION_INVALID", 503) from exc
if canonical_gid not in groups:
raise AuthorizationError("SOCKGROUP_NOT_AUTHORIZED", 403)
return True
class AuthorizationError(RuntimeError):
@@ -134,6 +221,7 @@ class DurablePolicy:
persistent_until_revoked: bool
constraints: Constraints
policy_sha256: str
expires_at: datetime | None
@classmethod
def load(
@@ -158,10 +246,14 @@ class DurablePolicy:
"capabilities",
"issued_at",
"not_before",
"expires_at",
"persistent_until_revoked",
"constraints",
}
if set(value) != allowed_fields or value.get("schema") != POLICY_SCHEMA:
if (
not set(value).issubset(allowed_fields)
or value.get("schema") != POLICY_SCHEMA
):
raise AuthorizationError("POLICY_FIELDS_INVALID", 503)
grant_id = str(value.get("grant_id") or "")
@@ -190,6 +282,13 @@ class DurablePolicy:
current = now or datetime.now(timezone.utc)
if issued_at > not_before or current < not_before:
raise AuthorizationError("POLICY_NOT_ACTIVE", 403)
expires_at = None
if value.get("expires_at") is not None:
expires_at = parse_time(value.get("expires_at"), field="expires_at")
if expires_at <= not_before:
raise AuthorizationError("POLICY_EXPIRY_INVALID", 503)
if current >= expires_at:
raise AuthorizationError("POLICY_EXPIRED", 403)
if value.get("persistent_until_revoked") is not True:
raise AuthorizationError("POLICY_PERSISTENCE_INVALID", 503)
@@ -243,8 +342,43 @@ class DurablePolicy:
max_task_risk=max_risk,
),
policy_sha256=sha256(policy_path.read_bytes()),
expires_at=expires_at,
)
def validate_caller_scope(
self,
caller: str,
scope: str,
request: Mapping[str, Any],
) -> dict[str, Any]:
if caller != self.account:
raise AuthorizationError("CALLER_MISMATCH", 403)
if scope not in CALLER_PROJECTION_SCOPES:
raise AuthorizationError("SCOPE_MISMATCH", 403)
if scope not in self.capabilities:
raise AuthorizationError("CAPABILITY_NOT_AUTHORIZED", 403)
if not isinstance(request, Mapping):
raise AuthorizationError("CALLER_PROJECTION_REQUEST_INVALID")
if scope == "release:candidate":
if set(request) != {"source_commit", "version"}:
raise AuthorizationError("CALLER_PROJECTION_FIELDS_INVALID")
source_commit = str(request.get("source_commit") or "")
version = str(request.get("version") or "")
if OID_RE.fullmatch(source_commit) is None:
raise AuthorizationError("RELEASE_SOURCE_COMMIT_INVALID")
if VERSION_RE.fullmatch(version) is None or not version.startswith(
self.constraints.release_version_prefix
):
raise AuthorizationError("RELEASE_VERSION_NOT_AUTHORIZED", 403)
elif request:
raise AuthorizationError("CALLER_PROJECTION_FIELDS_INVALID")
return {
"schema": CALLER_PROJECTION_SCHEMA,
"caller": caller,
"scope": scope,
"request_sha256": sha256(canonical(request)),
}
def authorize(self, capability: str, request: Mapping[str, Any]) -> dict[str, Any]:
if capability not in self.capabilities:
raise AuthorizationError("CAPABILITY_NOT_AUTHORIZED", 403)
@@ -447,9 +581,160 @@ class ReceiptStore:
ON projection_outbox(state, capability, created_at)
"""
)
con.execute(
"""
CREATE TABLE IF NOT EXISTS caller_projections(
projection_id TEXT PRIMARY KEY,
caller TEXT NOT NULL,
scope TEXT NOT NULL,
grant_id TEXT NOT NULL,
policy_sha256 TEXT NOT NULL,
projection_state TEXT NOT NULL,
credential_state TEXT NOT NULL,
rehydration_state TEXT NOT NULL,
issued_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(caller, scope)
)
"""
)
con.execute(
"""
CREATE TABLE IF NOT EXISTS gate_amendments(
idempotency_key TEXT PRIMARY KEY,
amendment_sha256 TEXT NOT NULL,
amendment_json TEXT NOT NULL,
created_at TEXT NOT NULL
)
"""
)
con.commit()
self._lock = threading.RLock()
@staticmethod
def caller_projection_id(caller: str, scope: str) -> str:
digest = sha256(canonical({"caller": caller, "scope": scope}))
return "caller-projection-" + digest[:32]
def upsert_caller_projection(
self,
*,
caller: str,
scope: str,
grant_id: str,
policy_sha256: str,
ttl_seconds: int,
effective_scopes: Any,
) -> dict[str, Any]:
if not 30 <= ttl_seconds <= 900:
raise AuthorizationError("CALLER_PROJECTION_TTL_INVALID", 503)
normalized_scopes = sorted({str(item) for item in effective_scopes})
if (
not normalized_scopes
or scope not in normalized_scopes
or not set(normalized_scopes).issubset(CALLER_PROJECTION_SCOPES)
):
raise AuthorizationError("CALLER_PROJECTION_SCOPES_INVALID", 503)
requested_projection_id = self.caller_projection_id(caller, scope)
issued = datetime.now(timezone.utc)
issued_at = issued.isoformat()
expires_at = (issued + timedelta(seconds=ttl_seconds)).isoformat()
requested_recovery = False
with self._lock, sqlite3.connect(self.path) as con:
con.row_factory = sqlite3.Row
con.execute("BEGIN IMMEDIATE")
for projected_scope in normalized_scopes:
projection_id = self.caller_projection_id(caller, projected_scope)
row = con.execute(
"SELECT * FROM caller_projections WHERE caller=? AND scope=?",
(caller, projected_scope),
).fetchone()
if row is not None and row["projection_state"] not in {"ACKED", "PENDING"}:
con.rollback()
raise AuthorizationError("PROJECTION_STATE_INVALID", 503)
recovery = row is not None
if projected_scope == scope:
requested_recovery = recovery
if row is None:
con.execute(
"""
INSERT INTO caller_projections(
projection_id,caller,scope,grant_id,policy_sha256,
projection_state,credential_state,rehydration_state,
issued_at,expires_at,updated_at
) VALUES(?,?,?,?,?,?,?,?,?,?,?)
""",
(
projection_id,
caller,
projected_scope,
grant_id,
policy_sha256,
"PENDING",
"GENERATING",
"RECOVERING" if recovery else "GENERATING",
issued_at,
expires_at,
issued_at,
),
)
else:
con.execute(
"""
UPDATE caller_projections
SET projection_id=?,grant_id=?,policy_sha256=?,
projection_state='PENDING',
credential_state='GENERATING',
rehydration_state=?,
issued_at=?,expires_at=?,updated_at=?
WHERE caller=? AND scope=?
""",
(
projection_id,
grant_id,
policy_sha256,
"RECOVERING",
issued_at,
expires_at,
issued_at,
caller,
projected_scope,
),
)
con.execute(
"""
UPDATE caller_projections
SET projection_state='ACKED',
credential_state='REHYDRATED',
rehydration_state=?,
updated_at=?
WHERE caller=? AND scope=?
""",
(
"RECOVERED" if recovery else "GENERATED",
issued_at,
caller,
projected_scope,
),
)
con.commit()
return {
"schema": CALLER_PROJECTION_SCHEMA,
"projection_id": requested_projection_id,
"caller": caller,
"scope": scope,
"grant_id": grant_id,
"policy_sha256": policy_sha256,
"projection_state": "ACKED",
"credential_state": "REHYDRATED",
"rehydration_state": "RECOVERED" if requested_recovery else "GENERATED",
"effective_scopes": normalized_scopes,
"issued_at": issued_at,
"expires_at": expires_at,
"secret_exposed": False,
}
def get(self, key: str) -> dict[str, Any] | None:
with sqlite3.connect(self.path) as con:
row = con.execute(
@@ -519,6 +804,37 @@ class ReceiptStore:
)
con.commit()
def put_gate_amendment(
self,
idempotency_key: str,
amendment: Mapping[str, Any],
) -> dict[str, Any]:
encoded = json.dumps(amendment, sort_keys=True, separators=(",", ":"))
lowered = encoded.lower()
if any(marker in lowered for marker in (
"bearer ", "access_token", "refresh_token", "private_key", "credential_file"
)):
raise AuthorizationError("GATE_AMENDMENT_SECRET_MATERIAL_DENY", 403)
digest = sha256(encoded.encode())
with self._lock, sqlite3.connect(self.path) as con:
con.execute("BEGIN IMMEDIATE")
row = con.execute(
"SELECT amendment_sha256,amendment_json FROM gate_amendments WHERE idempotency_key=?",
(idempotency_key,),
).fetchone()
if row is not None:
if row[0] != digest or row[1] != encoded:
con.rollback()
raise AuthorizationError("GATE_AMENDMENT_IDEMPOTENCY_CONFLICT", 409)
con.commit()
return {"amendment_sha256": digest, "idempotent": True}
con.execute(
"INSERT INTO gate_amendments(idempotency_key,amendment_sha256,amendment_json,created_at) VALUES(?,?,?,?)",
(idempotency_key, digest, encoded, datetime.now(timezone.utc).isoformat()),
)
con.commit()
return {"amendment_sha256": digest, "idempotent": False}
@staticmethod
def projection_id(
grant_id: str,
@@ -716,12 +1032,83 @@ class Broker:
store: ReceiptStore,
upstream: Upstream,
expected_account: str,
expected_peer_user: str,
allowed_peer_users: frozenset[str] | None = None,
) -> None:
self.policy_path = policy_path
self.revocation_path = revocation_path
self.store = store
self.upstream = upstream
self.expected_account = expected_account
self.expected_peer_user = expected_peer_user
self.allowed_peer_users = allowed_peer_users or frozenset({expected_peer_user})
@staticmethod
def validate_caller_projection(
value: Mapping[str, Any],
*,
expected_scopes: Any = None,
) -> dict[str, Any]:
expected = sorted(
set(expected_scopes) if expected_scopes is not None else CALLER_PROJECTION_SCOPES
)
if value.get("caller") != "chatgpt-governed-implementer-singlehost":
raise AuthorizationError("CALLER_MISMATCH", 503)
if value.get("scope") not in expected:
raise AuthorizationError("SCOPE_MISMATCH", 503)
if value.get("projection_state") != "ACKED":
raise AuthorizationError("PROJECTION_STATE_INVALID", 503)
if value.get("credential_state") != "REHYDRATED":
raise AuthorizationError("CREDENTIAL_REHYDRATION_INVALID", 503)
if value.get("rehydration_state") not in {"GENERATED", "RECOVERED"}:
raise AuthorizationError("REHYDRATION_STATE_INVALID", 503)
if value.get("effective_scopes") != expected:
raise AuthorizationError("PROJECTION_SCOPE_INVALID", 503)
if value.get("secret_exposed") is not False:
raise AuthorizationError("PROJECTION_SECRET_EXPOSURE", 503)
return dict(value)
def project_caller(
self,
caller: str,
scope: str,
request: Mapping[str, Any],
*,
idempotency_key: str,
) -> dict[str, Any]:
if not GRANT_RE.fullmatch(idempotency_key):
raise AuthorizationError("IDEMPOTENCY_KEY_INVALID")
if caller != self.expected_account:
raise AuthorizationError("CALLER_MISMATCH", 403)
if scope not in CALLER_PROJECTION_SCOPES:
raise AuthorizationError("SCOPE_MISMATCH", 403)
policy = self.policy()
policy.validate_caller_scope(caller, scope, request)
effective_scopes = sorted(policy.capabilities & CALLER_PROJECTION_SCOPES)
try:
ttl_seconds = int(
os.environ.get(
"AIOS_OWNER_AUTH_CALLER_PROJECTION_TTL_SECONDS",
str(CALLER_PROJECTION_TTL_DEFAULT),
)
)
except ValueError as exc:
raise AuthorizationError("CALLER_PROJECTION_TTL_INVALID", 503) from exc
credential = secrets.token_urlsafe(32)
value = self.store.upsert_caller_projection(
caller=caller,
scope=scope,
grant_id=policy.grant_id,
policy_sha256=policy.policy_sha256,
ttl_seconds=ttl_seconds,
effective_scopes=effective_scopes,
)
value = self.validate_caller_projection(
value,
expected_scopes=effective_scopes,
)
value["_credential"] = credential
return value
def policy(self) -> DurablePolicy:
policy = DurablePolicy.load(self.policy_path, self.revocation_path)
@@ -758,24 +1145,90 @@ class Broker:
"separate_owner_release_approval_still_required": True,
}
@staticmethod
def validate_gate_amendment(
amendment: Mapping[str, Any],
*,
policy: DurablePolicy,
request: Mapping[str, Any],
) -> dict[str, Any]:
expected = {
"schema": GATE_AMENDMENT_SCHEMA,
"classification": GATE_AMENDMENT_CLASSIFICATION,
"supervisor_task_id": "r42-release-preparation-autonomous-supervisor-20260820",
"supervisor_execution_digest": "491457595667a95809f6f3cd19e9e7c9e74181070fd8038b86f30eb41bdfc231",
"actor_type": "credentialed_host_coordinator",
"operation": "createGovernedTask",
"repository": "aios/aios",
"original_external_request_sha256": "08fc2f3d1b64a2ca11da9f22da48de834cff3442fa7fa2d3bc390f8dbf9b254a",
"external_connector_rule": "ACK_STILL_REQUIRED",
"host_coordinator_rule": "DURABLE_OWNER_GOVERNED_TASK_PROJECT",
"owner_grant_id": policy.grant_id,
"owner_policy_sha256": policy.policy_sha256,
"builder_task_id": "r42-trusted-candidate-builder-runtime-wiring-20260820",
"builder_base_oid": "d957a216a6e9bd4c2292ffc6e44876a1c08fa1e3",
"builder_expected_branch_oid": "d957a216a6e9bd4c2292ffc6e44876a1c08fa1e3",
"builder_target_branch": "agent/r42-trusted-candidate-builder-runtime-wiring-20260820",
"builder_generation": 1,
"builder_idempotency_key": "r42-trusted-candidate-builder-runtime-wiring-binding-v1",
}
value = dict(amendment)
if value != expected:
raise AuthorizationError("GATE_AMENDMENT_BINDING_INVALID", 403)
request_coordinates = {
"task_id": expected["builder_task_id"],
"base_oid": expected["builder_base_oid"],
"expected_branch_oid": expected["builder_expected_branch_oid"],
"target_branch": expected["builder_target_branch"],
"generation": expected["builder_generation"],
"idempotency_key": expected["builder_idempotency_key"],
"repository": expected["repository"],
}
if any(request.get(key) != item for key, item in request_coordinates.items()):
raise AuthorizationError("GATE_AMENDMENT_BUILDER_BINDING_INVALID", 403)
return value
def execute(
self,
capability: str,
request: Mapping[str, Any],
*,
idempotency_key: str,
amendment: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
if not GRANT_RE.fullmatch(idempotency_key):
raise AuthorizationError("IDEMPOTENCY_KEY_INVALID")
policy = self.policy()
decision = policy.authorize(capability, request)
request_sha = decision["request_sha256"]
amendment_value: dict[str, Any] | None = None
if amendment is not None:
if capability != "governed-task:project":
raise AuthorizationError("GATE_AMENDMENT_CAPABILITY_INVALID", 403)
amendment_value = self.validate_gate_amendment(
amendment,
policy=policy,
request=request,
)
prior = self.store.get(idempotency_key)
if prior is not None:
if prior["request_sha256"] != request_sha:
raise AuthorizationError("IDEMPOTENCY_CONFLICT", 409)
response = prior["response"]
if amendment_value is not None:
amendment_result = self.store.put_gate_amendment(
idempotency_key,
amendment_value,
)
response = {
**response,
"gate_amendment": {
"classification": GATE_AMENDMENT_CLASSIFICATION,
"amendment_sha256": amendment_result["amendment_sha256"],
},
}
self.store.update_response(idempotency_key, request_sha, response)
if (
capability == "release:candidate"
and not response.get("projection_id")
@@ -862,6 +1315,15 @@ class Broker:
),
"separate_owner_release_approval_still_required": True,
}
if amendment_value is not None:
amendment_result = self.store.put_gate_amendment(
idempotency_key,
amendment_value,
)
response["gate_amendment"] = {
"classification": GATE_AMENDMENT_CLASSIFICATION,
"amendment_sha256": amendment_result["amendment_sha256"],
}
self.store.put(idempotency_key, capability, request_sha, response)
return response
@@ -881,11 +1343,22 @@ class Handler(BaseHTTPRequestHandler):
def log_message(self, *_args: Any) -> None:
return
def send_json(self, value: Mapping[str, Any], status: int) -> None:
def send_json(
self,
value: Mapping[str, Any],
status: int,
*,
internal_credential: str | None = None,
) -> None:
raw = canonical(value)
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Cache-Control", "no-store")
if internal_credential:
self.send_header(
"X-AIOS-Caller-Projection-Credential",
internal_credential,
)
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
@@ -893,6 +1366,16 @@ class Handler(BaseHTTPRequestHandler):
def caller_account(self) -> str:
return self.headers.get("X-AIOS-Account", "").strip()
def peer_user(self) -> str:
if self.broker is None:
return ""
return peer_user_from_connection(self.connection)
def peer_authorized(self) -> bool:
if self.broker is None:
return False
return authorize_broker_peer(self.connection)
def account_authorized(self) -> bool:
if self.broker is None:
return False
@@ -924,6 +1407,14 @@ class Handler(BaseHTTPRequestHandler):
200,
)
return
try:
peer_ok = self.peer_authorized()
except AuthorizationError as exc:
self.send_json({"error": exc.code}, exc.status)
return
if not peer_ok:
self.send_json({"error": "SOCKGROUP_NOT_AUTHORIZED"}, 403)
return
if not self.account_authorized():
self.send_json({"error": "ACCOUNT_NOT_AUTHORIZED"}, 403)
return
@@ -938,6 +1429,12 @@ class Handler(BaseHTTPRequestHandler):
"grant_id": policy.grant_id,
"account": policy.account,
"capabilities": sorted(policy.capabilities),
"policy_sha256": policy.policy_sha256,
"constraints": {
"repository": policy.constraints.repository,
"allowed_target_prefix": policy.constraints.allowed_target_prefix,
"max_task_risk": policy.constraints.max_task_risk,
},
"persistent_until_revoked": True,
"separate_owner_release_approval_still_required": True,
}
@@ -957,12 +1454,21 @@ class Handler(BaseHTTPRequestHandler):
self.send_json({"error": exc.code}, exc.status)
def do_POST(self) -> None:
try:
peer_ok = self.peer_authorized()
except AuthorizationError as exc:
self.send_json({"error": exc.code}, exc.status)
return
if not peer_ok:
self.send_json({"error": "SOCKGROUP_NOT_AUTHORIZED"}, 403)
return
if not self.account_authorized():
self.send_json({"error": "ACCOUNT_NOT_AUTHORIZED"}, 403)
return
routes = {
"/v1/project-governed-task": "governed-task:project",
"/v1/authorize-release-candidate": "release:candidate",
"/v1/project-caller": "caller:projection",
}
capability = routes.get(self.path)
if capability is None or self.broker is None:
@@ -970,14 +1476,49 @@ class Handler(BaseHTTPRequestHandler):
return
try:
body = self.body()
if set(body) != {"idempotency_key", "request"}:
if self.path == "/v1/project-caller":
if set(body) != {
"caller",
"scope",
"idempotency_key",
"request",
}:
raise AuthorizationError("CALLER_PROJECTION_FIELDS_INVALID")
if not isinstance(body["request"], Mapping):
raise AuthorizationError("CALLER_PROJECTION_REQUEST_INVALID")
value = self.broker.project_caller(
str(body["caller"]),
str(body["scope"]),
dict(body["request"]),
idempotency_key=str(body["idempotency_key"]),
)
credential = str(value.pop("_credential", ""))
if len(credential) < 32:
raise AuthorizationError("CREDENTIAL_REHYDRATION_MISSING", 503)
self.send_json(
value,
200,
internal_credential=credential,
)
return
if set(body) not in (
{"idempotency_key", "request"},
{"idempotency_key", "request", "amendment"},
):
raise AuthorizationError("BROKER_FIELDS_INVALID")
if not isinstance(body["request"], Mapping):
raise AuthorizationError("BROKER_REQUEST_INVALID")
if "amendment" in body and not isinstance(body["amendment"], Mapping):
raise AuthorizationError("GATE_AMENDMENT_INVALID")
value = self.broker.execute(
capability,
dict(body["request"]),
idempotency_key=str(body["idempotency_key"]),
amendment=(
dict(body["amendment"])
if isinstance(body.get("amendment"), Mapping)
else None
),
)
self.send_json(value, 200)
except AuthorizationError as exc:
@@ -989,6 +1530,41 @@ class UnixHTTPServer(socketserver.ThreadingUnixStreamServer):
def serve() -> None:
credentials = Path(os.environ.get("CREDENTIALS_DIRECTORY", "/run/credentials"))
raw_allowed_users = os.environ.get(
"AIOS_OWNER_AUTH_BROKER_ALLOWED_USERS",
"",
).strip()
if raw_allowed_users:
allowed_peer_users = tuple(
dict.fromkeys(
item.strip()
for item in raw_allowed_users.split(",")
if item.strip()
)
)
else:
single_allowed_user = os.environ.get(
"AIOS_OWNER_AUTH_BROKER_ALLOWED_USER",
"aios-autonomous-agent-worker",
).strip()
allowed_peer_users = (single_allowed_user,) if single_allowed_user else ()
if not allowed_peer_users:
raise RuntimeError("BROKER_ALLOWED_USER_MISSING")
allowed_peer_user = allowed_peer_users[0]
for username in allowed_peer_users:
pwd.getpwnam(username)
allowed_peer_uid = pwd.getpwnam(allowed_peer_user).pw_uid
global CANONICAL_BROKER_GROUP
CANONICAL_BROKER_GROUP = os.environ.get(
"AIOS_OWNER_AUTH_BROKER_ALLOWED_GROUP",
"aios-autonomous-agent-worker",
).strip()
if not CANONICAL_BROKER_GROUP:
raise RuntimeError("BROKER_ALLOWED_GROUP_MISSING")
try:
allowed_peer_gid = grp.getgrnam(CANONICAL_BROKER_GROUP).gr_gid
except KeyError as exc:
raise RuntimeError("BROKER_ALLOWED_GROUP_INVALID") from exc
policy_path = Path(
os.environ.get(
"AIOS_OWNER_DURABLE_AUTHORIZATION_POLICY",
@@ -1029,6 +1605,8 @@ def serve() -> None:
bridge_token=bridge_token,
),
expected_account=account,
expected_peer_user=allowed_peer_user,
allowed_peer_users=frozenset(allowed_peer_users),
)
socket_path = Path(
os.environ.get(
@@ -1044,6 +1622,7 @@ def serve() -> None:
raise RuntimeError("BROKER_SOCKET_CONFLICT")
socket_path.unlink()
server = UnixHTTPServer(str(socket_path), Handler)
os.chown(socket_path, allowed_peer_uid, allowed_peer_gid)
os.chmod(socket_path, 0o660)
try:
server.serve_forever()
@@ -2,13 +2,20 @@
from __future__ import annotations
from dataclasses import dataclass, asdict
from contextlib import contextmanager
import fcntl
import hashlib
import json
from pathlib import Path
import re
from typing import Any, Mapping
from aios_gitops_job_controller import GitOpsJobController, JobStore
from aios_gitops_job_controller import GitOpsJobController, JobStore, sha256_json
from aios_online_agent_execution import (
GovernedExecutionBinding,
project_execution_envelope,
project_online_task,
)
OID_RE = re.compile(r"^[0-9a-f]{40}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
@@ -36,35 +43,161 @@ class GovernedTaskBinding:
return {"task_id": self.task_id, "repository": self.repository, "base_oid": self.base_oid, "target_branch": self.target_branch, "expected_branch_oid": self.expected_branch_oid, "generation": self.generation, "branch_mode": self.branch_mode, "create_target_branch": self.branch_mode == "CREATE_NEW", "worktree_mode": "detached_at_base_oid" if self.branch_mode == "CREATE_NEW" else "detached_at_expected_head", "cas_ref_update_required": True, "idempotent": idempotent, "state": "BOUND", "job_id": self.job_id}
def _safe(value: str) -> str:
def _legacy_safe(value: str) -> str:
return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-")[:120] or "task"
def _task_key(value: str) -> str:
readable = _legacy_safe(value)[:72]
return f"{readable}-{hashlib.sha256(value.encode('utf-8')).hexdigest()[:24]}"
class GovernedTaskBootstrap:
def __init__(self, authority: Any, root: Path, *, remote_name: str, token_path: Path, ssh_key_path: Path, api_base: str = "https://g.cnnetmkt.top/git/api/v1") -> None:
self.authority = authority; self.root = Path(root); self.remote_name = remote_name; self.token_path = Path(token_path); self.ssh_key_path = Path(ssh_key_path); self.api_base = api_base; self.task_meta_root = self.root / "task-bindings"; self.job_root = self.root / "jobs"; self.task_meta_root.mkdir(parents=True, exist_ok=True); self.job_root.mkdir(parents=True, exist_ok=True)
self.authority = authority; self.root = Path(root); self.remote_name = remote_name; self.token_path = Path(token_path); self.ssh_key_path = Path(ssh_key_path); self.api_base = api_base; self.task_meta_root = self.root / "task-bindings"; self.job_root = self.root / "jobs"; self.lock_root = self.root / "task-locks"; self.task_meta_root.mkdir(parents=True, exist_ok=True); self.job_root.mkdir(parents=True, exist_ok=True); self.lock_root.mkdir(parents=True, exist_ok=True)
def _store(self, task_id: str) -> tuple[JobStore, GitOpsJobController]:
root = self.job_root / _safe(task_id); store = JobStore(root, payload_root=root / "payloads"); return store, GitOpsJobController(store)
legacy = self.job_root / _legacy_safe(task_id)
keyed = self.job_root / _task_key(task_id)
root = legacy if legacy.is_dir() and not keyed.exists() else keyed
store = JobStore(root, payload_root=root / "payloads"); return store, GitOpsJobController(store)
def _metadata(self, task_id: str) -> Path:
return self.task_meta_root / (_safe(task_id) + ".json")
return self.task_meta_root / (_task_key(task_id) + ".json")
def _legacy_metadata(self, task_id: str) -> Path:
return self.task_meta_root / (_legacy_safe(task_id) + ".json")
@contextmanager
def _task_lock(self, task_id: str):
path = self.lock_root / (_task_key(task_id) + ".lock")
with path.open("a+b") as handle:
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
try:
yield
finally:
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
@contextmanager
def _branch_lock(self, repository: str, target_branch: str):
key = hashlib.sha256((repository + "\0" + target_branch).encode("utf-8")).hexdigest()
path = self.lock_root / ("branch-" + key + ".lock")
with path.open("a+b") as handle:
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
try:
yield
finally:
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
def _read_existing(self, task_id: str) -> GovernedTaskBinding | None:
path = self._metadata(task_id)
if not path.exists():
path = self._legacy_metadata(task_id)
if not path.is_file() or path.is_symlink(): return None
return GovernedTaskBinding(**json.loads(path.read_text(encoding="utf-8")))
binding = GovernedTaskBinding(**json.loads(path.read_text(encoding="utf-8")))
if binding.task_id != task_id:
raise BootstrapError("TASK_BINDING_KEY_COLLISION")
return binding
def get_binding(self, task_id: str) -> GovernedTaskBinding:
"""Return the exact durable binding; never synthesize missing lineage."""
existing = self._read_existing(str(task_id))
if existing is None or not existing.job_id:
raise BootstrapError("TASK_EXECUTION_PROJECTION_MISSING")
return existing
def execution_binding(self, binding: GovernedTaskBinding) -> GovernedExecutionBinding:
expected = binding.expected_branch_oid
mode = binding.branch_mode
if mode == "CREATE_NEW":
current = self.authority.read_ref("refs/heads/" + binding.target_branch)
if current is not None:
store, _ = self._store(binding.task_id)
with store._connect() as connection:
rows = connection.execute(
"SELECT current_head_oid,generation,state,lineage_anchor_oid "
"FROM branch_leases WHERE job_id=? AND branch=? AND state='ACTIVE' "
"ORDER BY revision DESC",
(binding.job_id, binding.target_branch),
).fetchall()
if len(rows) != 1:
raise BootstrapError("SAME_JOB_CONTINUATION_LEASE_INVALID")
row = rows[0]
if (
str(row["current_head_oid"] or "") != current
or int(row["generation"] or 0) != binding.generation
or str(row["lineage_anchor_oid"] or "") != binding.base_oid
):
raise BootstrapError("SAME_JOB_CONTINUATION_BINDING_INVALID")
expected = current
mode = "CONTINUE_EXISTING"
return GovernedExecutionBinding.from_mapping(
{
"task_id": binding.task_id,
"job_id": binding.job_id,
"repository": binding.repository,
"base_oid": binding.base_oid,
"expected_branch_oid": expected,
"target_branch": binding.target_branch,
"branch_mode": mode,
"generation": binding.generation,
"idempotency_key": binding.idempotency_key,
}
)
def execution_envelope(
self,
task_id: str,
execution_request: Mapping[str, Any] | None = None,
*,
required_capabilities: tuple[str, ...] = (),
expected_job_id: str | None = None,
) -> dict[str, Any]:
binding = self.get_binding(task_id)
if expected_job_id and binding.job_id != expected_job_id:
raise BootstrapError("JOB_BINDING_CONFLICT")
return project_execution_envelope(
self.execution_binding(binding),
execution_request,
required_capabilities=required_capabilities,
)
def online_task_projection(
self,
task_id: str,
task_request: Mapping[str, Any],
execution_request: Mapping[str, Any] | None = None,
*,
expected_job_id: str | None = None,
) -> dict[str, Any]:
binding = self.get_binding(task_id)
if expected_job_id and binding.job_id != expected_job_id:
raise BootstrapError("JOB_BINDING_CONFLICT")
return project_online_task(self.execution_binding(binding), task_request, execution_request)
def _write(self, binding: GovernedTaskBinding) -> None:
path = self._metadata(binding.task_id); temporary = path.with_suffix(".tmp"); temporary.write_text(json.dumps(asdict(binding), sort_keys=True), encoding="utf-8"); temporary.replace(path)
def create(self, request: Mapping[str, Any]) -> dict[str, Any]:
task_id = str(request.get("task_id") or "")
if not task_id:
raise BootstrapError("TASK_BINDING_INVALID")
repository = str(request.get("repository") or "")
target_branch = str(request.get("target_branch") or "")
with self._task_lock(task_id):
with self._branch_lock(repository, target_branch):
return self._create_locked(request)
def _create_locked(self, request: Mapping[str, Any]) -> dict[str, Any]:
values = {key: str(request[key]) for key in ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key")}
if values["repository"] != "aios/aios" or not OID_RE.fullmatch(values["base_oid"]): raise BootstrapError("TASK_BINDING_INVALID")
if not BRANCH_RE.fullmatch(values["target_branch"]): raise BootstrapError("TARGET_BRANCH_INVALID")
existing = self._read_existing(values["task_id"])
if existing is not None:
if any(getattr(existing, key) != values[key] for key in ("repository", "base_oid", "target_branch", "risk", "description")): raise BootstrapError("TASK_BINDING_CONFLICT")
if any(getattr(existing, key) != values[key] for key in ("repository", "base_oid", "target_branch", "risk", "description", "idempotency_key")): raise BootstrapError("TASK_BINDING_CONFLICT")
optional = {"expected_branch_oid": existing.expected_branch_oid, "branch_mode": existing.branch_mode, "generation": existing.generation}
for key, durable in optional.items():
if key in request and str(request[key]) != str(durable): raise BootstrapError("TASK_BINDING_CONFLICT")
return existing.response(idempotent=True)
if not self.authority.commit_exists(values["base_oid"]): raise BootstrapError("BASE_COMMIT_NOT_FOUND")
current = self.authority.read_ref("refs/heads/" + values["target_branch"])
@@ -74,8 +207,61 @@ class GovernedTaskBootstrap:
if expected != values["base_oid"]: raise BootstrapError("EXPECTED_BASE_CONFLICT")
else:
if expected != current: raise BootstrapError("EXISTING_BRANCH_OID_CONFLICT")
mode, generation = "CONTINUE_EXISTING", int(request.get("generation", 2))
if generation < 1: raise BootstrapError("GENERATION_INVALID")
parent_task_id = str(request.get("parent_task_id") or "")
parent_job_id = str(request.get("parent_job_id") or "")
if not parent_task_id or not parent_job_id:
raise BootstrapError("EXISTING_BRANCH_OWNERSHIP_REQUIRED")
parent = self._read_existing(parent_task_id)
if parent is None or parent.job_id != parent_job_id or parent.repository != values["repository"] or parent.target_branch != values["target_branch"]:
raise BootstrapError("EXISTING_BRANCH_OWNERSHIP_CONFLICT")
store, _ = self._store(parent_task_id)
with store._connect() as connection:
rows = connection.execute(
"SELECT current_head_oid,generation,state FROM branch_leases WHERE job_id=? AND branch=? AND state='ACTIVE' ORDER BY revision DESC",
(parent_job_id, values["target_branch"]),
).fetchall()
if len(rows) != 1 or str(rows[0]["current_head_oid"] or "") != current:
raise BootstrapError("EXISTING_BRANCH_OWNERSHIP_CONFLICT")
mode, generation = "CONTINUE_EXISTING", int(rows[0]["generation"]) + 1
if "generation" in request and int(request["generation"]) != generation:
raise BootstrapError("GENERATION_BINDING_CONFLICT")
online_request = request.get("online_task")
execution_request = request.get("execution")
projection_required = (
online_request is not None
or execution_request is not None
or request.get("require_online_task_projection") is True
)
if projection_required:
if not isinstance(online_request, Mapping):
raise BootstrapError("ONLINE_TASK_REQUEST_INVALID")
if execution_request is not None and not isinstance(execution_request, Mapping):
raise BootstrapError("EXECUTION_REQUEST_INVALID")
provisional_job_id = "job-" + sha256_json(
{"client_idempotency_key": values["idempotency_key"]}
)[:32]
try:
provisional_binding = GovernedExecutionBinding.from_mapping(
{
"task_id": values["task_id"],
"job_id": provisional_job_id,
"repository": values["repository"],
"base_oid": values["base_oid"],
"expected_branch_oid": expected,
"target_branch": values["target_branch"],
"branch_mode": mode,
"generation": generation,
"idempotency_key": values["idempotency_key"],
}
)
project_online_task(
provisional_binding,
dict(online_request),
dict(execution_request or {}),
)
except Exception as exc:
raise BootstrapError("EXECUTION_BINDING_INVALID") from exc
payload = json.dumps(values, sort_keys=True, separators=(",", ":")).encode()
_, controller = self._store(values["task_id"])
receipt = controller.submit({"idempotency_key": values["idempotency_key"], "campaign_id": values["task_id"], "task_id": values["task_id"], "repository": values["repository"], "operation": "GOVERNED_TASK_CREATE", "branch": values["target_branch"], "expected_branch_oid": expected, "payload_digest": hashlib.sha256(payload).hexdigest(), "lineage_anchor_oid": values["base_oid"]}, payload=payload)
+380 -221
View File
@@ -3,8 +3,16 @@ from __future__ import annotations
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, urllib.request, uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from aios_caller_admission_projection import (
CALLER,
SCOPE,
GOVERNED_TASK_SCOPE,
PROJECTED_SCOPES,
CallerProjectionClient,
CallerProjectionError,
)
from baseline_authority import BaselineAuthority, BaselineAuthorityError
from silent_release_controller import ReleaseCandidate
from typing import Any
try:
@@ -29,18 +37,11 @@ from orchestrator.action_monitor import ActionMonitor
from silent_release_hardening import (
ApprovalKeyRing,
ApprovalRevocationStore,
BuilderAttestation,
BuilderAttestationKeyRing,
BuilderAttestationVerifier,
CapabilityAuthorizationError,
ReleasePlan,
SagaConflict,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TrustedCandidateBuilderUnavailable,
TrustedCandidateBundle,
TrustedReleasePolicy,
)
try:
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
@@ -90,33 +91,18 @@ SILENT_RELEASE_STATE_ROOT = Path(os.getenv(
'AIOS_SILENT_RELEASE_STATE_ROOT',
'/var/lib/aios-gitops/silent-release',
))
# This immutable staging API can prepare only a shadow candidate. An
# environment value must not turn its HTTP process into a release executor.
SILENT_RELEASE_EXECUTION_ENABLED = False
SILENT_RELEASE_EXECUTION_ENABLED = str(os.getenv(
'AIOS_SILENT_RELEASE_EXECUTION_ENABLED', 'false'
)).lower() in {'1', 'true', 'yes', 'on'}
SILENT_RELEASE_PUBLIC_KEYS = Path(os.getenv(
'AIOS_SILENT_RELEASE_PUBLIC_KEYS',
'/etc/aios-gitops/silent-release-owner-public-keys.json',
))
TRUSTED_BUILDER_ENABLED = str(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_ENABLED', 'false'
)).lower() in {'1', 'true', 'yes', 'on'}
TRUSTED_BUILDER_POLICY = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_POLICY', ''
))
TRUSTED_BUILDER_CREDENTIALS_DIRECTORY = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_CREDENTIALS_DIRECTORY',
os.getenv('CREDENTIALS_DIRECTORY', ''),
))
TRUSTED_BUILDER_SOCKET = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_SOCKET',
'/run/aios-trusted-candidate-builder.sock',
))
TRUSTED_BUILDER_PUBLIC_KEYRING = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_PUBLIC_KEYRING', ''
))
TRUSTED_RELEASE_POLICY = Path(os.getenv(
'AIOS_SILENT_RELEASE_TRUSTED_POLICY', ''
CALLER_PROJECTION_SOCKET = Path(os.getenv(
'AIOS_OWNER_AUTH_BROKER_SOCKET',
'/run/aios-owner-authorization-broker/broker.sock',
))
CALLER_PROJECTION_CLIENT = CallerProjectionClient(CALLER_PROJECTION_SOCKET)
monitor = ActionMonitor()
BASE_MODELS = [
@@ -140,6 +126,9 @@ READ_ONLY_GET_PATHS = frozenset({
'/admin/ai-evolution/api/data',
'/api/release-consistency', '/api/tag-publication-status',
'/api/release-acceptance',
'/api/governed-write-capabilities', '/api/control-plane-snapshot',
'/api/caller-capabilities',
'/api/online-agent-task', '/api/online-agent-task-events',
'/getReleaseCandidate', '/getReleasePlan', '/getReleaseAcceptance',
'/getApprovalEnvelope', '/getReleaseSaga', '/getCapabilityStatus',
'/getRecoveryStatus',
@@ -152,6 +141,25 @@ SILENT_RELEASE_POST_PATHS = frozenset({
'/startDeployment',
})
GOVERNED_EXECUTION_CAPABILITIES = frozenset({
"governed.write.branch",
"governed.write.file",
})
def requires_governed_execution(value: Mapping[str, Any]) -> bool:
capabilities = value.get("required_capabilities")
if capabilities is None:
return False
if not isinstance(capabilities, list):
return True
return bool(
GOVERNED_EXECUTION_CAPABILITIES.intersection(
{str(item) for item in capabilities}
)
)
def reject_non_read_http(handler, method: str, path: str) -> bool:
if method == 'GET':
return False
@@ -194,79 +202,6 @@ def _file_sha256(path: Path) -> str:
digest.update(chunk)
return digest.hexdigest()
def _trusted_release_policy_from_file(path: Path) -> TrustedReleasePolicy:
payload = json.loads(path.read_text(encoding='utf-8'))
required = {
'services', 'release_roots', 'cutover_links', 'dropin_paths',
'authoritative_git_dirs', 'repository_identities',
}
if not isinstance(payload, dict) or set(payload) != required:
raise ValueError('trusted release policy schema is invalid')
values = {}
for name in required:
raw = payload[name]
if not isinstance(raw, list) or not raw or not all(
isinstance(item, str) and item for item in raw
):
raise ValueError('trusted release policy values are invalid')
values[name] = frozenset(raw)
return TrustedReleasePolicy(**values)
class _HostTrustedCandidateBuilderAdapter:
"""Maps only a complete host-owned silent-release bundle response.
The API never manufactures candidate evidence or a builder signature. A
host builder protocol that returns a different response is therefore
unavailable, rather than silently downgraded to caller-owned evidence.
"""
def __init__(self, client):
self._client = client
def build(self, intent):
import secrets
from aios_trusted_candidate_builder import BuilderError
try:
version = str(intent['version'])
candidate_id = str(intent['candidate_id'])
source_commit = str(intent['source_commit'])
except (KeyError, TypeError) as exc:
raise CapabilityAuthorizationError(
'trusted builder requires a release worker request'
) from exc
try:
response = self._client.build(
version,
candidate_id,
source_commit,
nonce=secrets.token_hex(32),
)
except BuilderError as exc:
raise TrustedCandidateBuilderUnavailable(
'trusted builder request failed'
) from exc
raw_bundle = response.get('silent_release_bundle')
if not isinstance(raw_bundle, dict):
raise CapabilityAuthorizationError(
'trusted builder response is not a silent release bundle'
)
try:
return TrustedCandidateBundle(
candidate=ReleaseCandidate.from_dict(raw_bundle['candidate']),
release_plan=ReleasePlan.from_dict(raw_bundle['release_plan']),
checks=raw_bundle['checks'],
attestation=BuilderAttestation.from_dict(
raw_bundle['attestation'], require_fingerprint=True
),
)
except (KeyError, TypeError, ValueError) as exc:
raise CapabilityAuthorizationError(
'trusted builder response is invalid'
) from exc
def _build_silent_release_service() -> SilentReleaseActionService:
verifier=None
if SILENT_RELEASE_PUBLIC_KEYS.is_file():
@@ -278,86 +213,10 @@ def _build_silent_release_service() -> SilentReleaseActionService:
)
policy_path=WS / 'config' / 'gitops' / 'silent-release-policy.json'
policy_sha=_file_sha256(policy_path) if policy_path.is_file() else ''
candidate_builder = None
builder_attestation_verifier = None
trusted_policy = None
builder_readiness_probe = None
if TRUSTED_BUILDER_ENABLED:
# Every path is opt-in. In particular, this API receives its own
# client HMAC credential, never the builder's signing private key.
if not (
TRUSTED_BUILDER_POLICY.is_file()
and TRUSTED_BUILDER_CREDENTIALS_DIRECTORY.is_dir()
and TRUSTED_BUILDER_PUBLIC_KEYRING.is_file()
and TRUSTED_RELEASE_POLICY.is_file()
):
raise CapabilityAuthorizationError(
'trusted builder configuration is incomplete'
)
try:
from aios_trusted_candidate_builder import (
BuilderKeyring,
TrustedCandidateBuilderConfig,
UnixTrustedCandidateBuilderClient,
load_builder_policy,
readiness_probe,
)
builder_policy = load_builder_policy(TRUSTED_BUILDER_POLICY)
builder_config = TrustedCandidateBuilderConfig.from_policy(
builder_policy
)
client_hmac = (
TRUSTED_BUILDER_CREDENTIALS_DIRECTORY / 'builder_hmac'
).read_bytes().strip()
client_keyring = BuilderKeyring(builder_hmac=client_hmac)
client = UnixTrustedCandidateBuilderClient(
builder_config,
client_keyring,
socket_path=TRUSTED_BUILDER_SOCKET,
)
candidate_builder = _HostTrustedCandidateBuilderAdapter(client)
builder_attestation_verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing.from_file(
TRUSTED_BUILDER_PUBLIC_KEYRING
)
)
trusted_policy = _trusted_release_policy_from_file(
TRUSTED_RELEASE_POLICY
)
def builder_readiness_probe():
if not TRUSTED_BUILDER_SOCKET.exists():
return {'status': 'UNAVAILABLE'}
remote = dict(readiness_probe(client))
if remote.get('policy_sha256') != builder_config.policy_sha256:
return {'status': 'UNAVAILABLE'}
builder_attestation_verifier.keyring.require_active_identity(
key_id=str(remote.get('builder_key_id') or ''),
generation=remote.get('builder_key_generation'),
algorithm=str(remote.get('signature_algorithm') or ''),
purpose=str(remote.get('signature_purpose') or ''),
)
return {
**remote,
'hmac_credential_loaded': True,
'peer_verification_configured': True,
'public_key_trusted': True,
'policy_loaded': True,
}
except Exception as exc:
raise CapabilityAuthorizationError(
'trusted builder configuration is invalid'
) from exc
return SilentReleaseActionService(
SILENT_RELEASE_STATE_ROOT,
execution_enabled=SILENT_RELEASE_EXECUTION_ENABLED,
approval_verifier=verifier,
candidate_builder=candidate_builder,
builder_attestation_verifier=builder_attestation_verifier,
trusted_policy=trusted_policy,
builder_readiness_probe=builder_readiness_probe,
allow_legacy_candidate_input=False,
expected_repository=REPOSITORY_IDENTITY,
policy_sha256=policy_sha,
)
@@ -584,14 +443,113 @@ class H(BaseHTTPRequestHandler):
except Exception: return {'_raw':raw}
def actor(self):
auth=self.headers.get('Authorization',''); tok=auth[7:].strip() if auth.startswith('Bearer ') else self.headers.get('X-AIOS-Token','')
rec=authenticate(tok)
if rec: return {'ok':True,'account':safe(rec.get('account') or rec.get('name') or 'token-user'),'role':rec.get('role','user'),'scopes':set(rec.get('scopes') or [])}
return {'ok':False,'account':'anonymous','role':'anonymous','scopes':set()}
def need(self, scope):
a=self.actor()
if not a['ok']: self.sendj({'error':'unauthorized','hint':'Bearer token required'},401); return None
if '*' not in a['scopes'] and scope not in a['scopes']: self.sendj({'error':'forbidden','required_scope':scope,'account':a['account']},403); return None
try:
rec=authenticate(tok)
except Exception:
rec=None
if not isinstance(rec, dict) or not rec:
return {
'ok': False,
'account': 'anonymous',
'role': 'anonymous',
'scopes': set(),
'static_scopes': set(),
'projection': None,
'credential_loaded': False,
}
scopes = set(rec.get('scopes') or [])
return {
'ok': True,
'account': safe(rec.get('account') or rec.get('name') or 'token-user'),
'role': rec.get('role', 'user'),
'scopes': scopes,
'static_scopes': set(scopes),
'projection': None,
'credential_loaded': False,
}
def need(self, scope, *, projection_request=None):
a = self.actor()
if not a['ok']:
self.sendj({'error': 'unauthorized', 'hint': 'Bearer token required'}, 401)
return None
if a['account'] == CALLER and scope in PROJECTED_SCOPES:
try:
result = CALLER_PROJECTION_CLIENT.rehydrate(
a['account'],
scope,
projection_request,
)
except CallerProjectionError as exc:
self.sendj(
{
'error': 'caller_projection_denied',
'projection_state': 'DENIED',
'detail': exc.code,
'account': a['account'],
},
exc.status,
)
return None
projection = result.public
a['scopes'].update(projection.get('effective_scopes') or [])
a['projection'] = projection
a['credential_loaded'] = result.credential_loaded
if '*' not in a['scopes'] and scope not in a['scopes']:
self.sendj(
{
'error': 'forbidden',
'required_scope': scope,
'account': a['account'],
},
403,
)
return None
return a
def caller_capabilities(self):
actor = self.actor()
if not actor['ok']:
return self.sendj(
{'error': 'unauthorized', 'hint': 'Bearer token required'},
401,
)
effective = set(actor['static_scopes'])
projection_state = 'NOT_REQUIRED'
credential_state = 'NOT_REQUIRED'
rehydration_state = 'NOT_APPLICABLE'
admitted = True
projection_error = None
if actor['account'] == CALLER:
try:
result = CALLER_PROJECTION_CLIENT.rehydrate(
actor['account'],
GOVERNED_TASK_SCOPE,
{},
)
except CallerProjectionError as exc:
admitted = False
projection_state = 'DENIED'
credential_state = 'NOT_REHYDRATED'
rehydration_state = 'FAILED'
projection_error = exc.code
else:
effective.update(result.public.get('effective_scopes') or [])
projection_state = result.public['projection_state']
credential_state = result.public['credential_state']
rehydration_state = result.public['rehydration_state']
return self.sendj(
{
'schema': 'aios.caller-capability-introspection.v1',
'caller_identity': actor['account'],
'admitted': admitted,
'effective_scopes': sorted(effective),
'projection_state': projection_state,
'credential_state': credential_state,
'rehydration_state': rehydration_state,
'projection_error': projection_error,
'secret_exposed': False,
}
)
def bridge_release(self, path, data):
route = {
'/createTagApprovalRequest': '/v2/tag/approval-request',
@@ -623,31 +581,187 @@ class H(BaseHTTPRequestHandler):
return self.sendj(body, exc.code)
except (urllib.error.URLError, TimeoutError) as exc:
return self.sendj({'error': 'GOVERNED_BRIDGE_UNAVAILABLE', 'detail': type(exc).__name__}, 503)
def do_POST(self):
path = urllib.parse.urlparse(self.path).path
if path in {
'/createTagApprovalRequest', '/requestOrCreateTag', '/getTagPublicationStatus',
'/buildProductionArtifact', '/executeApprovedProductionRelease',
'/getReleaseConsistency', '/runReleaseAcceptance',
}:
actor = self.need('tag:create')
if not actor:
return
return self.bridge_release(path, self.body())
if reject_non_read_http(self, 'POST', path):
return
return self.sendj({'error': 'not_found', 'path': path}, 404)
def bridge_v2_json(self, route, data=None, method='POST'):
base = os.getenv('AIOS_BRIDGE_V2_URL', 'http://127.0.0.1:18892').rstrip('/')
payload = None if data is None else json.dumps(
data, sort_keys=True, separators=(',', ':')
).encode('utf-8')
request = urllib.request.Request(
base + route,
data=payload,
headers={'Content-Type': 'application/json'} if payload is not None else {},
method=method,
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.loads(response.read().decode('utf-8'))
return self.sendj(result, response.status)
except urllib.error.HTTPError as exc:
try:
body = json.loads(exc.read().decode('utf-8'))
except Exception:
body = {'error': 'bridge_http_error'}
return self.sendj(body, exc.code)
except (urllib.error.URLError, TimeoutError) as exc:
return self.sendj(
{'error': 'GOVERNED_BRIDGE_UNAVAILABLE', 'detail': type(exc).__name__},
503,
)
def control_plane_snapshot(self):
"""Build a non-sensitive snapshot locally when Bridge v2 has no snapshot HTTP route."""
base = os.getenv('AIOS_BRIDGE_V2_URL', 'http://127.0.0.1:18892').rstrip('/')
def bridge_get(route):
request = urllib.request.Request(
base + route,
headers={'Accept': 'application/json'},
method='GET',
)
with urllib.request.urlopen(request, timeout=15) as response:
if response.status != 200:
raise RuntimeError('bridge_read_http_' + str(response.status))
value = json.loads(response.read().decode('utf-8'))
if not isinstance(value, dict):
raise RuntimeError('bridge_read_not_object')
return value
try:
runtime = bridge_get('/v2/runtime')
capabilities = bridge_get('/v2/capabilities')
repository = repository_snapshot(
git_dir=AUTHORITATIVE_GIT_DIR,
repository_identity=REPOSITORY_IDENTITY,
)
if not repository.get('repository_available'):
return self.sendj({
'error': 'AUTHORITATIVE_REPOSITORY_UNAVAILABLE',
'query_error': repository.get('query_error'),
}, 503)
baseline = dict(BASELINE_AUTHORITY.read())
except BaselineAuthorityError as exc:
return self.sendj({
'error': 'RECONCILIATION_REQUIRED',
'detail': str(exc),
}, 503)
except urllib.error.HTTPError as exc:
return self.sendj({
'error': 'GOVERNED_BRIDGE_UNAVAILABLE',
'detail': 'HTTP_' + str(exc.code),
}, 503)
except (urllib.error.URLError, TimeoutError, OSError, ValueError, RuntimeError, json.JSONDecodeError) as exc:
return self.sendj({
'error': 'GOVERNED_BRIDGE_UNAVAILABLE',
'detail': type(exc).__name__,
}, 503)
legacy = capabilities.get('legacy_writes_enabled')
if legacy is None:
legacy = capabilities.get('legacy_global_writes_enabled')
payload = {
'schema': 'aios.control-plane-snapshot.aggregate.v1',
'snapshot_source': 'model-api-local-aggregate',
'version': {
'target_release': runtime.get('control_plane_release_version'),
},
'runtime': {
'bridge_schema_version': runtime.get('bridge_schema_version'),
'control_plane_release_version': runtime.get('control_plane_release_version'),
'runtime_release_commit': runtime.get('runtime_release_commit'),
'runtime_path': runtime.get('runtime_path'),
'artifact_sha256': runtime.get('artifact_sha256'),
'drift_status': runtime.get('drift_status'),
'deployment_state': runtime.get('deployment_state'),
},
'repository': {
'identity': runtime.get('repository_identity') or runtime.get('repository') or REPOSITORY_IDENTITY,
'authority_git_dir': runtime.get('authoritative_git_dir') or repository.get('authoritative_git_dir'),
'authority_main_oid': repository.get('authoritative_head_oid'),
},
'production': {
'baseline_version': baseline.get('current_baseline'),
'target_oid': baseline.get('target_oid'),
'deployment_state': baseline.get('status'),
'baseline_generation': baseline.get('generation'),
'baseline_sha256': baseline.get('authority_sha256'),
},
'task': {},
'capabilities': {
'legacy_writes_enabled': legacy,
'governed_writes_enabled': capabilities.get('governed_writes_enabled'),
'force_push': capabilities.get('force_push'),
'main_write': capabilities.get('main_write'),
'tag_write': capabilities.get('tag_write'),
'baseline_write': capabilities.get('baseline_write'),
'production_write': capabilities.get('production_write'),
},
'credential_broker': {
'status': 'SERVER_SIDE_ONLY',
'provider': 'BridgeV2RuntimeCredentialProjection',
},
'secret_material_exposed': False,
}
return self.sendj(payload)
def agent_task_control(self, method, route, data=None):
token_path = Path(os.getenv(
'AIOS_AGENT_TASK_CONTROL_TOKEN_FILE',
'/run/credentials/aios-gitops-model-api-v2r2c1-staging.service/agent_task_control_token',
))
if token_path.is_symlink() or not token_path.is_file():
return self.sendj({'error': 'AGENT_TASK_CONTROL_CREDENTIAL_UNAVAILABLE'}, 503)
token = token_path.read_text(encoding='utf-8').strip()
if len(token) < 32:
return self.sendj({'error': 'AGENT_TASK_CONTROL_CREDENTIAL_INVALID'}, 503)
base = os.getenv('AIOS_AGENT_TASK_API_URL', 'http://127.0.0.1:18940').rstrip('/')
payload = None if data is None else json.dumps(
data, sort_keys=True, separators=(',', ':')
).encode('utf-8')
headers = {'Authorization': 'Bearer ' + token, 'Accept': 'application/json'}
if payload is not None:
headers['Content-Type'] = 'application/json'
request = urllib.request.Request(
base + route, data=payload, headers=headers, method=method
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.loads(response.read().decode('utf-8'))
return self.sendj(result, response.status)
except urllib.error.HTTPError as exc:
try:
body = json.loads(exc.read().decode('utf-8'))
except Exception:
body = {'error': 'agent_task_http_error'}
return self.sendj(body, exc.code)
except (urllib.error.URLError, TimeoutError) as exc:
return self.sendj(
{'error': 'AGENT_TASK_API_UNAVAILABLE', 'detail': type(exc).__name__},
503,
)
def do_GET(self):
u=urllib.parse.urlparse(self.path); path=u.path.rstrip('/') if u.path != '/' else u.path
if path not in READ_ONLY_GET_PATHS: return self.sendj({'error':'not_found','path':path},404)
q=urllib.parse.parse_qs(u.query)
if path == '/api/governed-write-capabilities':
return self.bridge_v2_json('/v2/capabilities', method='GET')
if path == '/api/control-plane-snapshot':
return self.control_plane_snapshot()
if path == '/api/caller-capabilities':
return self.caller_capabilities()
if path in ('/api/online-agent-task', '/api/online-agent-task-events'):
actor = self.need('agent-task:control')
if not actor:
return
task_id = (q.get('task_id') or [''])[0]
if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._:-]{2,179}', task_id):
return self.sendj({'error': 'task_id_invalid'}, 400)
suffix = '/events' if path.endswith('-events') else ''
return self.agent_task_control('GET', '/v1/tasks/' + task_id + suffix)
try:
if path in ('/health','/api/health'):
builder_readiness = silent_release_service().get_builder_readiness()
return self.sendj({
'status': 'ok',
'process_liveness': 'PASS',
'builder_readiness': builder_readiness,
'service': os.getenv('AIOS_GITOPS_MODEL_API_SERVICE', 'aios-gitops-model-api-v2r2c1-staging.service'),
'workspace': str(WS),
'version': 'V2R2C1',
@@ -880,21 +994,70 @@ class H(BaseHTTPRequestHandler):
return self.sendj({'error':'not_found','path':path},404)
def do_POST(self):
path=urllib.parse.urlparse(self.path).path.rstrip('/')
governed_routes = {
'/createGovernedTask': ('/v2/governed-task', 'branch:create'),
'/acquireWriterLease': ('/v2/writer-lease', 'file:write'),
'/commitFileGoverned': ('/v2/commit-file', 'file:write'),
'/commitFilesGoverned': ('/v2/commit-files', 'file:write'),
'/getGitOpsJob': ('/v2/job', 'file:write'),
'/resumeGitOpsJob': ('/v2/job/resume', 'file:write'),
'/reconcileGitOpsJob': ('/v2/job/reconcile', 'file:write'),
'/createTagApprovalRequest': ('/v2/tag/approval-request', 'tag:create'),
'/requestOrCreateTag': ('/v2/tag/publish', 'tag:create'),
'/getTagPublicationStatus': ('/v2/tag/status', 'tag:create'),
'/buildProductionArtifact': ('/v2/release/artifact', 'tag:create'),
'/executeApprovedProductionRelease': ('/v2/release/execute', 'tag:create'),
'/getReleaseConsistency': ('/v2/release/consistency', 'tag:create'),
'/runReleaseAcceptance': ('/v2/release/acceptance', 'tag:create'),
}
if path in governed_routes:
route, scope = governed_routes[path]
if path == '/createGovernedTask' and self.actor()['account'] == CALLER:
scope = GOVERNED_TASK_SCOPE
actor = self.need(scope)
if not actor:
return
return self.bridge_v2_json(route, self.body(), method='POST')
if path in {
'/createOnlineAgentTask', '/registerOnlineAgent',
'/verifyOnlineAgentTask', '/cancelOnlineAgentTask',
}:
actor = self.need('agent-task:control')
if not actor:
return
data = self.body()
if path == '/createOnlineAgentTask':
if requires_governed_execution(data):
return self.sendj(
{
'error': 'governed_execution_binding_required',
'hint': 'use createGovernedTask',
},
409,
)
return self.agent_task_control('POST', '/v1/tasks', data)
if path == '/registerOnlineAgent':
return self.agent_task_control('POST', '/v1/agents/register', data)
task_id = str(data.pop('task_id', '') or '')
if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._:-]{2,179}', task_id):
return self.sendj({'error': 'task_id_invalid'}, 400)
suffix = '/verify' if path == '/verifyOnlineAgentTask' else '/cancel'
return self.agent_task_control('POST', '/v1/tasks/' + task_id + suffix, data)
if reject_non_read_http(self, 'POST', path): return
data=self.body()
try:
if path == '/createReleaseCandidate':
a=self.need('release:candidate')
if not a: return
service = silent_release_service()
readiness = service.get_builder_readiness()
if readiness.get('status') != 'READY':
return self.sendj({
'error': 'TRUSTED_BUILDER_UNAVAILABLE',
'builder_readiness': readiness,
}, 503)
a = self.need(
'release:candidate',
projection_request={
'source_commit': str(data.get('source_commit') or ''),
'version': str(data.get('version') or ''),
},
)
if not a:
return
return self.sendj(
service.create_release_candidate(data)
silent_release_service().create_release_candidate(data)
)
if path == '/createReleaseIntent':
a=self.need('release:candidate')
@@ -934,13 +1097,6 @@ class H(BaseHTTPRequestHandler):
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
return self.sendj({'error':error},403)
except TrustedCandidateBuilderUnavailable:
monitor.record_failure(
'do_POST', 'TRUSTED_BUILDER_UNAVAILABLE', {'path': path}
)
return self.sendj(
{'error': 'TRUSTED_BUILDER_UNAVAILABLE'}, 503
)
except SagaConflict as e:
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
@@ -1190,7 +1346,7 @@ def api_spec():
return {
'version': 'V2R2C1-IMMUTABLE-STAGING',
'openapi': '/api/openapi.yaml',
'read': ['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run','GET /getReleaseCandidate','GET /getApprovalEnvelope','GET /getReleaseSaga','GET /admin/ai-evolution'],
'read': ['GET /api/caller-capabilities','GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run','GET /getReleaseCandidate','GET /getApprovalEnvelope','GET /getReleaseSaga','GET /admin/ai-evolution'],
'write': ['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl','POST /createReleaseCandidate','POST /submitOwnerApproval','POST /startDeployment'],
'policy': {
'bearer_required_for_write': True,
@@ -1205,6 +1361,9 @@ def api_spec():
'silent_release_shadow_default': True,
'runtime_mode': 'IMMUTABLE_STAGING',
'governed_release_routing': 'bridge-v2',
'caller_admission_projection': 'durable-owner-policy-server-side-ack',
'caller_projection_scope': SCOPE,
'caller_projection_caller': CALLER,
},
}
@@ -179,10 +179,7 @@ def _agent_runtime_input(value: object) -> dict[str, Any]:
if not isinstance(value, Mapping):
_error("AGENT_RUNTIME_INPUT_REQUIRED")
raw = dict(value)
allowed = {
"goal", "instructions", "success_criteria", "constraints",
"scope", "trace_id",
}
allowed = {"goal", "instructions", "success_criteria", "constraints"}
if set(raw) - allowed:
_error("AGENT_RUNTIME_INPUT_FIELDS_INVALID")
goal = _string(raw.get("goal"), "AGENT_RUNTIME_GOAL_REQUIRED")
@@ -211,34 +208,11 @@ def _agent_runtime_input(value: object) -> dict[str, Any]:
lowered = str(key).lower()
if any(marker in lowered for marker in sensitive_markers):
_error("AGENT_RUNTIME_SECRET_FIELD_DENY")
scope = raw.get("scope", {})
if not isinstance(scope, Mapping):
_error("AGENT_RUNTIME_SCOPE_INVALID")
scope = {
"scope_type": str(scope.get("scope_type") or "legacy"),
"tenant_id": scope.get("tenant_id"),
"project_id": scope.get("project_id"),
}
if scope["scope_type"] not in {"legacy", "system", "tenant"}:
_error("AGENT_RUNTIME_SCOPE_INVALID")
if scope["scope_type"] == "tenant":
if not isinstance(scope["tenant_id"], str) or not scope["tenant_id"].strip():
_error("AGENT_RUNTIME_SCOPE_INVALID")
elif scope["tenant_id"] not in (None, "") or scope["project_id"] not in (None, ""):
_error("AGENT_RUNTIME_SCOPE_INVALID")
trace_id = str(raw.get("trace_id") or "").strip()
if not trace_id or len(trace_id) > 255:
_error("AGENT_RUNTIME_TRACE_ID_INVALID")
normalized = {
"goal": goal[:12000],
"instructions": instructions[:32000],
"success_criteria": normalized_criteria,
"constraints": dict(constraints),
"scope": scope,
"trace_id": trace_id,
}
if len(_canonical(normalized).encode("utf-8")) > MAX_AGENT_RUNTIME_INPUT_BYTES:
_error("AGENT_RUNTIME_INPUT_TOO_LARGE")
@@ -451,12 +425,6 @@ def project_online_task(
if adapter not in AGENT_RUNTIME_ADAPTERS:
_error("AGENT_RUNTIME_ADAPTER_INVALID")
runtime_input = execution_value.get("runtime_input")
tenant_id = request.get("tenant_id")
project_id = request.get("project_id")
declared_scope = str(
request.get("scope_type")
or ("tenant" if isinstance(tenant_id, str) and tenant_id.strip() else "legacy")
)
if runtime_input is None:
runtime_input = {
"goal": request.get("goal"),
@@ -464,20 +432,6 @@ def project_online_task(
"success_criteria": request.get("success_criteria") or [],
"constraints": request.get("constraints") or {},
}
elif not isinstance(runtime_input, Mapping):
_error("AGENT_RUNTIME_INPUT_REQUIRED")
runtime_input = dict(runtime_input)
# Scope/trace are authoritative Task identity, never caller-selected runtime hints.
runtime_input["scope"] = {
"scope_type": declared_scope,
"tenant_id": tenant_id,
"project_id": project_id,
}
runtime_input["trace_id"] = str(
request.get("trace_id")
or request.get("run_id")
or binding.task_id
)
execution_value["runtime_input"] = runtime_input
execution = project_execution_envelope(
binding,
+29 -39
View File
@@ -1910,6 +1910,8 @@ class WorkerSupervisor:
# engineering placeholder: never pass that stale payload back to the
# resolver, and never claim it until a deterministic plan exists.
pending_plan = source_capture_requires_deterministic_plan(persisted)
if not pending_plan:
return persisted, "persisted_verified"
projection_input = (
execution
if persisted.action in {"source_capture", "agent_runtime"} and not pending_plan
@@ -1958,25 +1960,7 @@ class WorkerSupervisor:
phase="projection",
)
return persisted, "persisted_verified"
status, projected = self.bridge.project_execution(task)
if status == 404 or projected.get("error") == "TASK_EXECUTION_PROJECTION_MISSING":
raise WorkerError("TASK_SKIPPED_PROJECTION_MISSING", phase="projection")
if not 200 <= status < 300:
raise WorkerError(
"TASK_SKIPPED_POLICY_DENY",
phase="projection",
details={"api_status": status, "error": projected.get("error")},
)
envelope = ExecutionEnvelope.from_task(task, projected, WORKER_CAPABILITIES)
audit(
"TASK_EXECUTION_ENVELOPE_PROJECTED",
task_id=envelope.task_id,
job_id=envelope.job_id,
generation=envelope.generation,
action=envelope.action,
execution_digest=envelope.digest,
)
return envelope, "recovered_projection"
raise WorkerError("TASK_SKIPPED_PROJECTION_MISSING", phase="projection")
def process_claim(
self,
@@ -2448,23 +2432,21 @@ class WorkerSupervisor:
write_worker_liveness("POLL_OK", visible_count=len(tasks))
audit("TASK_POLL", visible_count=len(tasks))
except WorkerError as exc:
write_worker_liveness(
"POLL_FAILED",
error=exc.code,
)
write_worker_liveness("POLL_FAILED", error=exc.code)
audit("TASK_POLL_FAILED", error=exc.code, phase=exc.phase, details=exc.details)
return 10
dynamic_tasks: list[tuple[dict[str, Any], ExecutionEnvelope]] = []
dynamic_tasks: list[tuple[dict[str, Any], ExecutionEnvelope, str]] = []
for task in tasks:
try:
envelope, source = self.resolve_envelope(task)
metadata = task.get("metadata")
execution = metadata.get("execution") if isinstance(metadata, Mapping) else None
if not isinstance(execution, Mapping):
raise WorkerError("TASK_SKIPPED_PROJECTION_MISSING", phase="projection")
envelope = ExecutionEnvelope.from_task(task, worker_capabilities=WORKER_CAPABILITIES)
source = "pending_plan" if source_capture_requires_deterministic_plan(envelope) else "persisted_verified"
except WorkerError as exc:
event = "TASK_SKIPPED_ENVELOPE_INVALID"
if exc.code in {
"TASK_SKIPPED_PROJECTION_MISSING",
"TASK_SKIPPED_POLICY_DENY",
"TASK_SKIPPED_PENDING_DETERMINISTIC_PLAN",
}:
if exc.code in {"TASK_SKIPPED_PROJECTION_MISSING", "TASK_SKIPPED_POLICY_DENY", "TASK_SKIPPED_PENDING_DETERMINISTIC_PLAN"}:
event = exc.code
elif exc.code == "CAPABILITY_MISMATCH_DENY":
event = "TASK_SKIPPED_CAPABILITY_MISMATCH"
@@ -2472,16 +2454,11 @@ class WorkerSupervisor:
continue
audit("TASK_VISIBLE_TO_WORKER_CLAIMABLE", task_id=task.get("task_id"), envelope_source=source, action=envelope.action)
if envelope.adapter not in WORKER_ALLOWED_ADAPTERS:
audit(
"TASK_SKIPPED_ADAPTER_MISMATCH",
task_id=task.get("task_id"),
adapter=envelope.adapter,
allowed_adapters=sorted(WORKER_ALLOWED_ADAPTERS),
)
audit("TASK_SKIPPED_ADAPTER_MISMATCH", task_id=task.get("task_id"), adapter=envelope.adapter, allowed_adapters=sorted(WORKER_ALLOWED_ADAPTERS))
continue
dynamic_tasks.append((task, envelope))
dynamic_tasks.sort(key=_claim_order)
for task, envelope in dynamic_tasks:
dynamic_tasks.append((task, envelope, source))
dynamic_tasks.sort(key=lambda item: _claim_order(item[0]))
for task, envelope, source in dynamic_tasks:
task_id = str(task.get("task_id") or "")
status, claim = self.agent.claim(task_id)
if status == 409:
@@ -2490,6 +2467,19 @@ class WorkerSupervisor:
if status != 200:
audit("TASK_CLAIM_FAILED", task_id=task_id, api_status=status, error=claim.get("error"))
continue
if source == "pending_plan":
current_status, current_task = self.agent.get(task_id)
if current_status == 200:
try:
envelope, _ = self.resolve_envelope(current_task)
except WorkerError as exc:
audit("TASK_PLAN_RESOLUTION_FAILED", task_id=task_id, reason=exc.code, phase=exc.phase, details=exc.details)
self.process_claim(task, claim, envelope)
return 0
else:
audit("TASK_READ_AFTER_CLAIM_FAILED", task_id=task_id, api_status=current_status)
self.process_claim(task, claim, envelope)
return 0
self.process_claim(task, claim, envelope)
return 0
return 0
@@ -0,0 +1,287 @@
"""Credential-isolated durable coordinator for the release-preparation supervisor.
The coordinator owns only its Online Task agent credential. It never forwards
that credential to Hermes/OpenClaw and never submits a Task result while the
supervision goal is incomplete. Its job is deliberately small: claim the one
fixed supervisor Task after an exact coordinator projection and keep a durable
heartbeat lease while later phases observe and repair through governed APIs.
"""
from __future__ import annotations
from datetime import datetime, timezone
import json
import os
from pathlib import Path
import tempfile
import time
from typing import Any, Mapping
import urllib.error
import urllib.request
API_BASE = os.environ.get(
"AIOS_AGENT_TASK_API_URL", "http://127.0.0.1:18940"
).rstrip("/")
AGENT_ID = os.environ.get(
"AIOS_AGENT_ID",
"online-engineering-worker-cn-network-mkt-autonomous-20260815",
)
TASK_ID = os.environ.get(
"AIOS_RELEASE_PREPARATION_SUPERVISOR_TASK_ID",
"r42-release-preparation-autonomous-supervisor-20260820",
)
EXPECTED_EXECUTION_DIGEST = os.environ.get(
"AIOS_RELEASE_PREPARATION_SUPERVISOR_EXECUTION_DIGEST", ""
)
RUNTIME_CAPABILITY = "agent.runtime.openclaw"
COORDINATOR_CAPABILITY = "engineering.autonomous"
REQUIRED_CAPABILITIES = [RUNTIME_CAPABILITY, COORDINATOR_CAPABILITY]
POLL_SECONDS = int(os.environ.get("AIOS_SUPERVISOR_COORDINATOR_POLL_SECONDS", "5"))
HEARTBEAT_SECONDS = int(
os.environ.get("AIOS_SUPERVISOR_COORDINATOR_HEARTBEAT_SECONDS", "10")
)
LEASE_SECONDS = int(os.environ.get("AIOS_SUPERVISOR_COORDINATOR_LEASE_SECONDS", "300"))
STATE_ROOT = Path(
os.environ.get(
"AIOS_SUPERVISOR_COORDINATOR_STATE_ROOT",
"/var/lib/aios-release-preparation-supervisor-coordinator",
)
)
class CoordinatorError(RuntimeError):
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
def now() -> str:
return datetime.now(timezone.utc).isoformat()
def canonical(value: object) -> bytes:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
).encode("utf-8")
def atomic_json(path: Path, value: Mapping[str, Any]) -> None:
path.parent.mkdir(parents=True, mode=0o750, exist_ok=True)
if path.parent.is_symlink() or path.is_symlink():
raise CoordinatorError("COORDINATOR_STATE_SYMLINK_REJECTED")
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=str(path.parent))
try:
os.fchmod(fd, 0o640)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(dict(value), handle, ensure_ascii=False, sort_keys=True)
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, path)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def audit(event_type: str, **fields: Any) -> None:
record = {
"event_type": event_type,
"created_at": now(),
"agent_id": AGENT_ID,
"task_id": TASK_ID,
"credential_material_exposed": False,
"planner_credential_material_exposed": False,
"lease_material_exposed": False,
**fields,
}
STATE_ROOT.mkdir(parents=True, mode=0o750, exist_ok=True)
with (STATE_ROOT / "events.jsonl").open("a", encoding="utf-8") as handle:
handle.write(json.dumps(record, ensure_ascii=False, sort_keys=True) + "\n")
atomic_json(STATE_ROOT / "liveness.json", record)
def api_call(
method: str,
route: str,
token: str,
body: Mapping[str, Any] | None = None,
) -> tuple[int, dict[str, Any]]:
payload = None if body is None else canonical(dict(body))
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
if payload is not None:
headers["Content-Type"] = "application/json"
request = urllib.request.Request(
API_BASE + route, data=payload, headers=headers, method=method
)
try:
with urllib.request.urlopen(request, timeout=20) as response:
value = json.loads(response.read().decode("utf-8"))
return response.status, value if isinstance(value, dict) else {}
except urllib.error.HTTPError as exc:
try:
value = json.loads(exc.read(4096).decode("utf-8", "replace"))
except Exception:
value = {"error": "HTTP_ERROR"}
return exc.code, value if isinstance(value, dict) else {"error": "HTTP_ERROR"}
except (urllib.error.URLError, TimeoutError):
return 599, {"error": "AGENT_TASK_API_UNAVAILABLE"}
def load_agent_token() -> str:
path = Path(os.environ.get("CREDENTIALS_DIRECTORY", "")) / "agent_token"
if path.is_symlink() or not path.is_file():
raise CoordinatorError("COORDINATOR_AGENT_CREDENTIAL_UNAVAILABLE")
token = path.read_text(encoding="utf-8").strip()
if len(token) < 24 or any(character.isspace() for character in token):
raise CoordinatorError("COORDINATOR_AGENT_CREDENTIAL_INVALID")
return token
def validate_task(task: Mapping[str, Any]) -> dict[str, Any]:
if task.get("task_id") != TASK_ID or task.get("status") != "QUEUED":
raise CoordinatorError("SUPERVISOR_TASK_STATE_INVALID")
if task.get("required_capabilities") != REQUIRED_CAPABILITIES:
raise CoordinatorError("SUPERVISOR_COORDINATOR_CAPABILITIES_INVALID")
metadata = task.get("metadata")
if not isinstance(metadata, Mapping):
raise CoordinatorError("SUPERVISOR_EXECUTION_METADATA_MISSING")
execution = metadata.get("execution")
if not isinstance(execution, Mapping):
raise CoordinatorError("SUPERVISOR_EXECUTION_METADATA_MISSING")
governance = execution.get("governance")
if (
execution.get("action") != "agent_runtime"
or execution.get("adapter") != "openclaw"
or execution.get("capability_scope") != REQUIRED_CAPABILITIES
or execution.get("execution_digest") != EXPECTED_EXECUTION_DIGEST
or execution.get("change_set") != []
or execution.get("verification") != []
or not isinstance(governance, Mapping)
or governance.get("writer_lease") is not False
or any(
governance.get(field) is not False
for field in (
"main_write",
"production_write",
"tag_write",
"baseline_write",
"force_push",
"protected_ref_bypass",
)
)
):
raise CoordinatorError("SUPERVISOR_COORDINATOR_ENVELOPE_INVALID")
return dict(execution)
def heartbeat_progress(sequence: int) -> dict[str, Any]:
return {
"stage": "supervisor_coordinator_active",
"phase": "external_caller_projection",
"state": "PENDING_EXTERNAL_CONNECTOR_ACK",
"sequence": sequence,
"control_plane_access_granted": True,
"external_planner_role": "UNPRIVILEGED_PLANNER",
"credential_forwarded_to_planner": False,
"main_write": False,
"production_write": False,
"owner_approval_submitted": False,
}
def coordinate_claim(
claim: Mapping[str, Any],
token: str,
*,
max_heartbeats: int | None = None,
) -> int:
lease_token = claim.get("lease_token")
if not isinstance(lease_token, str) or len(lease_token) < 24:
raise CoordinatorError("COORDINATOR_LEASE_TOKEN_MISSING")
sequence = 0
while max_heartbeats is None or sequence < max_heartbeats:
sequence += 1
status, response = api_call(
"POST",
f"/v1/tasks/{TASK_ID}/heartbeat",
token,
{
"lease_token": lease_token,
"lease_seconds": LEASE_SECONDS,
"progress": heartbeat_progress(sequence),
},
)
if status != 200:
raise CoordinatorError(
"COORDINATOR_HEARTBEAT_FAILED_" + str(response.get("error") or status)
)
audit(
"SUPERVISOR_COORDINATOR_HEARTBEAT",
sequence=sequence,
task_status=response.get("status"),
lease_expires_at=response.get("lease_expires_at"),
control_plane_access_granted=True,
external_planner_role="UNPRIVILEGED_PLANNER",
)
if max_heartbeats is None or sequence < max_heartbeats:
time.sleep(HEARTBEAT_SECONDS)
return sequence
def worker_once(token: str) -> bool:
status, poll = api_call("POST", "/v1/tasks/poll", token, {"limit": 100})
if status != 200:
audit("SUPERVISOR_COORDINATOR_POLL_FAILED", error=poll.get("error") or status)
return False
task = next(
(
item
for item in poll.get("tasks", [])
if isinstance(item, Mapping) and item.get("task_id") == TASK_ID
),
None,
)
if task is None:
audit("SUPERVISOR_COORDINATOR_WAITING_FOR_TASK")
return False
validate_task(task)
status, claim = api_call(
"POST", f"/v1/tasks/{TASK_ID}/claim", token, {"lease_seconds": LEASE_SECONDS}
)
if status != 200:
audit("SUPERVISOR_COORDINATOR_CLAIM_FAILED", error=claim.get("error") or status)
return False
audit(
"SUPERVISOR_COORDINATOR_CLAIMED",
attempt=claim.get("attempt"),
revision=claim.get("revision"),
claim_mode="server_atomic",
control_plane_access_granted=True,
)
coordinate_claim(claim, token)
return True
def main() -> int:
if not EXPECTED_EXECUTION_DIGEST or len(EXPECTED_EXECUTION_DIGEST) != 64:
raise CoordinatorError("SUPERVISOR_EXECUTION_DIGEST_CONFIG_INVALID")
token = load_agent_token()
audit(
"SUPERVISOR_COORDINATOR_READY",
required_capabilities=REQUIRED_CAPABILITIES,
control_plane_access_granted=True,
external_planner_role="UNPRIVILEGED_PLANNER",
)
while True:
try:
worker_once(token)
except Exception as exc:
audit(
"SUPERVISOR_COORDINATOR_LOOP_ERROR",
error=exc.code if isinstance(exc, CoordinatorError) else type(exc).__name__,
)
time.sleep(POLL_SECONDS)
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,794 +0,0 @@
#!/usr/bin/env python3
"""Fail-closed AF_UNIX builder for server-owned trusted candidate evidence."""
from __future__ import annotations
import argparse
import base64
import hashlib
import hmac
import json
import os
import secrets
import socket
import stat
import struct
import subprocess
import time
from datetime import datetime, timezone
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol
class BuilderError(RuntimeError):
"""A stable, non-sensitive error code returned by the builder boundary."""
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
def _sha256(*parts: str) -> str:
return hashlib.sha256("\0".join(parts).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class PeerIdentity:
pid: int
uid: int
gid: int
class DomainPurposeSigner(Protocol):
"""A signer deliberately limited to the builder's one domain purpose."""
@property
def key_id(self) -> str:
...
@property
def key_generation(self) -> int:
...
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
...
class CredentialPathEd25519Signer:
"""Reads a systemd-mounted credential path; never exports or logs key bytes."""
def __init__(self, credential_path: Path, *, key_id: str, key_generation: int, allowed_purpose: str):
self._credential_path = Path(credential_path)
self._key_id = str(key_id)
self._key_generation = key_generation
self._allowed_purpose = str(allowed_purpose)
if not self._key_id or not isinstance(key_generation, int) or key_generation < 1:
raise BuilderError("SIGNING_IDENTITY_INVALID")
if not self._credential_path.is_file():
raise BuilderError("SIGNING_CREDENTIAL_UNAVAILABLE")
try:
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
self._private_key = load_pem_private_key(self._credential_path.read_bytes(), password=None)
if not isinstance(self._private_key, Ed25519PrivateKey):
raise TypeError("signing key is not Ed25519")
except Exception as exc: # do not expose parse/key material details
raise BuilderError("SIGNING_CREDENTIAL_INVALID") from exc
@property
def key_id(self) -> str:
return self._key_id
@property
def key_generation(self) -> int:
return self._key_generation
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
if purpose != self._allowed_purpose:
raise BuilderError("SIGNING_PURPOSE_DENIED")
return self._private_key.sign(payload)
@dataclass(frozen=True)
class BuilderKeyring:
builder_hmac: bytes
signing_private_key_path: Path | None = None
def __post_init__(self) -> None:
if len(self.builder_hmac) < 32:
raise BuilderError("BUILDER_HMAC_INVALID")
@classmethod
def from_credentials_directory(cls, credentials_directory: Path) -> "BuilderKeyring":
root = Path(credentials_directory)
hmac_path = root / "builder_hmac"
signing_path = root / "signing_private_key"
try:
secret = hmac_path.read_bytes().strip()
except OSError as exc:
raise BuilderError("BUILDER_HMAC_UNAVAILABLE") from exc
if not signing_path.is_file():
raise BuilderError("SIGNING_CREDENTIAL_UNAVAILABLE")
return cls(builder_hmac=secret, signing_private_key_path=signing_path)
@dataclass(frozen=True)
class TrustedCandidateBuilderConfig:
policy_schema: str
repository: str
request_schema: str
response_schema: str
operation: str
readiness_operation: str
maximum_request_age_seconds: int
request_timeout_seconds: int
allowed_peer_uid: int
allowed_peer_cgroups: tuple[str, ...]
allowed_peer_executable_prefix: str
allowed_peer_runtime_prefix: str
allowed_peer_runtime_suffix: str
required_checks: tuple[str, ...]
evidence_layout: Mapping[str, str]
recipes: Mapping[str, Mapping[str, str]]
governed_recipes: Mapping[str, Mapping[str, str]]
policy_sha256: str
@classmethod
def from_policy(cls, policy: Mapping[str, Any]) -> "TrustedCandidateBuilderConfig":
required = {
"schema", "repository", "request_schema", "response_schema", "operation",
"readiness_operation", "maximum_request_age_seconds", "request_timeout_seconds",
"allowed_peer_uid", "allowed_peer_cgroups", "allowed_peer_executable_prefix",
"allowed_peer_runtime_prefix", "allowed_peer_runtime_suffix",
"required_checks", "evidence_layout", "recipes", "governed_recipes",
}
if set(policy) != required or policy.get("schema") != "aios.trusted-candidate-builder-policy.v1":
raise BuilderError("POLICY_SCHEMA_INVALID")
recipes = policy.get("recipes")
if not isinstance(recipes, Mapping) or set(recipes) != {"V2R2C2", "V2R2C3"}:
raise BuilderError("POLICY_RECIPES_INVALID")
c2, c3 = recipes["V2R2C2"], recipes["V2R2C3"]
if not isinstance(c2, Mapping) or set(c2) != {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose"} or c2.get("mode") != "fixed":
raise BuilderError("POLICY_V2R2C2_INVALID")
if not isinstance(c3, Mapping) or set(c3) != {"mode", "derives_from", "version", "purpose"} or c3.get("mode") != "governed-derived-only" or c3.get("derives_from") != "V2R2C2":
raise BuilderError("POLICY_V2R2C3_INVALID")
if (
policy["repository"] != "aios/aios"
or policy["operation"] != "BuildTrustedCandidateV1"
or policy["readiness_operation"] != "ProbeTrustedCandidateBuilderV1"
):
raise BuilderError("POLICY_SCOPE_INVALID")
if not isinstance(policy["maximum_request_age_seconds"], int) or not 1 <= policy["maximum_request_age_seconds"] <= 300:
raise BuilderError("POLICY_FRESHNESS_INVALID")
if not isinstance(policy["request_timeout_seconds"], int) or not 1 <= policy["request_timeout_seconds"] <= 120:
raise BuilderError("POLICY_TIMEOUT_INVALID")
if not isinstance(policy["allowed_peer_uid"], int) or policy["allowed_peer_uid"] != 1000:
raise BuilderError("POLICY_PEER_UID_INVALID")
cgroups, checks = policy["allowed_peer_cgroups"], policy["required_checks"]
if not isinstance(cgroups, list) or not cgroups or not all(isinstance(value, str) and value.startswith("/") for value in cgroups):
raise BuilderError("POLICY_CGROUPS_INVALID")
for field in (
"allowed_peer_executable_prefix",
"allowed_peer_runtime_prefix",
"allowed_peer_runtime_suffix",
):
if not isinstance(policy[field], str) or not policy[field].startswith("/"):
raise BuilderError("POLICY_PROCESS_IDENTITY_INVALID")
if not isinstance(checks, list) or not checks or len(set(checks)) != len(checks) or not all(isinstance(value, str) for value in checks):
raise BuilderError("POLICY_CHECKS_INVALID")
layout = policy["evidence_layout"]
evidence_names = {
"artifact", "manifest", "package_closure", "tests", "canary",
"gitea", "rollback", "policy", "secret_scan",
}
if (
not isinstance(layout, Mapping)
or set(layout) != evidence_names
or not all(
isinstance(value, str)
and value.startswith("{version}/")
and ".." not in Path(value).parts
and not Path(value).is_absolute()
for value in layout.values()
)
):
raise BuilderError("POLICY_EVIDENCE_LAYOUT_INVALID")
return cls(
policy_schema=str(policy["schema"]), repository=str(policy["repository"]),
request_schema=str(policy["request_schema"]), response_schema=str(policy["response_schema"]),
operation=str(policy["operation"]), readiness_operation=str(policy["readiness_operation"]),
maximum_request_age_seconds=policy["maximum_request_age_seconds"],
request_timeout_seconds=policy["request_timeout_seconds"],
allowed_peer_uid=policy["allowed_peer_uid"], allowed_peer_cgroups=tuple(cgroups),
allowed_peer_executable_prefix=policy["allowed_peer_executable_prefix"],
allowed_peer_runtime_prefix=policy["allowed_peer_runtime_prefix"],
allowed_peer_runtime_suffix=policy["allowed_peer_runtime_suffix"],
required_checks=tuple(checks), evidence_layout=dict(layout),
recipes={name: dict(value) for name, value in recipes.items()},
governed_recipes={name: dict(value) for name, value in policy["governed_recipes"].items()} if isinstance(policy["governed_recipes"], Mapping) else _invalid_governed_recipes(),
policy_sha256=hashlib.sha256(_canonical_json(dict(policy))).hexdigest(),
)
def fixed_recipe(self, name: str) -> Mapping[str, str]:
recipe = self.recipes.get(name)
if recipe is None:
raise BuilderError("RECIPE_UNKNOWN")
if recipe["mode"] == "governed-derived-only":
derived = self.governed_recipes.get(name)
required = {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "derived_from_v2r2c2_attestation", "builder_engineering_oid"}
if not isinstance(derived, Mapping) or set(derived) != required or derived.get("mode") != "governed-derived" or derived.get("version") != name or derived.get("purpose") != recipe.get("purpose"):
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
for name in ("base_oid", "source_commit", "builder_engineering_oid"):
value = derived.get(name)
if not isinstance(value, str) or len(value) != 40 or any(char not in "0123456789abcdef" for char in value.lower()):
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
return dict(derived)
return recipe
def resolve_recipe(self, version: str, candidate_id: str, source_commit: str) -> Mapping[str, str]:
recipe = self.fixed_recipe(version)
if {"version": recipe.get("version"), "candidate_id": recipe.get("candidate_id"), "source_commit": recipe.get("source_commit")} != {"version": version, "candidate_id": candidate_id, "source_commit": source_commit}:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
return recipe
def _invalid_governed_recipes() -> Mapping[str, Mapping[str, str]]:
raise BuilderError("POLICY_GOVERNED_RECIPES_INVALID")
def load_builder_policy(path: Path) -> Mapping[str, Any]:
try:
decoded = json.loads(Path(path).read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise BuilderError("POLICY_UNAVAILABLE") from exc
if not isinstance(decoded, Mapping):
raise BuilderError("POLICY_SCHEMA_INVALID")
TrustedCandidateBuilderConfig.from_policy(decoded)
return decoded
class ServerOwnedEvidenceSource:
"""Recomputes a fixed on-host evidence closure without caller-owned paths."""
def __init__(
self,
config: TrustedCandidateBuilderConfig,
authority_git_dir: Path,
evidence_root: Path,
*,
expected_owner_uid: int | None = 0,
git_runner: Callable[..., Any] = subprocess.run,
):
self._config = config
self._authority_git_dir = Path(authority_git_dir)
self._evidence_root = Path(evidence_root)
self._expected_owner_uid = expected_owner_uid
self._git_runner = git_runner
def _regular_path(self, relative: str) -> Path:
root = self._evidence_root.resolve(strict=True)
candidate = self._evidence_root / relative
current = self._evidence_root
for part in Path(relative).parts:
current = current / part
if current.is_symlink():
raise BuilderError("COLLECTOR_SYMLINK_DENIED")
try:
resolved = candidate.resolve(strict=True)
resolved.relative_to(root)
metadata = resolved.stat(follow_symlinks=False)
except (OSError, ValueError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if not stat.S_ISREG(metadata.st_mode):
raise BuilderError("COLLECTOR_SPECIAL_FILE_DENIED")
if os.name != "nt" and metadata.st_mode & 0o022:
raise BuilderError("COLLECTOR_EVIDENCE_MODE_DENIED")
if (
os.name != "nt"
and
self._expected_owner_uid is not None
and metadata.st_uid != self._expected_owner_uid
):
raise BuilderError("COLLECTOR_EVIDENCE_OWNER_DENIED")
return resolved
def _digest(self, path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _json(self, path: Path) -> Mapping[str, Any]:
try:
if path.stat().st_size > 2 * 1024 * 1024:
raise BuilderError("COLLECTOR_EVIDENCE_SIZE_INVALID")
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID") from exc
if not isinstance(value, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
return value
def __call__(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
try:
tree_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "rev-parse", f"{recipe['source_commit']}^{{tree}}"],
check=True, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
ancestry_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "merge-base", "--is-ancestor", recipe["base_oid"], recipe["source_commit"]],
check=False, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
tree_oid = tree_result.stdout.strip().lower()
except (OSError, subprocess.SubprocessError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if getattr(ancestry_result, "returncode", 1) != 0:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
receipt = self._json(self._regular_path(f"{recipe_name}/receipt.json"))
if set(receipt) != {"schema", "version", "candidate", "release_plan"}:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
if (
receipt.get("schema") != "aios.trusted-candidate-builder.evidence.v1"
or receipt.get("version") != recipe_name
):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
candidate = receipt.get("candidate")
release_plan = receipt.get("release_plan")
if not isinstance(candidate, Mapping) or not isinstance(release_plan, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
if (
candidate.get("candidate_id") != recipe["candidate_id"]
or candidate.get("source_commit") != recipe["source_commit"]
or candidate.get("tree_oid") != tree_oid
):
raise BuilderError("COLLECTOR_GIT_MISMATCH")
git_consistency = candidate.get("git_consistency")
if (
not isinstance(git_consistency, Mapping)
or git_consistency.get("consistent") is not True
or git_consistency.get("source_commit") != recipe["source_commit"]
or git_consistency.get("tree_oid") != tree_oid
or git_consistency.get("repository") != self._config.repository
):
raise BuilderError("COLLECTOR_GIT_MISMATCH")
paths = {
name: self._regular_path(template.format(version=recipe_name))
for name, template in self._config.evidence_layout.items()
}
deployment = release_plan.get("deployment")
if not isinstance(deployment, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
digests = {name: self._digest(path) for name, path in paths.items()}
if (
digests["artifact"] != candidate.get("artifact_sha256")
or digests["manifest"] != candidate.get("manifest_sha256")
or digests["package_closure"] != deployment.get("package_closure_sha256")
or digests["policy"] != release_plan.get("policy_sha256")
or digests["policy"] != deployment.get("policy_sha256")
):
raise BuilderError("COLLECTOR_DIGEST_MISMATCH")
tests = self._json(paths["tests"])
canary = self._json(paths["canary"])
gitea = self._json(paths["gitea"])
rollback = self._json(paths["rollback"])
secret_scan = self._json(paths["secret_scan"])
if tests != candidate.get("tests") or tests.get("passed") is not True:
raise BuilderError("COLLECTOR_TESTS_MISMATCH")
if canary != candidate.get("canary") or canary.get("passed") is not True:
raise BuilderError("COLLECTOR_CANARY_MISMATCH")
if (
digests["tests"] != hashlib.sha256(_canonical_json(dict(tests))).hexdigest()
or digests["canary"] != hashlib.sha256(_canonical_json(dict(canary))).hexdigest()
):
raise BuilderError("COLLECTOR_EVIDENCE_CANONICALIZATION_MISMATCH")
if (
gitea != candidate.get("gitea_consistency")
or gitea.get("consistent") is not True
or gitea.get("repository") != self._config.repository
or gitea.get("source_commit") != recipe["source_commit"]
or gitea.get("tree_oid") != tree_oid
or gitea.get("remote_ref_oid") != recipe["source_commit"]
):
raise BuilderError("COLLECTOR_GITEA_MISMATCH")
if (
candidate.get("rollback_ready") is not True
or rollback.get("ready") is not True
or rollback.get("source_commit") != recipe["source_commit"]
or rollback.get("rollback_sha256") != deployment.get("rollback_sha256")
):
raise BuilderError("COLLECTOR_ROLLBACK_MISMATCH")
if (
digests["gitea"] != hashlib.sha256(_canonical_json(dict(gitea))).hexdigest()
or digests["rollback"] != hashlib.sha256(_canonical_json(dict(rollback))).hexdigest()
):
raise BuilderError("COLLECTOR_EVIDENCE_CANONICALIZATION_MISMATCH")
if (
secret_scan.get("passed") is not True
or secret_scan.get("artifact_sha256") != candidate.get("artifact_sha256")
):
raise BuilderError("COLLECTOR_SECRET_SCAN_MISMATCH")
checks = {name: True for name in self._config.required_checks}
return {
"candidate": dict(candidate),
"release_plan": dict(release_plan),
"checks": checks,
"evidence_digests": digests,
}
class DeterministicEvidenceCollector:
"""Validates a server-owned source; request fields never influence collection."""
_BUNDLE_FIELDS = {"candidate", "release_plan", "checks", "evidence_digests"}
def __init__(self, config: TrustedCandidateBuilderConfig, *, evidence_source: Callable[[str, Mapping[str, str]], Mapping[str, Any]] | None = None):
self._config = config
self._evidence_source = evidence_source
def collect(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
if self._evidence_source is None:
raise BuilderError("COLLECTOR_UNCONFIGURED")
bundle = self._evidence_source(recipe_name, recipe)
if not isinstance(bundle, Mapping) or set(bundle) != self._BUNDLE_FIELDS:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
raw_checks = bundle.get("checks")
if (
not isinstance(raw_checks, Mapping)
or set(raw_checks) != set(self._config.required_checks)
or any(raw_checks.get(name) is not True for name in self._config.required_checks)
):
raise BuilderError("COLLECTOR_CHECKS_INCOMPLETE")
evidence_digests = bundle.get("evidence_digests")
if (
not isinstance(evidence_digests, Mapping)
or set(evidence_digests) != set(self._config.evidence_layout)
or any(
not isinstance(value, str)
or len(value) != 64
or any(char not in "0123456789abcdef" for char in value.lower())
for value in evidence_digests.values()
)
):
raise BuilderError("COLLECTOR_EVIDENCE_DIGESTS_INVALID")
try:
from silent_release_controller import ReleaseCandidate
from silent_release_hardening import ReleasePlan, TrustedCandidateBundle
candidate = ReleaseCandidate.from_dict(dict(bundle["candidate"]))
release_plan = ReleasePlan.from_dict(dict(bundle["release_plan"]))
checks = dict(raw_checks)
TrustedCandidateBundle(candidate, release_plan, checks).validate()
except (ImportError, KeyError, TypeError, ValueError, RuntimeError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID") from exc
if candidate.source_commit != recipe["source_commit"] or candidate.candidate_id != recipe["candidate_id"]:
raise BuilderError("COLLECTOR_SOURCE_MISMATCH")
return {
"candidate": candidate.to_dict(),
"release_plan": release_plan.to_dict(),
"checks": checks,
"evidence_digests": dict(evidence_digests),
}
def _extract_peer_identity(connection: socket.socket) -> PeerIdentity:
if not hasattr(socket, "SO_PEERCRED"):
raise BuilderError("SO_PEERCRED_UNAVAILABLE")
raw = connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i"))
if len(raw) != struct.calcsize("3i"):
raise BuilderError("SO_PEERCRED_INVALID")
return PeerIdentity(*struct.unpack("3i", raw))
def _resolve_cgroups(pid: int) -> tuple[str, ...]:
try:
return tuple(
line.rsplit(":", 1)[1].strip()
for line in Path(f"/proc/{pid}/cgroup").read_text(encoding="utf-8").splitlines()
if ":" in line and line.rsplit(":", 1)[1].strip().startswith("/")
)
except OSError as exc:
raise BuilderError("PEER_CGROUP_UNAVAILABLE") from exc
def _resolve_process_identity(pid: int) -> tuple[str, tuple[str, ...]]:
try:
executable = os.path.realpath(f"/proc/{pid}/exe")
argv = tuple(
item.decode("utf-8", "strict")
for item in Path(f"/proc/{pid}/cmdline").read_bytes().split(b"\0")
if item
)
except (OSError, UnicodeDecodeError) as exc:
raise BuilderError("PEER_PROCESS_UNAVAILABLE") from exc
if not executable or not argv:
raise BuilderError("PEER_PROCESS_UNAVAILABLE")
return executable, argv
class TrustedCandidateBuilder:
def __init__(
self, config: TrustedCandidateBuilderConfig, keyring: BuilderKeyring, *,
collector: DeterministicEvidenceCollector, signer: DomainPurposeSigner,
peer_identity_resolver: Callable[[socket.socket], PeerIdentity] = _extract_peer_identity,
cgroup_resolver: Callable[[int], tuple[str, ...] | str] = _resolve_cgroups,
process_identity_resolver: Callable[[int], tuple[str, tuple[str, ...]]] = _resolve_process_identity,
clock: Callable[[], float] = time.time, nonce_ledger_path: Path | None = None,
) -> None:
self.config, self.keyring, self.collector, self.signer = config, keyring, collector, signer
self.peer_identity_resolver, self.cgroup_resolver, self.clock = peer_identity_resolver, cgroup_resolver, clock
self.process_identity_resolver = process_identity_resolver
self._nonce_ledger_path = Path(nonce_ledger_path) if nonce_ledger_path else None
self._nonce_ledger: dict[str, tuple[bytes, dict[str, Any]]] = self._load_nonce_ledger()
def _load_nonce_ledger(self) -> dict[str, tuple[bytes, dict[str, Any]]]:
if self._nonce_ledger_path is None or not self._nonce_ledger_path.exists():
return {}
if self._nonce_ledger_path.is_symlink():
raise BuilderError("NONCE_LEDGER_INVALID")
try:
raw = json.loads(self._nonce_ledger_path.read_text(encoding="utf-8"))
return {nonce: (bytes.fromhex(value["request_sha256"]), dict(value["result"])) for nonce, value in raw.items()}
except (OSError, ValueError, TypeError, KeyError, json.JSONDecodeError) as exc:
raise BuilderError("NONCE_LEDGER_INVALID") from exc
def _persist_nonce_ledger(self) -> None:
if self._nonce_ledger_path is None:
return
try:
self._nonce_ledger_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = self._nonce_ledger_path.with_suffix(".tmp")
payload = {nonce: {"request_sha256": digest.hex(), "result": result} for nonce, (digest, result) in self._nonce_ledger.items()}
with temporary.open("wb") as handle:
handle.write(_canonical_json(payload))
handle.flush()
os.fsync(handle.fileno())
os.chmod(temporary, 0o600)
os.replace(temporary, self._nonce_ledger_path)
if os.name != "nt":
descriptor = os.open(self._nonce_ledger_path.parent, os.O_RDONLY)
try:
os.fsync(descriptor)
finally:
os.close(descriptor)
except OSError as exc:
raise BuilderError("NONCE_LEDGER_PERSIST_FAILED") from exc
def _verify_peer(self, connection: socket.socket) -> None:
peer = self.peer_identity_resolver(connection)
if peer.uid != self.config.allowed_peer_uid or peer.pid <= 0:
raise BuilderError("PEER_IDENTITY_DENIED")
resolved = self.cgroup_resolver(peer.pid)
cgroups = (resolved,) if isinstance(resolved, str) else tuple(resolved)
if not cgroups or not any(item in self.config.allowed_peer_cgroups for item in cgroups):
raise BuilderError("PEER_CGROUP_DENIED")
executable, argv = self.process_identity_resolver(peer.pid)
if not executable.startswith(self.config.allowed_peer_executable_prefix):
raise BuilderError("PEER_PROCESS_DENIED")
runtime_arguments = [
value
for value in argv
if value.startswith(self.config.allowed_peer_runtime_prefix)
and value.endswith(self.config.allowed_peer_runtime_suffix)
]
if len(runtime_arguments) != 1:
raise BuilderError("PEER_PROCESS_DENIED")
def handle_request(self, request: Mapping[str, Any], *, connection: socket.socket) -> dict[str, Any]:
if not isinstance(request, Mapping):
raise BuilderError("REQUEST_SCHEMA_INVALID")
expected_keys = {"schema", "operation", "recipe", "repository", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "issued_at", "nonce", "hmac"}
if (
set(request) != expected_keys
or request.get("schema") != self.config.request_schema
or request.get("operation") not in {
self.config.operation,
self.config.readiness_operation,
}
):
raise BuilderError("REQUEST_SCHEMA_INVALID")
supplied_mac = request.get("hmac")
unsigned = {key: value for key, value in request.items() if key != "hmac"}
if not isinstance(supplied_mac, str) or not hmac.compare_digest(hmac.new(self.keyring.builder_hmac, _canonical_json(unsigned), hashlib.sha256).hexdigest(), supplied_mac):
raise BuilderError("REQUEST_HMAC_INVALID")
issued_at, nonce = request.get("issued_at"), request.get("nonce")
if not isinstance(issued_at, int) or not isinstance(nonce, str) or len(nonce) < 16 or len(nonce) > 256:
raise BuilderError("REQUEST_SCHEMA_INVALID")
self._verify_peer(connection)
request_bytes = _canonical_json(dict(request))
replay = self._nonce_ledger.get(nonce)
if replay is not None:
if hmac.compare_digest(replay[0], hashlib.sha256(request_bytes).digest()):
return dict(replay[1])
raise BuilderError("REPLAY_CONFLICT")
if abs(int(self.clock()) - issued_at) > self.config.maximum_request_age_seconds:
raise BuilderError("REQUEST_STALE")
recipe_name = request.get("recipe")
if not isinstance(recipe_name, str):
raise BuilderError("REQUEST_SCHEMA_INVALID")
recipe = self.config.resolve_recipe(str(request.get("version") or ""), str(request.get("candidate_id") or ""), str(request.get("source_commit") or ""))
if recipe_name != recipe.get("version"):
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
bound = {"repository": self.config.repository, "task_id": recipe["task_id"], "candidate_id": recipe["candidate_id"], "base_oid": recipe["base_oid"], "source_commit": recipe["source_commit"], "version": recipe["version"], "purpose": recipe["purpose"]}
if {key: request.get(key) for key in bound} != bound:
raise BuilderError("REQUEST_SCOPE_INVALID")
bundle = dict(self.collector.collect(recipe_name, recipe))
candidate = bundle["candidate"]
release_plan = bundle["release_plan"]
checks = bundle.get("checks")
evidence_digests = bundle["evidence_digests"]
if not isinstance(checks, Mapping) or set(checks) != set(self.config.required_checks) or not all(value is True for value in checks.values()):
raise BuilderError("COLLECTOR_CHECKS_INCOMPLETE")
if request["operation"] == self.config.readiness_operation:
result = {
"schema": self.config.response_schema,
"status": "READY",
"repository": self.config.repository,
"recipe": recipe_name,
"candidate_id": recipe["candidate_id"],
"source_commit": recipe["source_commit"],
"builder_key_id": self.signer.key_id,
"builder_key_generation": self.signer.key_generation,
"signature_algorithm": "Ed25519",
"signature_purpose": "builder_attestation",
"policy_sha256": self.config.policy_sha256,
"collector_ready": True,
"signer_ready": True,
}
if len(self._nonce_ledger) >= 2048:
self._nonce_ledger.pop(next(iter(self._nonce_ledger)))
self._nonce_ledger[nonce] = (
hashlib.sha256(request_bytes).digest(),
dict(result),
)
self._persist_nonce_ledger()
return result
issued = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
attestation = {
"builder_attestation_id": "builder-attestation-" + _sha256(recipe_name, recipe["task_id"], candidate["artifact_sha256"])[:24],
"builder_key_id": self.signer.key_id,
"builder_key_generation": self.signer.key_generation,
"signature_algorithm": "Ed25519",
"signature_purpose": "builder_attestation",
"source_commit": candidate["source_commit"], "tree_oid": candidate["tree_oid"],
"artifact_sha256": candidate["artifact_sha256"], "manifest_sha256": candidate["manifest_sha256"],
"package_closure_sha256": release_plan["deployment"]["package_closure_sha256"],
"test_evidence_sha256": evidence_digests["tests"],
"canary_evidence_sha256": evidence_digests["canary"],
"gitea_evidence_sha256": evidence_digests["gitea"],
"rollback_evidence_sha256": evidence_digests["rollback"],
"policy_evidence_sha256": evidence_digests["policy"],
"secret_scan_evidence_sha256": evidence_digests["secret_scan"],
"issued_at": issued, "trust_domain": "aios.trusted-builder.attestation.v1",
}
canonical_attestation = _canonical_json(attestation)
fingerprint = hashlib.sha256(canonical_attestation).hexdigest()
signature = base64.b64encode(self.signer.sign_for_purpose("builder_attestation", canonical_attestation)).decode("ascii")
result = {
"schema": self.config.response_schema, "status": "BUILT", "recipe": recipe_name,
"repository": self.config.repository, "task_id": recipe["task_id"], "base_oid": recipe["base_oid"],
"purpose": recipe["purpose"], "checks": dict(checks),
"evidence": {"candidate_id": candidate["candidate_id"], "source_commit": candidate["source_commit"], "tree_oid": candidate["tree_oid"], "artifact_sha256": candidate["artifact_sha256"], "manifest_sha256": candidate["manifest_sha256"], "package_closure_sha256": release_plan["deployment"]["package_closure_sha256"], "test_evidence_sha256": attestation["test_evidence_sha256"], "canary_evidence_sha256": attestation["canary_evidence_sha256"]},
"attestation": {**attestation, "fingerprint": fingerprint, "signature": signature},
}
result["silent_release_bundle"] = {"candidate": candidate, "release_plan": release_plan, "checks": dict(checks), "attestation": result["attestation"]}
if len(self._nonce_ledger) >= 2048:
self._nonce_ledger.pop(next(iter(self._nonce_ledger)))
self._nonce_ledger[nonce] = (hashlib.sha256(request_bytes).digest(), dict(result))
self._persist_nonce_ledger()
return result
def serve_forever(self, listener: socket.socket) -> None:
while True:
connection, _ = listener.accept()
with connection:
try:
raw = connection.makefile("rb").readline(65537)
if not raw or len(raw) > 65536:
raise BuilderError("REQUEST_SIZE_INVALID")
result = self.handle_request(json.loads(raw), connection=connection)
except (BuilderError, UnicodeDecodeError, json.JSONDecodeError):
result = {"schema": self.config.response_schema, "status": "REJECTED", "error_code": "REQUEST_REJECTED"}
connection.sendall(_canonical_json(result) + b"\n")
class UnixTrustedCandidateBuilderClient:
def __init__(self, config: TrustedCandidateBuilderConfig, keyring: BuilderKeyring, *, socket_path: Path | None = None, clock: Callable[[], float] = time.time) -> None:
self.config, self.keyring, self.socket_path, self.clock = config, keyring, socket_path, clock
def create_request(self, version: str, candidate_id: str, source_commit: str, *, nonce: str, operation: str | None = None) -> dict[str, Any]:
selected = self.config.resolve_recipe(version, candidate_id, source_commit)
selected_operation = operation or self.config.operation
if selected_operation not in {self.config.operation, self.config.readiness_operation}:
raise BuilderError("REQUEST_OPERATION_INVALID")
request = {"schema": self.config.request_schema, "operation": selected_operation, "recipe": version, "repository": self.config.repository, "task_id": selected["task_id"], "candidate_id": selected["candidate_id"], "base_oid": selected["base_oid"], "source_commit": selected["source_commit"], "version": selected["version"], "purpose": selected["purpose"], "issued_at": int(self.clock()), "nonce": nonce}
request["hmac"] = hmac.new(self.keyring.builder_hmac, _canonical_json(request), hashlib.sha256).hexdigest()
return request
def _exchange(self, request: Mapping[str, Any]) -> Mapping[str, Any]:
if self.socket_path is None:
raise BuilderError("BUILDER_SOCKET_UNCONFIGURED")
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection:
connection.settimeout(self.config.request_timeout_seconds)
connection.connect(os.fspath(self.socket_path))
connection.sendall(_canonical_json(dict(request)) + b"\n")
raw = connection.makefile("rb").readline(65537)
except (OSError, TimeoutError) as exc:
raise BuilderError("BUILDER_TRANSPORT_UNAVAILABLE") from exc
try:
result = json.loads(raw)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BuilderError("BUILDER_RESPONSE_INVALID") from exc
if not isinstance(result, Mapping) or result.get("schema") != self.config.response_schema:
raise BuilderError("BUILDER_RESPONSE_INVALID")
return result
def build(self, version: str, candidate_id: str, source_commit: str, *, nonce: str) -> Mapping[str, Any]:
result = self._exchange(
self.create_request(version, candidate_id, source_commit, nonce=nonce)
)
if result.get("status") != "BUILT":
raise BuilderError("BUILDER_REQUEST_REJECTED")
return result
def probe(self, *, nonce: str | None = None) -> Mapping[str, Any]:
recipe = self.config.fixed_recipe("V2R2C2")
result = self._exchange(
self.create_request(
"V2R2C2",
recipe["candidate_id"],
recipe["source_commit"],
nonce=nonce or secrets.token_hex(32),
operation=self.config.readiness_operation,
)
)
if result.get("status") != "READY":
raise BuilderError("BUILDER_NOT_READY")
return result
def readiness_probe(client: UnixTrustedCandidateBuilderClient) -> Mapping[str, Any]:
result = dict(client.probe())
required_true = {"collector_ready", "signer_ready"}
if any(result.get(name) is not True for name in required_true):
raise BuilderError("BUILDER_NOT_READY")
return result
def _main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--socket-fd", required=True, type=int)
parser.add_argument("--policy", required=True, type=Path)
arguments = parser.parse_args()
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(arguments.policy))
credentials = os.environ.get("CREDENTIALS_DIRECTORY")
if not credentials:
raise SystemExit("CREDENTIALS_DIRECTORY is required")
keyring = BuilderKeyring.from_credentials_directory(Path(credentials))
signer = CredentialPathEd25519Signer(
keyring.signing_private_key_path,
key_id=os.environ["AIOS_TRUSTED_BUILDER_KEY_ID"],
key_generation=int(os.environ["AIOS_TRUSTED_BUILDER_KEY_GENERATION"]),
allowed_purpose="builder_attestation",
)
listener = socket.fromfd(arguments.socket_fd, socket.AF_UNIX, socket.SOCK_STREAM)
collector = DeterministicEvidenceCollector(
config,
evidence_source=ServerOwnedEvidenceSource(
config,
Path(os.environ["AIOS_TRUSTED_BUILDER_GIT_DIR"]),
Path(os.environ["AIOS_TRUSTED_BUILDER_EVIDENCE_ROOT"]),
),
)
TrustedCandidateBuilder(config, keyring, collector=collector, signer=signer, nonce_ledger_path=Path(os.environ["AIOS_TRUSTED_BUILDER_NONCE_LEDGER"])).serve_forever(listener)
if __name__ == "__main__":
_main()
+32 -590
View File
@@ -9,7 +9,6 @@ from __future__ import annotations
import base64
import hashlib
import hmac
import json
import os
import re
@@ -41,8 +40,6 @@ from silent_release_controller import (
IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$")
NONCE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{15,255}$")
BUILDER_ATTESTATION_TRUST_DOMAIN = "aios.trusted-builder.attestation.v1"
BUILDER_ATTESTATION_PURPOSE = "builder_attestation"
ALLOWED_CAPABILITIES = frozenset(
{
"runtime_switch",
@@ -65,10 +62,6 @@ class CapabilityAuthorizationError(RuntimeError):
"""The signed plan cannot authorize the requested operation."""
class TrustedCandidateBuilderUnavailable(RuntimeError):
"""The purpose-bound builder dependency cannot currently serve a build."""
class StaleApprovalError(CapabilityAuthorizationError):
"""Live release evidence no longer matches the signed approval."""
@@ -229,10 +222,6 @@ JOURNAL_GENESIS_HASH = "0" * 64
def _fsync_directory(path: Path) -> None:
if os.name == "nt":
# Windows does not permit opening a directory with os.open. Durable
# Linux host execution retains the directory fsync below.
return
descriptor = os.open(Path(path), os.O_RDONLY)
try:
os.fsync(descriptor)
@@ -726,14 +715,6 @@ class BuilderAttestation:
canary_evidence_sha256: str
issued_at: str
signature: str
trust_domain: str = BUILDER_ATTESTATION_TRUST_DOMAIN
builder_key_generation: int | None = None
signature_algorithm: str | None = None
signature_purpose: str | None = None
gitea_evidence_sha256: str = ""
rollback_evidence_sha256: str = ""
policy_evidence_sha256: str = ""
secret_scan_evidence_sha256: str = ""
def __post_init__(self) -> None:
_validate_identifier(
@@ -750,35 +731,7 @@ class BuilderAttestation:
"canary_evidence_sha256",
):
_validate_sha(name, getattr(self, name))
for name in (
"gitea_evidence_sha256",
"rollback_evidence_sha256",
"policy_evidence_sha256",
"secret_scan_evidence_sha256",
):
if getattr(self, name):
_validate_sha(name, getattr(self, name))
_parse_utc(self.issued_at)
if self.trust_domain != BUILDER_ATTESTATION_TRUST_DOMAIN:
raise CapabilityAuthorizationError(
"builder attestation trust domain is invalid"
)
metadata = (
self.builder_key_generation,
self.signature_algorithm,
self.signature_purpose,
)
if any(value is not None for value in metadata):
if (
not isinstance(self.builder_key_generation, int)
or isinstance(self.builder_key_generation, bool)
or self.builder_key_generation < 0
or self.signature_algorithm != "Ed25519"
or self.signature_purpose != BUILDER_ATTESTATION_PURPOSE
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata is invalid"
)
@property
def canonical_payload(self) -> dict[str, Any]:
@@ -801,290 +754,20 @@ class BuilderAttestation:
}
@classmethod
def from_dict(
cls,
payload: Mapping[str, Any],
*,
require_fingerprint: bool = False,
) -> "BuilderAttestation":
def from_dict(cls, payload: Mapping[str, Any]) -> "BuilderAttestation":
values = dict(payload)
supplied_fingerprint = values.pop("fingerprint", None)
# Older local records did not spell out the domain. They are only
# usable when re-signed by the current builder; their original
# signatures cannot validate against this domain-separated payload.
values.setdefault("trust_domain", BUILDER_ATTESTATION_TRUST_DOMAIN)
value = cls(**values)
if supplied_fingerprint is None:
if require_fingerprint:
raise CapabilityAuthorizationError(
"builder attestation digest is required"
)
return value
expected = _validate_sha(
"builder attestation digest", str(supplied_fingerprint)
)
if not hmac.compare_digest(expected, value.fingerprint):
raise CapabilityAuthorizationError(
"builder attestation digest is invalid"
)
return value
@dataclass(frozen=True)
class BuilderAttestationKeyRecord:
"""One purpose-bound public verification key from the host keyring."""
key_id: str
public_key: bytes
algorithm: str
purpose: str
generation: int
not_before: str
expires_at: str
status: str
def __post_init__(self) -> None:
_validate_identifier("builder_key_id", self.key_id)
if self.algorithm != "Ed25519":
raise ValueError("unsupported builder signature algorithm")
if self.purpose != BUILDER_ATTESTATION_PURPOSE:
raise ValueError("builder key purpose is invalid")
if not isinstance(self.generation, int) or isinstance(
self.generation, bool
) or self.generation < 0:
raise ValueError("builder key generation is invalid")
if self.status not in {"ACTIVE", "REVOKED"}:
raise ValueError("builder key status is invalid")
if len(self.public_key) != 32:
raise ValueError("Ed25519 public key must contain 32 bytes")
Ed25519PublicKey.from_public_bytes(self.public_key)
if _parse_utc(self.not_before) >= _parse_utc(self.expires_at):
raise ValueError("builder key validity window is invalid")
@property
def public_key_sha256(self) -> str:
return hashlib.sha256(self.public_key).hexdigest()
def validate_at(self, instant: datetime) -> None:
if self.status != "ACTIVE":
raise CapabilityAuthorizationError(
"builder attestation key is unavailable or revoked"
)
if instant.tzinfo is None:
raise CapabilityAuthorizationError(
"builder attestation verification time is invalid"
)
if not _parse_utc(self.not_before) <= instant <= _parse_utc(
self.expires_at
):
raise CapabilityAuthorizationError(
"builder attestation key is outside its validity window"
)
@classmethod
def from_dict(
cls, payload: Mapping[str, Any]
) -> "BuilderAttestationKeyRecord":
values = dict(payload)
encoded = values.pop("public_key", values.pop("public_key_b64", None))
if not isinstance(encoded, str):
raise ValueError("builder public key is missing")
try:
public_key = base64.b64decode(encoded, validate=True)
except (TypeError, ValueError) as exc:
raise ValueError("builder public key encoding is invalid") from exc
supplied_digest = values.pop(
"public_key_sha256",
values.pop("key_sha256", values.pop("digest", None)),
)
if not isinstance(supplied_digest, str):
raise ValueError("builder public key digest is missing")
digest = _validate_sha("builder public key digest", supplied_digest)
if not hmac.compare_digest(
digest, hashlib.sha256(public_key).hexdigest()
):
raise ValueError("builder public key digest is invalid")
allowed = {
"algorithm",
"purpose",
"key_id",
"generation",
"not_before",
"expires_at",
"status",
}
unknown = set(values).difference(allowed)
if unknown:
raise ValueError("builder key record contains unknown fields")
return cls(public_key=public_key, **values)
class BuilderAttestationKeyRing:
"""Validated, public-only keyring for builder attestations.
The file format deliberately cannot carry Owner approval keys: both the
keyring and every record must declare the builder-attestation purpose.
"""
def __init__(
self,
records: Iterable[BuilderAttestationKeyRecord],
*,
require_attestation_metadata: bool = False,
):
self._records: dict[str, BuilderAttestationKeyRecord] = {}
self._require_attestation_metadata = require_attestation_metadata
for record in records:
if record.key_id in self._records:
raise ValueError("duplicate builder key id")
self._records[record.key_id] = record
@classmethod
def from_public_keys(
cls, public_keys: Mapping[str, bytes]
) -> "BuilderAttestationKeyRing":
"""Compatibility adapter for explicit in-process builder key maps.
This is intentionally not used for host configuration; host keyrings
must use :meth:`from_file` so purpose, digest, validity and status are
checked before the API accepts traffic.
"""
return cls(
BuilderAttestationKeyRecord(
key_id=_validate_identifier("builder_key_id", key_id),
public_key=bytes(public_key),
algorithm="Ed25519",
purpose=BUILDER_ATTESTATION_PURPOSE,
generation=0,
not_before="1970-01-01T00:00:00Z",
expires_at="9999-12-31T23:59:59Z",
status="ACTIVE",
)
for key_id, public_key in public_keys.items()
)
@classmethod
def from_file(cls, path: Path) -> "BuilderAttestationKeyRing":
payload = json.loads(Path(path).read_text(encoding="utf-8"))
if payload.get("algorithm") != "Ed25519":
raise ValueError("unsupported builder keyring algorithm")
if payload.get("purpose") != BUILDER_ATTESTATION_PURPOSE:
raise ValueError("builder keyring purpose is invalid")
raw_records = payload.get("keys")
if not isinstance(raw_records, list) or not raw_records:
raise ValueError("builder keyring keys are invalid")
allowed = {"algorithm", "purpose", "keys", "keyring_sha256"}
if set(payload).difference(allowed):
raise ValueError("builder keyring contains unknown fields")
if "keyring_sha256" not in payload:
raise ValueError("builder keyring digest is missing")
expected = _validate_sha(
"builder keyring digest", str(payload["keyring_sha256"])
)
canonical = _canonical_json(
{
"algorithm": payload["algorithm"],
"purpose": payload["purpose"],
"keys": raw_records,
}
)
if not hmac.compare_digest(
expected, hashlib.sha256(canonical).hexdigest()
):
raise ValueError("builder keyring digest is invalid")
if not all(isinstance(record, Mapping) for record in raw_records):
raise ValueError("builder keyring record is invalid")
return cls(
(
BuilderAttestationKeyRecord.from_dict(record)
for record in raw_records
),
require_attestation_metadata=True,
)
def verify(
self,
key_id: str,
signature: bytes,
message: bytes,
*,
at: datetime,
generation: int | None,
algorithm: str | None,
purpose: str | None,
) -> BuilderAttestationKeyRecord:
record = self._records.get(key_id)
if record is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
record.validate_at(at)
metadata = (generation, algorithm, purpose)
if self._require_attestation_metadata and any(
value is None for value in metadata
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata is required"
)
if any(value is not None for value in metadata) and metadata != (
record.generation,
record.algorithm,
record.purpose,
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata does not match key"
)
try:
Ed25519PublicKey.from_public_bytes(record.public_key).verify(
signature, message
)
except InvalidSignature as exc:
raise CapabilityAuthorizationError(
"builder attestation signature is invalid"
) from exc
return record
def require_active_identity(
self,
*,
key_id: str,
generation: int,
algorithm: str,
purpose: str,
at: datetime | None = None,
) -> BuilderAttestationKeyRecord:
record = self._records.get(key_id)
if record is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
if (
record.generation != generation
or record.algorithm != algorithm
or record.purpose != purpose
):
raise CapabilityAuthorizationError(
"builder attestation key metadata is invalid"
)
record.validate_at(at or datetime.now(timezone.utc))
return record
values.pop("fingerprint", None)
return cls(**values)
class BuilderAttestationVerifier:
"""Public-key-only verifier; no builder private material is accepted."""
def __init__(
self,
public_keys: BuilderAttestationKeyRing | Mapping[str, bytes],
):
if isinstance(public_keys, BuilderAttestationKeyRing):
self.keyring = public_keys
elif isinstance(public_keys, Mapping):
self.keyring = BuilderAttestationKeyRing.from_public_keys(
public_keys
)
else:
raise TypeError("builder verifier requires a builder keyring")
def __init__(self, public_keys: Mapping[str, bytes]):
self.public_keys = {
_validate_identifier("builder_key_id", key_id): bytes(value)
for key_id, value in public_keys.items()
}
def verify(
self,
@@ -1110,54 +793,22 @@ class BuilderAttestationVerifier:
raise CapabilityAuthorizationError(
"builder attestation does not bind candidate evidence"
)
key = self.public_keys.get(attestation.builder_key_id)
if key is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
try:
signature = base64.b64decode(
attestation.signature, validate=True
)
key_record = self.keyring.verify(
attestation.builder_key_id,
signature,
attestation.canonical_bytes(),
at=_parse_utc(attestation.issued_at),
generation=attestation.builder_key_generation,
algorithm=attestation.signature_algorithm,
purpose=attestation.signature_purpose,
Ed25519PublicKey.from_public_bytes(key).verify(
signature, attestation.canonical_bytes()
)
except (InvalidSignature, ValueError, TypeError) as exc:
raise CapabilityAuthorizationError(
"builder attestation signature is invalid"
) from exc
if key_record.generation >= 1:
expected_gitea = hashlib.sha256(
_canonical_json(candidate.gitea_consistency)
).hexdigest()
expected_rollback = hashlib.sha256(
_canonical_json(
{
"ready": candidate.rollback_ready,
"rollback_sha256": release_plan.deployment.rollback_sha256,
"source_commit": candidate.source_commit,
}
)
).hexdigest()
production_evidence = (
attestation.gitea_evidence_sha256,
attestation.rollback_evidence_sha256,
attestation.policy_evidence_sha256,
attestation.secret_scan_evidence_sha256,
)
if any(not value for value in production_evidence):
raise CapabilityAuthorizationError(
"builder attestation production evidence is incomplete"
)
if (
attestation.gitea_evidence_sha256 != expected_gitea
or attestation.rollback_evidence_sha256 != expected_rollback
or attestation.policy_evidence_sha256 != release_plan.policy_sha256
):
raise CapabilityAuthorizationError(
"builder attestation production evidence is invalid"
)
return attestation
@@ -3530,12 +3181,9 @@ class SilentReleaseActionService:
builder_attestation_verifier: BuilderAttestationVerifier | None = None,
trusted_policy: TrustedReleasePolicy | None = None,
orchestrator: ReleaseOrchestrator | None = None,
builder_readiness_probe: Callable[[], Mapping[str, Any]] | None = None,
allow_legacy_candidate_input: bool = False,
expected_repository: str = "",
policy_sha256: str = "",
max_builder_attempts: int = 3,
builder_attempt_timeout_seconds: int = 180,
):
self.root = Path(root)
self.execution_enabled = bool(execution_enabled)
@@ -3546,28 +3194,11 @@ class SilentReleaseActionService:
self.builder_attestation_verifier = builder_attestation_verifier
self.trusted_policy = trusted_policy
self.orchestrator = orchestrator
self.builder_readiness_probe = builder_readiness_probe
self.allow_legacy_candidate_input = bool(
allow_legacy_candidate_input
)
self.expected_repository = expected_repository
self.policy_sha256 = policy_sha256
if (
not isinstance(max_builder_attempts, int)
or isinstance(max_builder_attempts, bool)
or not 1 <= max_builder_attempts <= 10
):
raise ValueError("max_builder_attempts must be between 1 and 10")
self.max_builder_attempts = max_builder_attempts
if (
not isinstance(builder_attempt_timeout_seconds, int)
or isinstance(builder_attempt_timeout_seconds, bool)
or not 1 <= builder_attempt_timeout_seconds <= 3600
):
raise ValueError(
"builder_attempt_timeout_seconds must be between 1 and 3600"
)
self.builder_attempt_timeout_seconds = builder_attempt_timeout_seconds
self.candidates = ReleaseCandidateStore(
self.root / "candidates"
)
@@ -3604,26 +3235,10 @@ class SilentReleaseActionService:
+ ", ".join(missing)
)
def get_builder_readiness(self) -> dict[str, Any]:
"""Report dependency readiness without conflating it with API liveness."""
if self.candidate_builder is None:
return {"status": "UNAVAILABLE"}
if self.builder_readiness_probe is None:
# Explicit in-process test/adaptor construction remains usable;
# production wiring provides a probe backed by its credentials.
return {"status": "READY"}
try:
value = dict(self.builder_readiness_probe())
except Exception:
return {"status": "UNAVAILABLE"}
if value.get("status") != "READY":
return {"status": "UNAVAILABLE"}
return _sanitize(value)
def create_release_intent(
self, payload: Mapping[str, Any]
) -> dict[str, Any]:
allowed = {"event_id", "source_commit", "version"}
allowed = {"event_id", "source_commit"}
unknown = set(payload).difference(allowed)
if unknown:
raise CapabilityAuthorizationError(
@@ -3635,17 +3250,12 @@ class SilentReleaseActionService:
source_commit = _validate_oid(
"source_commit", str(payload.get("source_commit") or "")
)
version = str(payload.get("version") or "")
if version:
_validate_identifier("version", version)
intent_id = "intent-" + hashlib.sha256(
f"{event_id}\0{source_commit}".encode("utf-8")
).hexdigest()[:24]
path = _safe_child(self.intents_root, intent_id)
was_existing = path.exists()
record = {
"created_at": utc_now(),
"attempt_count": 0,
"event_id": event_id,
"intent_id": intent_id,
"source_commit": source_commit,
@@ -3655,153 +3265,42 @@ class SilentReleaseActionService:
else "BUILDER_UNAVAILABLE"
),
}
if version:
record["version"] = version
if was_existing:
if path.exists():
existing = json.loads(path.read_text(encoding="utf-8"))
if (
existing.get("event_id") != event_id
or existing.get("source_commit") != source_commit
or str(existing.get("version") or "") != version
):
raise SagaConflict(
"release intent id is bound to different evidence"
)
if existing.get("status") == "BUILDING":
try:
started_at = _parse_utc(
str(existing.get("attempt_started_at") or "")
)
elapsed = (
datetime.now(timezone.utc) - started_at
).total_seconds()
except (TypeError, ValueError):
elapsed = self.builder_attempt_timeout_seconds + 1
if elapsed <= self.builder_attempt_timeout_seconds:
return self._intent_response(existing, idempotent=True)
existing.update(
{
"failure_code": "TRUSTED_BUILDER_CRASH_RECOVERED",
"last_recovery_code": "TRUSTED_BUILDER_CRASH_RECOVERED",
"recovery_count": int(existing.get("recovery_count") or 0) + 1,
"retryable": int(existing.get("attempt_count") or 0)
< self.max_builder_attempts,
"status": "BUILDER_RETRYABLE_FAILURE",
"updated_at": utc_now(),
}
)
_durable_json(path, existing)
if (
existing.get("status") != "BUILDER_RETRYABLE_FAILURE"
or int(existing.get("attempt_count") or 0)
>= self.max_builder_attempts
):
return self._intent_response(existing, idempotent=True)
record = dict(existing)
if self.candidate_builder is None:
record.update(
{
"retryable": False,
"status": "BUILDER_UNAVAILABLE",
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=was_existing)
record.update(
{
"attempt_count": int(record.get("attempt_count") or 0) + 1,
"attempt_started_at": utc_now(),
"retryable": False,
"status": "BUILDING",
"updated_at": utc_now(),
return {
**_sanitize(existing),
"idempotent": True,
"execution_enabled": self.execution_enabled,
"shadow_mode": not self.execution_enabled,
"side_effects_executed": False,
}
)
_durable_json(path, record)
try:
result = dict(record)
if self.candidate_builder is not None:
candidate = self.create_release_candidate(
{
"event_id": event_id,
"intent_id": intent_id,
"source_commit": source_commit,
**({"version": version} if version else {}),
}
)
except TrustedCandidateBuilderUnavailable:
record.update(
result.update(
{
"failure_code": "TRUSTED_BUILDER_UNAVAILABLE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
"candidate_id": candidate["candidate_id"],
"status": candidate["saga_state"],
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except CapabilityAuthorizationError:
record.update(
{
"failure_code": "TRUSTED_BUILDER_REJECTED",
"retryable": False,
"status": "BUILDER_TERMINAL_FAILURE",
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except (ConnectionError, OSError, TimeoutError):
record.update(
{
"failure_code": "TRUSTED_BUILDER_UNAVAILABLE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except Exception:
# The durable intent is recoverable without serializing an
# arbitrary exception (which may contain a credential or host
# path). Repeated requests consume the small fixed retry budget.
record.update(
{
"failure_code": "TRUSTED_BUILDER_FAILURE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
result = dict(record)
result.update(
{
"candidate_id": candidate["candidate_id"],
"retryable": False,
"status": candidate["saga_state"],
}
)
_durable_json(path, result)
return self._intent_response(result, idempotent=False)
def _intent_response(
self, record: Mapping[str, Any], *, idempotent: bool
) -> dict[str, Any]:
_durable_json(path, result)
return {
**_sanitize(dict(record)),
"idempotent": idempotent,
**_sanitize(result),
"idempotent": False,
"execution_enabled": self.execution_enabled,
"shadow_mode": not self.execution_enabled,
"side_effects_executed": False,
@@ -3822,7 +3321,6 @@ class SilentReleaseActionService:
self, payload: Mapping[str, Any]
) -> dict[str, Any]:
if self.candidate_builder is not None:
values = dict(payload)
forbidden_evidence = {
"artifact_sha256",
"canary",
@@ -3835,69 +3333,13 @@ class SilentReleaseActionService:
"tests",
"tree_oid",
}
if forbidden_evidence.intersection(values):
if forbidden_evidence.intersection(payload):
raise CapabilityAuthorizationError(
"candidate evidence must come from the trusted builder"
)
worker_fields = {"candidate_id", "source_commit", "version"}
intent_fields = {"event_id", "intent_id", "source_commit", "version"}
supplied = set(values)
is_worker_request = worker_fields.issubset(supplied)
if is_worker_request:
if supplied != worker_fields:
raise ValueError("release worker request contains unknown fields")
_validate_identifier(
"candidate_id", str(values.get("candidate_id") or "")
)
_validate_identifier(
"version", str(values.get("version") or "")
)
else:
if not supplied.issubset(intent_fields):
raise ValueError("trusted builder intent contains unknown fields")
# Preserve the established internal intent contract
# (source_commit + version) while allowing newer callers to
# add an event/intent identifier. Neither form can supply
# acceptance evidence; the host builder owns all collectors.
if values.get("event_id"):
_validate_identifier(
"event_id", str(values["event_id"])
)
if values.get("intent_id"):
_validate_identifier(
"intent_id", str(values["intent_id"])
)
if values.get("version"):
_validate_identifier(
"version", str(values["version"])
)
_validate_oid(
"source_commit", str(values.get("source_commit") or "")
)
readiness = self.get_builder_readiness()
if readiness.get("status") != "READY":
raise TrustedCandidateBuilderUnavailable(
"trusted builder dependency is unavailable"
)
bundle = self.candidate_builder.build(dict(payload)).validate(
self.builder_attestation_verifier
)
requested_source = str(payload.get("source_commit") or "")
if (
requested_source
and bundle.candidate.source_commit != requested_source
):
raise CapabilityAuthorizationError(
"trusted builder source commit does not match intent"
)
if (
is_worker_request
and bundle.candidate.candidate_id
!= str(values["candidate_id"])
):
raise CapabilityAuthorizationError(
"trusted builder candidate id does not match request"
)
if self.trusted_policy is None:
raise CapabilityAuthorizationError(
"trusted release policy is not configured"
@@ -1,57 +0,0 @@
{
"schema": "aios.trusted-candidate-builder-policy.v1",
"repository": "aios/aios",
"request_schema": "aios.trusted-candidate-builder.request.v1",
"response_schema": "aios.trusted-candidate-builder.response.v1",
"operation": "BuildTrustedCandidateV1",
"readiness_operation": "ProbeTrustedCandidateBuilderV1",
"maximum_request_age_seconds": 60,
"request_timeout_seconds": 30,
"allowed_peer_uid": 1000,
"allowed_peer_cgroups": [
"/system.slice/aios-gitops-model-api.service"
],
"allowed_peer_executable_prefix": "/usr/bin/python3",
"allowed_peer_runtime_prefix": "/opt/aios/gitops-model-api/",
"allowed_peer_runtime_suffix": "/runtime/app/aios_model_api_staging.py",
"required_checks": [
"authoritative_git",
"artifact",
"canary",
"gitea",
"manifest",
"package_closure",
"rollback",
"secret_scan",
"tests"
],
"evidence_layout": {
"artifact": "{version}/artifact.tar.gz",
"manifest": "{version}/manifest.json",
"package_closure": "{version}/checksums.sha256",
"tests": "{version}/tests.json",
"canary": "{version}/canary.json",
"gitea": "{version}/gitea.json",
"rollback": "{version}/rollback.json",
"policy": "{version}/silent-release-policy.json",
"secret_scan": "{version}/secret-scan.json"
},
"recipes": {
"V2R2C2": {
"mode": "fixed",
"task_id": "r42-release-candidate-v2r2c2-live-20260819",
"candidate_id": "r42-release-candidate-v2r2c2-20260819",
"base_oid": "71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
"source_commit": "71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
"version": "V2R2C2",
"purpose": "release:candidate:trusted-build"
},
"V2R2C3": {
"mode": "governed-derived-only",
"derives_from": "V2R2C2",
"version": "V2R2C3",
"purpose": "release:candidate:trusted-build"
}
},
"governed_recipes": {}
}
@@ -0,0 +1,37 @@
[Unit]
Description=AIOS owner-controlled Task initial execution projection API
After=aios-agent-task-api.service aios-chatgpt-bridge-v2-staging.service
Wants=aios-agent-task-api.service aios-chatgpt-bridge-v2-staging.service
[Service]
Type=simple
User=aios-agent-task
Group=aios-agent-task
LoadCredential=agent_task_control_token:/etc/aios-gitops/agent-task-control.token
Environment=AIOS_AGENT_TASK_DB=/var/lib/aios-agent-task/state.sqlite3
Environment=AIOS_AGENT_TASK_CONTROL_TOKEN_FILE=%d/agent_task_control_token
Environment=AIOS_GOVERNED_WRITE_API_URL=http://127.0.0.1:18892
Environment=AIOS_AGENT_TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZATION_ID=owner-initial-execution-projection-recovery-20260820
Environment=AIOS_TASK_INITIAL_EXECUTION_PROJECTION_HOST=127.0.0.1
Environment=AIOS_TASK_INITIAL_EXECUTION_PROJECTION_PORT=18943
ExecStart=/usr/bin/python3 -B /opt/aios/current/active-runtime/app/aios_agent_task_initial_execution_projection.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictNamespaces=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET
ReadWritePaths=/var/lib/aios-agent-task
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,37 @@
[Unit]
Description=AIOS owner-controlled supervisor coordinator projection API
After=aios-agent-task-api.service aios-chatgpt-bridge-v2-staging.service
Wants=aios-agent-task-api.service aios-chatgpt-bridge-v2-staging.service
[Service]
Type=simple
User=aios-agent-task
Group=aios-agent-task
LoadCredential=agent_task_control_token:/etc/aios-gitops/agent-task-control.token
Environment=AIOS_AGENT_TASK_DB=/var/lib/aios-agent-task/state.sqlite3
Environment=AIOS_AGENT_TASK_CONTROL_TOKEN_FILE=%d/agent_task_control_token
Environment=AIOS_GOVERNED_WRITE_API_URL=http://127.0.0.1:18892
Environment=AIOS_AGENT_TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZATION_ID=owner-supervisor-coordinator-projection-recovery-20260820
Environment=AIOS_SUPERVISOR_COORDINATOR_PROJECTION_HOST=127.0.0.1
Environment=AIOS_SUPERVISOR_COORDINATOR_PROJECTION_PORT=18944
ExecStart=/usr/bin/python3 -B /opt/aios/current/active-runtime/app/aios_agent_task_supervisor_coordinator_projection.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictNamespaces=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET
ReadWritePaths=/var/lib/aios-agent-task
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,45 @@
[Unit]
Description=AIOS credential-isolated release-preparation supervisor coordinator
After=network-online.target aios-agent-task-api.service aios-autonomous-agent-register.service
Wants=network-online.target aios-agent-task-api.service
Requires=aios-autonomous-agent-register.service
[Service]
Type=simple
User=aios-autonomous-agent-worker
Group=aios-autonomous-agent-worker
SupplementaryGroups=aios-agent-runtime
LoadCredentialEncrypted=agent_token:/etc/credstore.encrypted/aios-autonomous-agent-worker/agent_token
Environment=PYTHONUNBUFFERED=1
Environment=AIOS_AGENT_TASK_API_URL=http://127.0.0.1:18940
Environment=AIOS_AGENT_ID=online-engineering-worker-cn-network-mkt-autonomous-20260815
Environment=AIOS_RELEASE_PREPARATION_SUPERVISOR_TASK_ID=r42-release-preparation-autonomous-supervisor-20260820
Environment=AIOS_SUPERVISOR_COORDINATOR_STATE_ROOT=/var/lib/aios-release-preparation-supervisor-coordinator
Environment=AIOS_SUPERVISOR_COORDINATOR_POLL_SECONDS=5
Environment=AIOS_SUPERVISOR_COORDINATOR_HEARTBEAT_SECONDS=10
Environment=AIOS_SUPERVISOR_COORDINATOR_LEASE_SECONDS=300
ExecStart=/usr/bin/python3 -B /opt/aios/current/active-runtime/app/aios_release_preparation_supervisor_coordinator.py
Restart=always
RestartSec=5
TimeoutStopSec=20
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictNamespaces=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_INET AF_UNIX
ReadWritePaths=/var/lib/aios-release-preparation-supervisor-coordinator
InaccessiblePaths=-/etc/credstore.encrypted
CapabilityBoundingSet=
AmbientCapabilities=
[Install]
WantedBy=multi-user.target
@@ -1,44 +0,0 @@
[Unit]
Description=AIOS trusted candidate builder
Requires=aios-trusted-candidate-builder.socket
After=aios-trusted-candidate-builder.socket
[Service]
Type=simple
User=root
Group=root
UMask=0077
LoadCredentialEncrypted=signing_private_key:/etc/credstore.encrypted/aios-trusted-candidate-builder/signing_private_key
LoadCredentialEncrypted=builder_hmac:/etc/credstore.encrypted/aios-trusted-candidate-builder/builder_hmac
Environment=AIOS_TRUSTED_BUILDER_POLICY=/opt/aios/trusted-candidate-builder/current/config/gitops/trusted-candidate-builder-policy.json
Environment=AIOS_TRUSTED_BUILDER_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_TRUSTED_BUILDER_EVIDENCE_ROOT=/var/lib/aios-trusted-candidate-builder/evidence
Environment=AIOS_TRUSTED_BUILDER_NONCE_LEDGER=/var/lib/aios-trusted-candidate-builder/nonce-ledger.json
Environment=AIOS_TRUSTED_BUILDER_KEY_ID=builder-attestation-ed25519-g1
Environment=AIOS_TRUSTED_BUILDER_KEY_GENERATION=1
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -B /opt/aios/trusted-candidate-builder/current/app/aios_trusted_candidate_builder.py --socket-fd 3 --policy ${AIOS_TRUSTED_BUILDER_POLICY}
Restart=on-failure
RestartSec=2
StateDirectory=aios-trusted-candidate-builder
StateDirectoryMode=0700
NoNewPrivileges=true
PrivateTmp=true
PrivateNetwork=true
ProtectSystem=strict
ProtectHome=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
RestrictAddressFamilies=AF_UNIX
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
CapabilityBoundingSet=
ReadOnlyPaths=/opt/aios/trusted-candidate-builder /data/aios-gitops/repo.git /var/lib/aios-trusted-candidate-builder/evidence
ReadWritePaths=/var/lib/aios-trusted-candidate-builder
[Install]
WantedBy=multi-user.target
@@ -1,13 +0,0 @@
[Unit]
Description=AIOS trusted candidate builder AF_UNIX socket
[Socket]
ListenStream=/run/aios-trusted-candidate-builder.sock
SocketUser=root
SocketGroup=gitops
SocketMode=0660
RemoveOnStop=yes
Accept=no
[Install]
WantedBy=sockets.target
+20 -83
View File
@@ -1,12 +1,10 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import gzip
import hashlib
import json
import os
from pathlib import Path
import stat
import tarfile
import tempfile
@@ -19,18 +17,8 @@ REQUIRED_APP = (
'gitea_tag_publication.py',
'silent_release_controller.py',
'silent_release_hardening.py',
'aios_trusted_candidate_builder.py',
'aios_gitops_job_controller.py',
)
REQUIRED_RUNTIME = (
'orchestrator/__init__.py',
'orchestrator/action_monitor.py',
)
REQUIRED_BUILDER_ASSETS = (
Path('config/gitops/trusted-candidate-builder-policy.json'),
Path('deploy/systemd/aios-trusted-candidate-builder.socket'),
Path('deploy/systemd/aios-trusted-candidate-builder.service'),
)
OPTIONAL_APP = (
'aios_chatgpt_actions_api.py',
'aios_continuation_executor.py',
@@ -56,38 +44,6 @@ def private_marker(path: Path) -> bool:
)
return any(re.search(pattern, text) for pattern in patterns)
def checked_regular_file(path: Path, label: str) -> Path:
forbidden_parts = {'.git', 'credential', 'credentials', 'secret', 'secrets'}
if forbidden_parts.intersection(part.lower() for part in Path(label).parts):
raise SystemExit(f'credential path rejected: {label}')
if path.is_symlink():
raise SystemExit(f'symlink rejected: {label}')
try:
mode = path.stat(follow_symlinks=False).st_mode
except FileNotFoundError:
raise SystemExit(f'required dependency missing: {label}') from None
if not stat.S_ISREG(mode):
raise SystemExit(f'non-regular file rejected: {label}')
return path
def normalized_tar_info(path: Path, arcname: str) -> tarfile.TarInfo:
info = tarfile.TarInfo(arcname)
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = 'root'
info.gname = 'root'
if path.is_dir():
info.type = tarfile.DIRTYPE
info.mode = 0o755
info.size = 0
else:
info.type = tarfile.REGTYPE
source_mode = path.stat(follow_symlinks=False).st_mode
info.mode = 0o755 if source_mode & stat.S_IXUSR else 0o644
info.size = path.stat(follow_symlinks=False).st_size
return info
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument('--source-root', required=True, type=Path)
@@ -107,48 +63,27 @@ def main() -> int:
paths = []
for name in REQUIRED_APP:
path = app / name
checked_regular_file(path, f'active-runtime/app/{name}')
if not path.is_file():
raise SystemExit(f'required app dependency missing: {name}')
paths.append((Path('app') / name, path))
for name in OPTIONAL_APP:
path = app / name
if path.is_symlink():
raise SystemExit(f'symlink rejected: active-runtime/app/{name}')
if path.is_file():
checked_regular_file(path, f'active-runtime/app/{name}')
paths.append((Path('app') / name, path))
for rel in REQUIRED_RUNTIME:
for rel in ('orchestrator/__init__.py', 'orchestrator/action_monitor.py'):
path = runtime / rel
checked_regular_file(path, f'runtime/agent-os/{rel}')
if not path.is_file():
raise SystemExit(f'required runtime dependency missing: {rel}')
paths.append((Path('runtime-agent-os') / rel, path))
for rel in REQUIRED_BUILDER_ASSETS:
path = source / rel
checked_regular_file(path, str(rel).replace('\\', '/'))
paths.append((rel, path))
openapi = docs / 'openapi-chatgpt-actions.yaml'
if openapi.is_symlink():
raise SystemExit('symlink rejected: docs/gitops/openapi-chatgpt-actions.yaml')
if openapi.is_file():
checked_regular_file(openapi, 'docs/gitops/openapi-chatgpt-actions.yaml')
paths.append((Path('docs') / 'gitops' / 'openapi-chatgpt-actions.yaml', openapi))
for folder in ('config/model_registry', 'config/model_priority', 'patches', 'versions'):
root = source / folder
if root.is_dir():
for path in sorted(root.rglob('*')):
if path.is_symlink():
raise SystemExit(f'symlink rejected: {path.relative_to(source)}')
if not path.is_dir() and not path.is_file():
raise SystemExit(f'special file rejected: {path.relative_to(source)}')
if path.is_file() and path.suffix.lower() in {'.json', '.yaml', '.yml', '.md'}:
checked_regular_file(path, str(path.relative_to(source)).replace('\\', '/'))
paths.append((Path(folder) / path.relative_to(root), path))
seen: set[str] = set()
for rel, _ in paths:
normalized = str(rel).replace('\\', '/')
if normalized.startswith('/') or '..' in Path(normalized).parts:
raise SystemExit(f'path traversal rejected: {normalized}')
if normalized in seen:
raise SystemExit(f'duplicate archive member rejected: {normalized}')
seen.add(normalized)
for rel, path in paths:
if private_marker(path):
raise SystemExit(f'private marker rejected: {rel}')
@@ -175,25 +110,27 @@ def main() -> int:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(path.read_bytes())
(root / 'manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + '\n', encoding='utf-8')
with archive.open('wb') as raw:
with gzip.GzipFile(filename='', mode='wb', fileobj=raw, mtime=0) as compressed:
with tarfile.open(fileobj=compressed, mode='w', format=tarfile.PAX_FORMAT) as tf:
for path in sorted(root.rglob('*')):
rel = path.relative_to(root.parent)
arcname = str(rel).replace('\\', '/')
info = normalized_tar_info(path, arcname)
if path.is_file():
with path.open('rb') as fh:
tf.addfile(info, fh)
else:
tf.addfile(info)
with tarfile.open(archive, 'w:gz', format=tarfile.PAX_FORMAT) as tf:
for path in sorted(root.rglob('*')):
rel = path.relative_to(root.parent)
info = tf.gettarinfo(str(path), arcname=str(rel).replace('\\', '/'))
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = 'root'
info.gname = 'root'
if path.is_file():
with path.open('rb') as fh:
tf.addfile(info, fh)
else:
tf.addfile(info)
digest = sha256(archive)
receipt = {
'schema': 'aios.v2r2c1.model-api-staging-build-receipt.v1',
'target_version': 'V2R2C1',
'source_oid': oid,
'repository_identity': 'aios/aios',
'artifact_path': archive.name,
'artifact_path': str(archive),
'artifact_sha256': digest,
'deployment_state': 'STAGING',
'manifest_schema': SCHEMA,
@@ -10,9 +10,13 @@ ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime/app"))
from aios_capability_contract import ( # noqa: E402
capability_contract,
has_capabilities,
normalize_required_capabilities,
normalize_worker_capabilities,
normalized_capability_matrix,
registered_capability,
validate_capability_contract_registry,
)
@@ -31,3 +35,32 @@ def test_runtime_capabilities_are_isolated_between_workers() -> None:
def test_unknown_runtime_capability_remains_fail_closed() -> None:
assert has_capabilities(["agent.runtime.hermes"], ["agent.runtime.unknown"]) is False
def test_autonomous_coordinator_capability_is_registered_on_both_sides() -> None:
capability = "engineering.autonomous"
assert registered_capability(capability) is True
assert registered_capability(capability, worker=True) is True
assert normalize_required_capabilities([capability]) == frozenset({capability})
assert normalize_worker_capabilities([capability]) == frozenset({capability})
contract = capability_contract(capability)
assert contract is not None
assert contract.dependencies == ("agent_task_control_plane",)
assert validate_capability_contract_registry()["valid"] is True
def test_supervisor_coordinator_requires_runtime_and_autonomous_capabilities() -> None:
required = ["agent.runtime.openclaw", "engineering.autonomous"]
assert normalized_capability_matrix(required, required) == {
"worker_effective": required,
"task_required": required,
"eligible": True,
}
assert has_capabilities(["agent.runtime.openclaw"], required) is False
assert has_capabilities(["engineering.autonomous"], required) is False
def test_autonomous_capability_near_matches_remain_fail_closed() -> None:
worker = ["agent.runtime.openclaw", "engineering.autonomous"]
assert has_capabilities(worker, ["engineering.autonomous.extra"]) is False
assert has_capabilities(worker, ["engineering.*"]) is False
@@ -0,0 +1,452 @@
from __future__ import annotations
from pathlib import Path
import hashlib
import json
import sqlite3
import sys
import pytest
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/"active-runtime/app"))
from aios_agent_task_initial_execution_projection import (
InitialExecutionProjectionError,
InitialExecutionProjectionRequest,
InitialExecutionProjectionStore,
SourceCaptureProjectionRecoveryRequest,
)
from aios_online_agent_execution import (
GovernedExecutionBinding,
project_execution_envelope,
)
AUTHORIZATION="owner-initial-execution-projection-recovery-20260820"
TASK_ID="task-initial-projection-a"
JOB_ID="job-initial-projection-a"
BASE="a"*40
BRANCH="agent/task-initial-projection-a"
IDEMPOTENCY="task-initial-projection-binding-v1"
CAPABILITY="agent.runtime.openclaw"
@pytest.fixture(autouse=True)
def _auth(monkeypatch):
monkeypatch.setenv(
"AIOS_AGENT_TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZATION_ID",
AUTHORIZATION,
)
def canon(value):
return hashlib.sha256(
json.dumps(value,sort_keys=True,separators=(",",":")).encode()
).hexdigest()
def binding():
return GovernedExecutionBinding.from_mapping({
"task_id":TASK_ID,
"job_id":JOB_ID,
"repository":"aios/aios",
"base_oid":BASE,
"expected_branch_oid":BASE,
"target_branch":BRANCH,
"branch_mode":"CREATE_NEW",
"generation":1,
"idempotency_key":IDEMPOTENCY,
})
def runtime_input():
return {
"goal":"Supervise release preparation",
"instructions":"Observe and resume only the same lineage.",
"success_criteria":[{"id":"heartbeat","description":"Heartbeat observed"}],
"constraints":{"production_write":False},
"scope":{"scope_type":"legacy","tenant_id":None,"project_id":None},
"trace_id":TASK_ID,
}
def envelope(*, job_id=JOB_ID, action="agent_runtime", capability=CAPABILITY):
bound=binding()
if job_id != JOB_ID:
bound=GovernedExecutionBinding.from_mapping({**bound.public(),"job_id":job_id})
return project_execution_envelope(
bound,
{
"action":action,
"adapter":"openclaw" if action=="agent_runtime" else "source_capture",
"capability_scope":[capability],
"runtime_input":runtime_input() if action=="agent_runtime" else None,
},
)
def make_db(tmp_path, *, claimed_by=None, lease=False, metadata=None, caps=None, event="TASK_CREATED"):
path=tmp_path/"state.sqlite3"
con=sqlite3.connect(path)
con.executescript("""
CREATE TABLE tasks(
task_id TEXT PRIMARY KEY,
goal TEXT NOT NULL,
instructions TEXT NOT NULL,
success_criteria_json TEXT NOT NULL,
required_capabilities_json TEXT NOT NULL,
constraints_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
deadline TEXT,
priority INTEGER NOT NULL,
max_attempts INTEGER NOT NULL,
attempt INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL,
claimed_by TEXT,
lease_token_hash TEXT,
lease_expires_at TEXT,
result_json TEXT,
result_digest TEXT,
result_idempotency_key TEXT,
verification_json TEXT,
revision INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE events(
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT,
agent_id TEXT,
event_type TEXT NOT NULL,
payload_json TEXT NOT NULL,
created_at TEXT NOT NULL
);
""")
metadata={} if metadata is None else metadata
caps=[] if caps is None else caps
con.execute(
"INSERT INTO tasks VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
TASK_ID,"Supervise release preparation",
"Observe and resume only the same lineage.",
json.dumps([{"id":"heartbeat","description":"Heartbeat observed"}]),
json.dumps(caps),json.dumps({"production_write":False}),json.dumps(metadata),
None,100,20,0,"QUEUED",claimed_by,
"lease-hash" if lease else None,
"2099-01-01T00:00:00Z" if lease else None,
None,None,None,None,1,"2026-08-20T00:00:00Z","2026-08-20T00:00:00Z",
),
)
con.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) VALUES (?,?,?,?,?)",
(TASK_ID,None,event,"{}","2026-08-20T00:00:00Z"),
)
con.commit();con.close()
return path
def request(*, digest=None, key="initial-projection-a-v1"):
return InitialExecutionProjectionRequest.from_mapping({
"expected_status":"QUEUED",
"expected_revision":1,
"expected_attempt":0,
"expected_metadata_sha256":canon({}),
"expected_required_capabilities_sha256":canon([]),
"expected_job_id":JOB_ID,
"expected_execution_digest":digest or envelope()["execution_digest"],
"required_capability":CAPABILITY,
"adapter":"openclaw",
"authorization_id":AUTHORIZATION,
"idempotency_key":key,
"reason":"initial governed supervisor runtime projection",
})
def resolver(_task_id,_job_id,_capability,_adapter,received_runtime_input):
assert received_runtime_input==runtime_input()
return envelope()
def source_capture_result():
old=envelope(action="source_capture",capability="git.read")
return {
"task_id":TASK_ID,
"job_id":JOB_ID,
"repository":"aios/aios",
"base_oid":BASE,
"expected_branch_oid":BASE,
"target_branch":BRANCH,
"generation":1,
"status":"SUCCEEDED",
"classification":"NEEDS_REWORK_SOURCE_CAPTURE",
"execution_mode":"source_capture",
"execution_digest":old["execution_digest"],
"changed_files":[],
"writer_lease":"NONE",
"verification_state":"NEEDS_REWORK",
"direct_main_write":False,
"production_write":False,
"force_push":False,
"protected_ref_bypass":False,
"secret_material_exposed":False,
"source_snapshot":{"schema":"test.snapshot.v1","payload_sha256":"d"*64},
}
def make_source_capture_rework_db(tmp_path, *, mutate=None, events=None):
path=make_db(tmp_path)
result=source_capture_result()
verification={
"decision":"NEEDS_REWORK",
"reason":"SOURCE_CAPTURE_COMPLETE_RUNTIME_PROJECTION_REQUIRED",
}
if mutate:
mutate(result,verification)
event_types=events or [
"TASK_CLAIMED","TASK_HEARTBEAT","RESULT_SUBMITTED","TASK_VERIFIED"
]
con=sqlite3.connect(path)
con.execute(
"UPDATE tasks SET attempt=1,status='QUEUED',claimed_by=NULL,revision=5,"
"result_json=?,result_digest=?,result_idempotency_key=?,verification_json=?",
(
json.dumps(result,sort_keys=True,separators=(",",":")),
canon(result),"source-capture-result-v1",
json.dumps(verification,sort_keys=True,separators=(",",":")),
),
)
for event_type in event_types:
payload={}
if event_type=="TASK_VERIFIED":
payload={"decision":"NEEDS_REWORK","next_state":"QUEUED"}
con.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) "
"VALUES (?,?,?,?,?)",
(TASK_ID,"generic-worker",event_type,json.dumps(payload),"2026-08-20T00:01:00Z"),
)
con.commit();con.close()
return path,result,verification
def recovery_request(result,verification,*,digest=None,key="source-capture-recovery-a-v1"):
return SourceCaptureProjectionRecoveryRequest.from_mapping({
"expected_status":"QUEUED",
"expected_revision":5,
"expected_attempt":1,
"expected_metadata_sha256":canon({}),
"expected_required_capabilities_sha256":canon([]),
"previous_verification_sha256":canon(verification),
"expected_source_capture_result_sha256":canon(result),
"expected_source_capture_execution_digest":result["execution_digest"],
"expected_job_id":JOB_ID,
"expected_execution_digest":digest or envelope()["execution_digest"],
"required_capability":CAPABILITY,
"adapter":"openclaw",
"authorization_id":AUTHORIZATION,
"idempotency_key":key,
"reason":"first durable runtime projection after governed source capture",
})
def test_initial_projection_cas_persists_envelope_capability_and_event(tmp_path):
path=make_db(tmp_path)
result=InitialExecutionProjectionStore(path,resolver).project(TASK_ID,request())
assert result["revision"]==2
assert result["attempt"]==0
assert result["idempotent"] is False
con=sqlite3.connect(path)
row=con.execute(
"SELECT status,attempt,revision,claimed_by,metadata_json,required_capabilities_json FROM tasks"
).fetchone()
assert row[:4]==("QUEUED",0,2,None)
metadata=json.loads(row[4])
assert metadata["execution"]["action"]=="agent_runtime"
assert metadata["execution"]["adapter"]=="openclaw"
assert metadata["execution"]["governance"]["writer_lease"] is False
assert json.loads(row[5])==[CAPABILITY]
events=con.execute("SELECT event_type,payload_json FROM events ORDER BY event_id").fetchall()
assert [event[0] for event in events]==[
"TASK_CREATED","TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZED"
]
payload=json.loads(events[-1][1])
assert payload["job_id"]==JOB_ID
assert payload["main_write"] is False
assert payload["production_write"] is False
def test_initial_projection_idempotent_exact_replay(tmp_path):
path=make_db(tmp_path)
store=InitialExecutionProjectionStore(path,resolver)
first=store.project(TASK_ID,request())
second=store.project(TASK_ID,request())
assert first["idempotent"] is False
assert second["idempotent"] is True
assert second["execution_digest"]==first["execution_digest"]
con=sqlite3.connect(path)
assert con.execute("SELECT COUNT(*) FROM task_initial_execution_projections").fetchone()[0]==1
assert con.execute("SELECT COUNT(*) FROM events").fetchone()[0]==2
def test_source_capture_recovery_projects_first_runtime_envelope(tmp_path):
path,result,verification=make_source_capture_rework_db(tmp_path)
response=InitialExecutionProjectionStore(path,resolver).project_from_source_capture(
TASK_ID,recovery_request(result,verification)
)
assert response["revision"]==6
assert response["attempt"]==1
assert response["recovery_from_source_capture"] is True
assert response["idempotent"] is False
con=sqlite3.connect(path)
row=con.execute(
"SELECT status,attempt,revision,claimed_by,metadata_json,"
"required_capabilities_json,result_json,verification_json FROM tasks"
).fetchone()
assert row[:4]==("QUEUED",1,6,None)
metadata=json.loads(row[4])
assert metadata["execution"]["action"]=="agent_runtime"
assert metadata["execution"]["adapter"]=="openclaw"
assert metadata["execution"]["governance"]["writer_lease"] is False
assert json.loads(row[5])==[CAPABILITY]
assert json.loads(row[6])==result
assert json.loads(row[7])==verification
events=con.execute("SELECT event_type,payload_json FROM events ORDER BY event_id").fetchall()
assert [event[0] for event in events]==[
"TASK_CREATED","TASK_CLAIMED","TASK_HEARTBEAT","RESULT_SUBMITTED",
"TASK_VERIFIED","TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZED",
]
payload=json.loads(events[-1][1])
assert payload["recovery_from_source_capture"] is True
assert payload["source_capture_execution_digest"]==result["execution_digest"]
assert payload["main_write"] is False
assert payload["production_write"] is False
def test_source_capture_recovery_idempotent_exact_replay(tmp_path):
path,result,verification=make_source_capture_rework_db(tmp_path)
store=InitialExecutionProjectionStore(path,resolver)
first=store.project_from_source_capture(TASK_ID,recovery_request(result,verification))
second=store.project_from_source_capture(TASK_ID,recovery_request(result,verification))
assert first["idempotent"] is False
assert second["idempotent"] is True
assert second["execution_digest"]==first["execution_digest"]
con=sqlite3.connect(path)
assert con.execute(
"SELECT COUNT(*) FROM task_source_capture_projection_recoveries"
).fetchone()[0]==1
assert con.execute("SELECT COUNT(*) FROM events").fetchone()[0]==6
@pytest.mark.parametrize(
"mutate,error",
[
(lambda result,verification: result.__setitem__("changed_files",["x"]),
"SOURCE_CAPTURE_RESULT_CONTRACT_INVALID"),
(lambda result,verification: result.__setitem__("job_id","job-other"),
"SOURCE_CAPTURE_RESULT_CONTRACT_INVALID"),
(lambda result,verification: verification.__setitem__("decision","SUCCEEDED"),
"SOURCE_CAPTURE_NEEDS_REWORK_VERIFICATION_REQUIRED"),
],
)
def test_source_capture_recovery_rejects_unsafe_result_or_verification(
tmp_path,mutate,error
):
path,result,verification=make_source_capture_rework_db(tmp_path,mutate=mutate)
with pytest.raises(InitialExecutionProjectionError,match=error):
InitialExecutionProjectionStore(path,resolver).project_from_source_capture(
TASK_ID,recovery_request(result,verification)
)
@pytest.mark.parametrize(
"events",
[
["TASK_CLAIMED","RESULT_SUBMITTED","TASK_VERIFIED"],
["TASK_CLAIMED","TASK_HEARTBEAT","RESULT_SUBMITTED","TASK_VERIFIED","TASK_CLAIMED"],
["TASK_CLAIMED","TASK_HEARTBEAT","TASK_VERIFIED","RESULT_SUBMITTED"],
],
)
def test_source_capture_recovery_rejects_invalid_event_chain(tmp_path,events):
path,result,verification=make_source_capture_rework_db(tmp_path,events=events)
with pytest.raises(
InitialExecutionProjectionError,match="SOURCE_CAPTURE_RECOVERY_EVENT_CHAIN_INVALID"
):
InitialExecutionProjectionStore(path,resolver).project_from_source_capture(
TASK_ID,recovery_request(result,verification)
)
def test_source_capture_recovery_rejects_execution_digest_drift(tmp_path):
path,result,verification=make_source_capture_rework_db(tmp_path)
with pytest.raises(InitialExecutionProjectionError,match="EXECUTION_DIGEST_DRIFT"):
InitialExecutionProjectionStore(path,resolver).project_from_source_capture(
TASK_ID,recovery_request(result,verification,digest="f"*64)
)
def test_source_capture_recovery_request_is_strict(tmp_path):
_path,result,verification=make_source_capture_rework_db(tmp_path)
data=recovery_request(result,verification).public()
data["metadata_patch"]={"execution":"forbidden"}
with pytest.raises(InitialExecutionProjectionError,match="FIELDS_INVALID"):
SourceCaptureProjectionRecoveryRequest.from_mapping(data)
@pytest.mark.parametrize(
"kwargs,error",
[
({"claimed_by":"worker-a"},"TASK_ALREADY_CLAIMED"),
({"lease":True},"ACTIVE_LEASE_DENY"),
({"metadata":{"execution":{}}},"TASK_EXECUTION_ALREADY_PROJECTED"),
({"caps":["git.read"]},"TASK_INITIAL_CAPABILITIES_MUST_BE_EMPTY"),
({"event":"TASK_CLAIMED"},"TASK_INITIAL_EVENT_CHAIN_REQUIRED"),
],
)
def test_initial_projection_rejects_noninitial_state(tmp_path,kwargs,error):
path=make_db(tmp_path,**kwargs)
with pytest.raises(InitialExecutionProjectionError,match=error):
InitialExecutionProjectionStore(path,resolver).project(TASK_ID,request())
def test_initial_projection_rejects_binding_drift(tmp_path):
path=make_db(tmp_path)
def wrong(*_args):
return envelope(job_id="job-other")
with pytest.raises(InitialExecutionProjectionError,match="GOVERNED_BINDING_CONFLICT"):
InitialExecutionProjectionStore(path,wrong).project(TASK_ID,request())
def test_initial_projection_rejects_nonruntime_envelope(tmp_path):
path=make_db(tmp_path)
def wrong(*_args):
return envelope(action="source_capture",capability="git.read")
with pytest.raises(InitialExecutionProjectionError):
InitialExecutionProjectionStore(path,wrong).project(TASK_ID,request())
def test_initial_projection_rejects_execution_digest_drift(tmp_path):
path=make_db(tmp_path)
with pytest.raises(InitialExecutionProjectionError,match="EXECUTION_DIGEST_DRIFT"):
InitialExecutionProjectionStore(path,resolver).project(
TASK_ID,request(digest="f"*64)
)
def test_request_is_narrow_and_initial_state_bound():
data=request().public()
data["arbitrary_metadata"]={"x":1}
with pytest.raises(InitialExecutionProjectionError,match="FIELDS_INVALID"):
InitialExecutionProjectionRequest.from_mapping(data)
data=request().public();data["expected_attempt"]=1
with pytest.raises(InitialExecutionProjectionError,match="INITIAL_STATE_REQUIRED"):
InitialExecutionProjectionRequest.from_mapping(data)
def test_initial_projection_unit_is_unprivileged_and_owner_credential_bound():
unit=(ROOT/"deploy/systemd/aios-agent-task-initial-execution-projection.service").read_text()
assert "User=aios-agent-task" in unit
assert "Group=aios-agent-task" in unit
assert "LoadCredential=agent_task_control_token:" in unit
assert "Environment=AIOS_GOVERNED_WRITE_API_URL=http://127.0.0.1:18892" in unit
assert "RestrictAddressFamilies=AF_UNIX AF_INET" in unit
assert "ReadWritePaths=/var/lib/aios-agent-task" in unit
assert "User=root" not in unit
@@ -0,0 +1,188 @@
from __future__ import annotations
from pathlib import Path
import hashlib
import json
import sqlite3
import sys
import pytest
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/"active-runtime/app"))
from aios_agent_task_initial_execution_projection import InitialExecutionProjectionError
from aios_agent_task_supervisor_coordinator_projection import (
COORDINATOR_CAPABILITIES,
RUNTIME_CAPABILITY,
SupervisorCoordinatorProjectionRequest,
SupervisorCoordinatorProjectionStore,
)
from aios_online_agent_execution import (
GovernedExecutionBinding,
project_execution_envelope,
)
TASK="task-supervisor-coordinator-a"
JOB="job-supervisor-coordinator-a"
BASE="a"*40
BRANCH="agent/task-supervisor-coordinator-a"
BINDING_KEY="task-supervisor-coordinator-binding-v1"
AUTH="owner-supervisor-coordinator-projection-recovery-20260820"
@pytest.fixture(autouse=True)
def authorization(monkeypatch):
monkeypatch.setenv(
"AIOS_AGENT_TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZATION_ID",AUTH
)
def sha(value):
return hashlib.sha256(
json.dumps(value,sort_keys=True,separators=(",",":")).encode()
).hexdigest()
def binding():
return GovernedExecutionBinding.from_mapping({
"task_id":TASK,"job_id":JOB,"repository":"aios/aios",
"base_oid":BASE,"expected_branch_oid":BASE,"target_branch":BRANCH,
"branch_mode":"CREATE_NEW","generation":1,"idempotency_key":BINDING_KEY,
})
def runtime_input():
return {
"goal":"Durably supervise release preparation",
"instructions":"Wait for exact connector ACK and preserve the same lineage.",
"success_criteria":[{"id":"heartbeat","description":"Coordinator heartbeat"}],
"constraints":{},
"scope":{"scope_type":"legacy","tenant_id":None,"project_id":None},
"trace_id":TASK,
}
def envelope(capabilities):
return project_execution_envelope(binding(),{
"action":"agent_runtime","adapter":"openclaw",
"capability_scope":capabilities,"runtime_input":runtime_input(),
})
def planner_result(old_digest,*,access=False):
return {
"task_id":TASK,"job_id":JOB,"repository":"aios/aios",
"base_oid":BASE,"expected_branch_oid":BASE,"target_branch":BRANCH,
"generation":1,"status":"SUCCEEDED","execution_mode":"agent_runtime",
"agent_runtime":"openclaw","execution_digest":old_digest,
"control_plane_access_granted_to_runtime":access,
"credential_material_exposed":False,"secret_material_exposed":False,
"changed_files":[],"writer_lease":"NONE","direct_main_write":False,
"production_write":False,"force_push":False,"protected_ref_bypass":False,
"classification":"EXTERNAL_AGENT_RUNTIME_SUCCEEDED",
}
def make_db(tmp_path,*,access=False,heartbeat=True):
tmp_path.mkdir(parents=True,exist_ok=True)
path=tmp_path/"state.sqlite3";con=sqlite3.connect(path)
con.executescript("""
CREATE TABLE tasks(
task_id TEXT PRIMARY KEY,goal TEXT NOT NULL,instructions TEXT NOT NULL,
success_criteria_json TEXT NOT NULL,required_capabilities_json TEXT NOT NULL,
constraints_json TEXT NOT NULL,metadata_json TEXT NOT NULL,deadline TEXT,
priority INTEGER NOT NULL,max_attempts INTEGER NOT NULL,attempt INTEGER NOT NULL,
status TEXT NOT NULL,claimed_by TEXT,lease_token_hash TEXT,lease_expires_at TEXT,
result_json TEXT,result_digest TEXT,result_idempotency_key TEXT,
verification_json TEXT,revision INTEGER NOT NULL,created_at TEXT,updated_at TEXT
);
CREATE TABLE events(
event_id INTEGER PRIMARY KEY AUTOINCREMENT,task_id TEXT,agent_id TEXT,
event_type TEXT NOT NULL,payload_json TEXT NOT NULL,created_at TEXT NOT NULL
);
""")
old=envelope([RUNTIME_CAPABILITY]);result=planner_result(old["execution_digest"],access=access)
verification={"decision":"NEEDS_REWORK","reason":"HOST_COORDINATOR_REQUIRED"}
metadata={"execution":old,"execution_projection":{
"schema":"aios.online-agent.task-projection.v1","task_id":TASK,
"job_id":JOB,"execution_digest":old["execution_digest"],
}}
con.execute("INSERT INTO tasks VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",(
TASK,runtime_input()["goal"],runtime_input()["instructions"],
json.dumps(runtime_input()["success_criteria"]),json.dumps([RUNTIME_CAPABILITY]),
"{}",json.dumps(metadata,sort_keys=True,separators=(",",":")),None,100,20,2,
"QUEUED",None,None,None,json.dumps(result,sort_keys=True,separators=(",",":")),
sha(result),"planner-result-v1",json.dumps(verification,sort_keys=True,separators=(",",":")),
11,"2026-08-20T00:00:00Z","2026-08-20T00:00:00Z",
))
events=["TASK_CREATED","TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZED","TASK_CLAIMED"]
if heartbeat: events.append("TASK_HEARTBEAT")
events += ["RESULT_SUBMITTED","TASK_VERIFIED"]
for event in events:
payload={"decision":"NEEDS_REWORK","next_state":"QUEUED"} if event=="TASK_VERIFIED" else {}
con.execute("INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) VALUES (?,?,?,?,?)",(TASK,"worker",event,json.dumps(payload),"2026-08-20T00:00:00Z"))
con.commit();con.close()
return path,metadata,result,verification,old
def request(metadata,result,verification,old,new):
return SupervisorCoordinatorProjectionRequest.from_mapping({
"expected_status":"QUEUED","expected_revision":11,"expected_attempt":2,
"expected_metadata_sha256":sha(metadata),
"expected_required_capabilities_sha256":sha([RUNTIME_CAPABILITY]),
"previous_verification_sha256":sha(verification),
"expected_planner_result_sha256":sha(result),
"expected_old_execution_digest":old["execution_digest"],
"expected_new_execution_digest":new["execution_digest"],
"expected_job_id":JOB,"authorization_id":AUTH,
"idempotency_key":"task-supervisor-coordinator-projection-v1",
"reason":"credential-isolated host coordinator required",
})
def test_projects_exact_coordinator_capabilities_and_preserves_planner_evidence(tmp_path):
path,metadata,result,verification,old=make_db(tmp_path)
new=envelope(COORDINATOR_CAPABILITIES)
store=SupervisorCoordinatorProjectionStore(path,lambda task,job,runtime:new)
response=store.project(TASK,request(metadata,result,verification,old,new))
assert response["revision"]==12 and response["attempt"]==2
assert response["required_capabilities"]==COORDINATOR_CAPABILITIES
assert response["credential_forwarded_to_planner"] is False
con=sqlite3.connect(path)
row=con.execute("select status,revision,metadata_json,required_capabilities_json,result_json from tasks").fetchone()
assert row[:2]==("QUEUED",12)
assert json.loads(row[2])["execution"]["execution_digest"]==new["execution_digest"]
assert json.loads(row[3])==COORDINATOR_CAPABILITIES
assert json.loads(row[4])==result
assert con.execute("select event_type from events order by event_id desc limit 1").fetchone()[0]=="TASK_SUPERVISOR_COORDINATOR_PROJECTION_AUTHORIZED"
def test_exact_replay_is_idempotent(tmp_path):
path,metadata,result,verification,old=make_db(tmp_path);new=envelope(COORDINATOR_CAPABILITIES)
store=SupervisorCoordinatorProjectionStore(path,lambda task,job,runtime:new)
req=request(metadata,result,verification,old,new)
assert store.project(TASK,req)["idempotent"] is False
assert store.project(TASK,req)["idempotent"] is True
def test_rejects_planner_with_control_access_or_missing_heartbeat(tmp_path):
path,metadata,result,verification,old=make_db(tmp_path,access=True);new=envelope(COORDINATOR_CAPABILITIES)
with pytest.raises(InitialExecutionProjectionError,match="RESULT_CONTRACT_INVALID"):
SupervisorCoordinatorProjectionStore(path,lambda *args:new).project(
TASK,request(metadata,result,verification,old,new)
)
path,metadata,result,verification,old=make_db(tmp_path/"second",heartbeat=False)
with pytest.raises(InitialExecutionProjectionError,match="EVENT_CHAIN_INVALID"):
SupervisorCoordinatorProjectionStore(path,lambda *args:new).project(
TASK,request(metadata,result,verification,old,new)
)
def test_request_rejects_arbitrary_metadata_patch(tmp_path):
path,metadata,result,verification,old=make_db(tmp_path);new=envelope(COORDINATOR_CAPABILITIES)
value=request(metadata,result,verification,old,new).public();value["metadata_patch"]={}
with pytest.raises(InitialExecutionProjectionError,match="FIELDS_INVALID"):
SupervisorCoordinatorProjectionRequest.from_mapping(value)
-117
View File
@@ -1,13 +1,4 @@
import hashlib
import importlib.util
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tarfile
import pytest
ROOT = Path(__file__).resolve().parents[1]
@@ -42,111 +33,3 @@ def test_model_api_builder_manifest_contract():
assert "'deployment_state': 'STAGING'" in builder
assert "'legacy_global_writes_enabled': False" in builder
assert 'private marker rejected' in builder
def _load_builder_module():
path = ROOT / 'scripts' / 'build_v2r2c1_model_api_staging.py'
spec = importlib.util.spec_from_file_location('model_api_packager', path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _run_builder(source: Path, output: Path, oid: str = 'a' * 40):
return subprocess.run(
[
sys.executable,
str(ROOT / 'scripts' / 'build_v2r2c1_model_api_staging.py'),
'--source-root',
str(source),
'--output-root',
str(output),
'--source-oid',
oid,
],
capture_output=True,
text=True,
)
def _minimal_source(tmp_path: Path) -> Path:
module = _load_builder_module()
source = tmp_path / 'source'
for name in module.REQUIRED_APP:
target = source / 'active-runtime' / 'app' / name
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / 'active-runtime' / 'app' / name, target)
for rel in module.REQUIRED_RUNTIME:
target = source / 'runtime' / 'agent-os' / rel
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / 'runtime' / 'agent-os' / rel, target)
for rel in module.REQUIRED_BUILDER_ASSETS:
target = source / rel
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / rel, target)
return source
def test_model_api_package_contains_builder_runtime_and_is_deterministic(tmp_path):
first = tmp_path / 'first'
second = tmp_path / 'second'
one = _run_builder(ROOT, first)
two = _run_builder(ROOT, second)
assert one.returncode == 0, one.stderr
assert two.returncode == 0, two.stderr
receipt = json.loads(one.stdout)
peer_receipt = json.loads(two.stdout)
assert one.stdout == two.stdout
archive = first / receipt['artifact_path']
peer_archive = second / peer_receipt['artifact_path']
assert hashlib.sha256(archive.read_bytes()).hexdigest() == hashlib.sha256(
peer_archive.read_bytes()
).hexdigest()
with tarfile.open(archive, 'r:gz') as bundle:
members = bundle.getmembers()
names = [member.name for member in members]
assert len(names) == len(set(names))
assert 'runtime/app/aios_trusted_candidate_builder.py' in names
assert 'runtime/config/gitops/trusted-candidate-builder-policy.json' in names
assert 'runtime/deploy/systemd/aios-trusted-candidate-builder.socket' in names
assert 'runtime/deploy/systemd/aios-trusted-candidate-builder.service' in names
assert all(member.isfile() or member.isdir() for member in members)
assert all('.git' not in Path(name).parts for name in names)
assert all(
member.mode == (0o755 if member.isdir() else 0o644)
for member in members
)
def test_model_api_packager_rejects_symlinked_required_asset(tmp_path):
source = _minimal_source(tmp_path)
policy = source / 'config' / 'gitops' / 'trusted-candidate-builder-policy.json'
policy.unlink()
try:
policy.symlink_to(
source / 'active-runtime' / 'app' / 'silent_release_hardening.py'
)
except OSError:
pytest.skip('creating symlinks is not permitted by this host')
result = _run_builder(source, tmp_path / 'output')
assert result.returncode != 0
assert 'symlink' in (result.stderr + result.stdout).lower()
def test_private_marker_rejects_real_pem_but_allows_prose(tmp_path):
module = _load_builder_module()
prose = tmp_path / 'prose.txt'
prose.write_text('Documentation says BEGIN PRIVATE KEY without a PEM fence.\n')
assert module.private_marker(prose) is False
private = tmp_path / 'private.pem'
pem_begin = '-----' + 'BEGIN PRIVATE KEY' + '-----\n'
pem_end = '-----' + 'END PRIVATE KEY' + '-----\n'
private.write_text(
pem_begin + 'c2Vuc2l0aXZlLWJ1aWxkZXIta2V5LW1hdGVyaWFs\n'
+ pem_end
)
assert module.private_marker(private) is True
@@ -1,484 +0,0 @@
from __future__ import annotations
import base64
import dataclasses
import hashlib
import importlib
import importlib.util
import json
import socket
import sys
import threading
import urllib.error
import urllib.request
from datetime import datetime, timedelta, timezone
from pathlib import Path
from types import SimpleNamespace
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
APP = Path(__file__).resolve().parents[1] / "active-runtime" / "app"
AGENT_OS = Path(__file__).resolve().parents[1] / "runtime" / "agent-os"
for import_root in (APP, AGENT_OS):
if str(import_root) not in sys.path:
sys.path.insert(0, str(import_root))
from silent_release_hardening import ( # noqa: E402
BuilderAttestation,
BuilderAttestationKeyRing,
BuilderAttestationVerifier,
CapabilityAuthorizationError,
SilentReleaseActionService,
TrustedCandidateBuilderUnavailable,
)
import silent_release_hardening as hardening # noqa: E402
COMMIT = "a" * 40
TREE = "b" * 40
SHA = "c" * 64
GITEA = {"consistent": True}
def _af_inet_available() -> bool:
try:
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except OSError:
return False
probe.close()
return True
def _canonical_digest(value: dict) -> str:
return hashlib.sha256(
json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
def _instant(delta: timedelta = timedelta()) -> str:
return (datetime.now(timezone.utc) + delta).isoformat().replace(
"+00:00", "Z"
)
def _keyring_payload(private: Ed25519PrivateKey, *, purpose: str) -> dict:
public = private.public_key().public_bytes(
serialization.Encoding.Raw, serialization.PublicFormat.Raw
)
record = {
"algorithm": "Ed25519",
"purpose": purpose,
"key_id": "builder-key-001",
"generation": 1,
"not_before": _instant(timedelta(minutes=-1)),
"expires_at": _instant(timedelta(days=1)),
"status": "ACTIVE",
"public_key": base64.b64encode(public).decode("ascii"),
"public_key_sha256": hashlib.sha256(public).hexdigest(),
}
payload = {
"algorithm": "Ed25519",
"purpose": "builder_attestation",
"keys": [record],
}
payload["keyring_sha256"] = hashlib.sha256(
json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
return payload
def _refresh_keyring_digest(payload: dict) -> dict:
canonical = {
"algorithm": payload["algorithm"],
"purpose": payload["purpose"],
"keys": payload["keys"],
}
payload["keyring_sha256"] = hashlib.sha256(
json.dumps(canonical, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
return payload
def _attestation(private: Ed25519PrivateKey) -> BuilderAttestation:
unsigned = BuilderAttestation(
builder_attestation_id="builder-attestation-001",
builder_key_id="builder-key-001",
source_commit=COMMIT,
tree_oid=TREE,
artifact_sha256=SHA,
manifest_sha256=SHA,
package_closure_sha256=SHA,
test_evidence_sha256=SHA,
canary_evidence_sha256=SHA,
issued_at=_instant(),
signature="",
builder_key_generation=1,
signature_algorithm="Ed25519",
signature_purpose="builder_attestation",
gitea_evidence_sha256=_canonical_digest(GITEA),
rollback_evidence_sha256=_canonical_digest({
"ready": True,
"rollback_sha256": SHA,
"source_commit": COMMIT,
}),
policy_evidence_sha256=SHA,
secret_scan_evidence_sha256=SHA,
)
return BuilderAttestation(
**{
**unsigned.canonical_payload,
"signature": base64.b64encode(
private.sign(unsigned.canonical_bytes())
).decode("ascii"),
}
)
def test_builder_verifier_rejects_owner_key_record_cross_purpose(tmp_path):
private = Ed25519PrivateKey.generate()
payload = _keyring_payload(private, purpose="owner_approval")
path = tmp_path / "builder-keyring.json"
path.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(ValueError, match="purpose"):
BuilderAttestationKeyRing.from_file(path)
def test_builder_verifier_checks_domain_key_window_digest_and_signature(tmp_path):
private = Ed25519PrivateKey.generate()
path = tmp_path / "builder-keyring.json"
path.write_text(
json.dumps(_keyring_payload(private, purpose="builder_attestation")),
encoding="utf-8",
)
verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing.from_file(path)
)
candidate = SimpleNamespace(
source_commit=COMMIT,
tree_oid=TREE,
artifact_sha256=SHA,
manifest_sha256=SHA,
gitea_consistency=GITEA,
rollback_ready=True,
)
plan = SimpleNamespace(
deployment=SimpleNamespace(
package_closure_sha256=SHA,
rollback_sha256=SHA,
),
policy_sha256=SHA,
)
assert verifier.verify(_attestation(private), candidate, plan).builder_key_id == "builder-key-001"
@pytest.mark.parametrize("state", ["REVOKED", "EXPIRED", "FUTURE"])
def test_builder_verifier_rejects_unavailable_key_lifecycle_states(tmp_path, state):
private = Ed25519PrivateKey.generate()
payload = _keyring_payload(private, purpose="builder_attestation")
record = payload["keys"][0]
if state == "REVOKED":
record["status"] = "REVOKED"
elif state == "EXPIRED":
record["not_before"] = _instant(timedelta(days=-2))
record["expires_at"] = _instant(timedelta(days=-1))
else:
record["not_before"] = _instant(timedelta(days=1))
record["expires_at"] = _instant(timedelta(days=2))
path = tmp_path / "builder-keyring.json"
path.write_text(json.dumps(_refresh_keyring_digest(payload)), encoding="utf-8")
verifier = BuilderAttestationVerifier(BuilderAttestationKeyRing.from_file(path))
candidate = SimpleNamespace(
source_commit=COMMIT, tree_oid=TREE,
artifact_sha256=SHA, manifest_sha256=SHA,
gitea_consistency=GITEA, rollback_ready=True,
)
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
with pytest.raises(CapabilityAuthorizationError, match="validity|revoked|unavailable"):
verifier.verify(_attestation(private), candidate, plan)
def test_builder_verifier_rejects_unknown_key_invalid_signature_and_keyring_digest(tmp_path):
private = Ed25519PrivateKey.generate()
payload = _keyring_payload(private, purpose="builder_attestation")
path = tmp_path / "builder-keyring.json"
path.write_text(json.dumps(payload), encoding="utf-8")
verifier = BuilderAttestationVerifier(BuilderAttestationKeyRing.from_file(path))
candidate = SimpleNamespace(
source_commit=COMMIT, tree_oid=TREE,
artifact_sha256=SHA, manifest_sha256=SHA,
gitea_consistency=GITEA, rollback_ready=True,
)
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
with pytest.raises(CapabilityAuthorizationError, match="not trusted"):
verifier.verify(
dataclasses.replace(_attestation(private), builder_key_id="unknown-builder-key"),
candidate,
plan,
)
with pytest.raises(CapabilityAuthorizationError, match="signature"):
verifier.verify(
dataclasses.replace(_attestation(private), signature=base64.b64encode(b"x" * 64).decode()),
candidate,
plan,
)
payload["keyring_sha256"] = "0" * 64
path.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(ValueError, match="keyring digest"):
BuilderAttestationKeyRing.from_file(path)
def test_builder_attestation_rejects_tampered_digest_and_signing_metadata():
private = Ed25519PrivateKey.generate()
raw = _attestation(private).to_dict()
raw["fingerprint"] = "0" * 64
with pytest.raises(CapabilityAuthorizationError, match="digest is invalid"):
BuilderAttestation.from_dict(raw, require_fingerprint=True)
@pytest.mark.parametrize(
"field,value",
[
("builder_key_generation", 2),
("signature_algorithm", "RSA"),
("signature_purpose", "owner_approval"),
],
)
def test_builder_keyring_rejects_attestation_metadata_mismatch(tmp_path, field, value):
private = Ed25519PrivateKey.generate()
path = tmp_path / "builder-keyring.json"
path.write_text(
json.dumps(_keyring_payload(private, purpose="builder_attestation")),
encoding="utf-8",
)
verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing.from_file(path)
)
candidate = SimpleNamespace(
source_commit=COMMIT, tree_oid=TREE,
artifact_sha256=SHA, manifest_sha256=SHA,
gitea_consistency=GITEA, rollback_ready=True,
)
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
with pytest.raises(CapabilityAuthorizationError, match="metadata"):
verifier.verify(
dataclasses.replace(_attestation(private), **{field: value}),
candidate,
plan,
)
raw = _attestation(private).to_dict()
raw["builder_key_generation"] = 2
with pytest.raises(CapabilityAuthorizationError, match="digest is invalid"):
BuilderAttestation.from_dict(raw, require_fingerprint=True)
def test_builder_unavailable_intent_is_bounded_retryable_and_recovers_without_secret(tmp_path, monkeypatch):
class UnavailableBuilder:
def build(self, intent):
raise TrustedCandidateBuilderUnavailable("socket token=should-not-leak")
monkeypatch.setattr(hardening, "_fsync_directory", lambda _path: None)
service = SilentReleaseActionService(
tmp_path,
candidate_builder=UnavailableBuilder(),
max_builder_attempts=2,
)
payload = {"event_id": "event-builder-001", "source_commit": COMMIT}
first = service.create_release_intent(payload)
second = service.create_release_intent(payload)
assert first["status"] == "BUILDER_RETRYABLE_FAILURE"
assert first["retryable"] is True
assert first["attempt_count"] == 1
assert "should-not-leak" not in json.dumps(first)
assert second["status"] == "BUILDER_TERMINAL_FAILURE"
assert second["retryable"] is False
assert second["attempt_count"] == 2
def test_stale_building_intent_recovers_after_restart_without_double_running_active_attempt(tmp_path, monkeypatch):
class CountingUnavailableBuilder:
calls = 0
def build(self, intent):
self.calls += 1
raise TrustedCandidateBuilderUnavailable("nonsecret")
monkeypatch.setattr(hardening, "_fsync_directory", lambda _path: None)
builder = CountingUnavailableBuilder()
service = SilentReleaseActionService(
tmp_path,
candidate_builder=builder,
max_builder_attempts=3,
builder_attempt_timeout_seconds=1,
)
payload = {"event_id": "event-builder-crash-001", "source_commit": COMMIT}
intent_id = "intent-" + hashlib.sha256(
f"{payload['event_id']}\0{COMMIT}".encode()
).hexdigest()[:24]
intent_path = tmp_path / "intents" / f"{intent_id}.json"
intent_path.parent.mkdir(parents=True)
active_record = {
"attempt_count": 1,
"attempt_started_at": _instant(),
"created_at": _instant(timedelta(minutes=-1)),
"event_id": payload["event_id"],
"intent_id": intent_id,
"source_commit": COMMIT,
"status": "BUILDING",
}
intent_path.write_text(json.dumps(active_record), encoding="utf-8")
active = service.create_release_intent(payload)
assert active["status"] == "BUILDING"
assert active["idempotent"] is True
assert builder.calls == 0
active_record["attempt_started_at"] = _instant(timedelta(minutes=-5))
intent_path.write_text(json.dumps(active_record), encoding="utf-8")
recovered = service.create_release_intent(payload)
assert recovered["status"] == "BUILDER_RETRYABLE_FAILURE"
assert recovered["attempt_count"] == 2
assert recovered["failure_code"] == "TRUSTED_BUILDER_UNAVAILABLE"
assert recovered["last_recovery_code"] == "TRUSTED_BUILDER_CRASH_RECOVERED"
assert recovered["recovery_count"] == 1
assert builder.calls == 1
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
@pytest.mark.skipif(
not _af_inet_available(),
reason="isolated executor permits AF_UNIX only; live HTTP probe runs at activation",
)
def test_model_api_keeps_process_liveness_separate_from_builder_readiness(monkeypatch):
api = importlib.import_module("aios_model_api_staging")
class FakeService:
ready = False
def get_builder_readiness(self):
return {"status": "READY" if self.ready else "UNAVAILABLE"}
def create_release_candidate(self, payload):
if not payload:
raise ValueError("event_id is required")
return {"status": "READY_FOR_APPROVAL"}
service = FakeService()
monkeypatch.setattr(api, "silent_release_service", lambda: service)
monkeypatch.setattr(
api,
"authenticate",
lambda token: (
{"account": "test", "role": "owner", "scopes": ["release:candidate"]}
if token == "test-token"
else None
),
)
server = api.ThreadingHTTPServer(("127.0.0.1", 0), api.H)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
base = f"http://127.0.0.1:{server.server_port}"
def request(path, body=None):
data = None if body is None else json.dumps(body).encode("utf-8")
value = urllib.request.Request(
base + path,
data=data,
headers={"Authorization": "Bearer test-token"},
method="POST" if body is not None else "GET",
)
try:
with urllib.request.urlopen(value, timeout=5) as response:
return response.status, json.load(response)
except urllib.error.HTTPError as error:
return error.code, json.load(error)
try:
health_code, health = request("/health")
unavailable_code, unavailable = request("/createReleaseCandidate", {})
service.ready = True
invalid_code, invalid = request("/createReleaseCandidate", {})
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
assert health_code == 200
assert health["process_liveness"] == "PASS"
assert health["builder_readiness"]["status"] == "UNAVAILABLE"
assert unavailable_code == 503
assert unavailable["error"] == "TRUSTED_BUILDER_UNAVAILABLE"
assert invalid_code == 400
assert invalid["error"] == "event_id is required"
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
def test_host_adapter_accepts_a_real_builder_bundle_response():
spec = importlib.util.spec_from_file_location(
"trusted_builder_public_seam",
APP.parents[1] / "tests" / "test_trusted_candidate_builder.py",
)
assert spec and spec.loader
backend_seam = importlib.util.module_from_spec(spec)
spec.loader.exec_module(backend_seam)
server, client, _ = backend_seam.make_seam()
candidate_id = "r42-release-candidate-v2r2c2-20260819"
source_commit = "71e736b05fab13efc06af7a4fe2dc022c1cf9a85"
response = server.handle_request(
client.create_request(
"V2R2C2", candidate_id, source_commit, nonce="z" * 32
),
connection=object(),
)
class ResponseClient:
def build(self, version, supplied_candidate_id, supplied_source, *, nonce):
assert version == "V2R2C2"
assert supplied_candidate_id == candidate_id
assert supplied_source == source_commit
assert len(nonce) >= 32
return response
api = importlib.import_module("aios_model_api_staging")
bundle = api._HostTrustedCandidateBuilderAdapter(ResponseClient()).build(
{
"candidate_id": candidate_id,
"source_commit": source_commit,
"version": "V2R2C2",
}
)
assert bundle.validate().candidate.source_commit == response["base_oid"]
assert bundle.attestation.builder_key_id == response["attestation"]["builder_key_id"]
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
def test_host_adapter_forwards_v2r2c3_to_the_policy_and_fails_closed_when_unavailable():
from aios_trusted_candidate_builder import BuilderError
api = importlib.import_module("aios_model_api_staging")
calls = []
class DerivedOnlyClient:
def build(self, version, candidate_id, source_commit, *, nonce):
calls.append((version, candidate_id, source_commit, nonce))
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
with pytest.raises(TrustedCandidateBuilderUnavailable):
api._HostTrustedCandidateBuilderAdapter(DerivedOnlyClient()).build(
{
"candidate_id": "r42-release-candidate-v2r2c3-20260820",
"source_commit": COMMIT,
"version": "V2R2C3",
}
)
assert calls[0][:3] == (
"V2R2C3",
"r42-release-candidate-v2r2c3-20260820",
COMMIT,
)
@@ -0,0 +1,116 @@
from __future__ import annotations
from pathlib import Path
import json
import sys
import pytest
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/"active-runtime/app"))
import aios_release_preparation_supervisor_coordinator as coordinator
def task():
return {
"task_id":coordinator.TASK_ID,
"status":"QUEUED",
"required_capabilities":coordinator.REQUIRED_CAPABILITIES,
"metadata":{
"execution":{
"action":"agent_runtime",
"adapter":"openclaw",
"capability_scope":coordinator.REQUIRED_CAPABILITIES,
"execution_digest":"e"*64,
"change_set":[],
"verification":[],
"governance":{
"writer_lease":False,
"main_write":False,
"production_write":False,
"tag_write":False,
"baseline_write":False,
"force_push":False,
"protected_ref_bypass":False,
},
}
},
}
@pytest.fixture(autouse=True)
def expected_digest(monkeypatch):
monkeypatch.setattr(coordinator,"EXPECTED_EXECUTION_DIGEST","e"*64)
def test_coordinator_accepts_only_exact_nonmutating_projection():
execution=coordinator.validate_task(task())
assert execution["adapter"]=="openclaw"
assert execution["governance"]["writer_lease"] is False
@pytest.mark.parametrize(
"mutate,error",
[
(lambda value:value.__setitem__("required_capabilities",["agent.runtime.openclaw"]),
"CAPABILITIES_INVALID"),
(lambda value:value["metadata"]["execution"].__setitem__("execution_digest","f"*64),
"ENVELOPE_INVALID"),
(lambda value:value["metadata"]["execution"]["governance"].__setitem__("production_write",True),
"ENVELOPE_INVALID"),
],
)
def test_coordinator_rejects_capability_or_governance_drift(mutate,error):
value=task();mutate(value)
with pytest.raises(coordinator.CoordinatorError,match=error):
coordinator.validate_task(value)
def test_heartbeat_is_control_plane_host_only_and_never_forwards_credentials():
progress=coordinator.heartbeat_progress(7)
assert progress["control_plane_access_granted"] is True
assert progress["external_planner_role"]=="UNPRIVILEGED_PLANNER"
assert progress["credential_forwarded_to_planner"] is False
assert progress["main_write"] is False
assert progress["production_write"] is False
encoded=json.dumps(progress,sort_keys=True).lower()
assert "bearer " not in encoded
assert "lease_token" not in encoded
def test_coordinate_claim_heartbeats_without_submitting_result(monkeypatch,tmp_path):
calls=[]
def fake(method,route,token,body=None):
calls.append((method,route,token,body))
return 200,{"status":"RUNNING","lease_expires_at":"2099-01-01T00:00:00Z"}
monkeypatch.setattr(coordinator,"api_call",fake)
monkeypatch.setattr(coordinator,"STATE_ROOT",tmp_path)
monkeypatch.setattr(coordinator,"HEARTBEAT_SECONDS",0)
count=coordinator.coordinate_claim(
{"lease_token":"l"*32},"agent-token-value-for-test",max_heartbeats=2
)
assert count==2
assert all(call[1].endswith("/heartbeat") for call in calls)
assert not any(call[1].endswith("/result") for call in calls)
assert all("agent-token" not in json.dumps(call[3]) for call in calls)
liveness=json.loads((tmp_path/"liveness.json").read_text())
assert liveness["event_type"]=="SUPERVISOR_COORDINATOR_HEARTBEAT"
assert liveness["credential_material_exposed"] is False
def test_units_use_existing_encrypted_agent_credential_and_no_planner_credential():
coordinator_unit=(
ROOT/"deploy/systemd/aios-release-preparation-supervisor-coordinator.service"
).read_text()
projection_unit=(
ROOT/"deploy/systemd/aios-agent-task-supervisor-coordinator-projection.service"
).read_text()
assert "aios-autonomous-agent-register.service" in coordinator_unit
assert "LoadCredentialEncrypted=agent_token:" in coordinator_unit
assert "control_token" not in coordinator_unit
assert "SupplementaryGroups=aios-agent-runtime" in coordinator_unit
assert "LoadCredential=agent_task_control_token:" in projection_unit
assert "User=aios-agent-task" in projection_unit
assert "ReadWritePaths=/var/lib/aios-agent-task" in projection_unit
-592
View File
@@ -1,592 +0,0 @@
from __future__ import annotations
import dataclasses
import hashlib
import hmac
import json
import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
import aios_trusted_candidate_builder as builder_runtime # noqa: E402
from aios_trusted_candidate_builder import ( # noqa: E402
BuilderError,
BuilderKeyring,
DeterministicEvidenceCollector,
PeerIdentity,
ServerOwnedEvidenceSource,
TrustedCandidateBuilder,
TrustedCandidateBuilderConfig,
UnixTrustedCandidateBuilderClient,
load_builder_policy,
)
from silent_release_controller import ReleaseCandidate, ReleaseState # noqa: E402
from silent_release_hardening import ( # noqa: E402
BuilderAttestation,
BuilderAttestationKeyRecord,
BuilderAttestationKeyRing,
BuilderAttestationVerifier,
DeploymentPlan,
FinalizationPlan,
ReleasePlan,
TagPlan,
TrustedCandidateBundle,
)
POLICY_PATH = ROOT / "config" / "gitops" / "trusted-candidate-builder-policy.json"
NOW = 1_725_000_000
class RecordingDomainSigner:
key_id = "test-builder-key"
key_generation = 1
def __init__(self) -> None:
self.calls: list[tuple[str, bytes]] = []
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
self.calls.append((purpose, payload))
return hashlib.sha256(b"test-signature\0" + payload).digest()
def make_seam(*, peer: PeerIdentity | None = None, cgroup: str = "/system.slice/aios-gitops-model-api.service", process=("/usr/bin/python3.12", ("/usr/bin/python3", "/opt/aios/gitops-model-api/test/runtime/app/aios_model_api_staging.py")), server_clock=lambda: NOW, client_clock=lambda: NOW, collector=None, nonce_ledger_path=None, signer_override=None) -> tuple[TrustedCandidateBuilder, UnixTrustedCandidateBuilderClient, RecordingDomainSigner]:
policy = load_builder_policy(POLICY_PATH)
config = TrustedCandidateBuilderConfig.from_policy(policy)
keyring = BuilderKeyring(builder_hmac=b"h" * 32)
signer = signer_override or RecordingDomainSigner()
server = TrustedCandidateBuilder(
config,
keyring,
collector=collector or DeterministicEvidenceCollector(config, evidence_source=passing_bundle),
signer=signer,
peer_identity_resolver=lambda _connection: peer or PeerIdentity(pid=77, uid=1000, gid=1000),
cgroup_resolver=lambda _pid: cgroup,
process_identity_resolver=lambda _pid: process,
clock=server_clock,
nonce_ledger_path=nonce_ledger_path,
)
return server, UnixTrustedCandidateBuilderClient(config, keyring, clock=client_clock), signer
def passing_bundle(recipe_name, recipe):
base_oid = recipe["base_oid"]
digest = lambda label: hashlib.sha256(f"{label}\0{base_oid}".encode()).hexdigest()
candidate = ReleaseCandidate(
candidate_id=recipe["candidate_id"], source_commit=base_oid,
tree_oid=digest("tree")[:40], artifact_sha256=digest("artifact"),
manifest_sha256=digest("manifest"), tests={"passed": True, "receipt": digest("tests")},
canary={"passed": True, "receipt": digest("canary")}, rollback_ready=True,
git_consistency={"consistent": True}, gitea_consistency={"consistent": True},
state=ReleaseState.READY_FOR_APPROVAL,
)
policy_sha = digest("policy")
plan = ReleasePlan(
deployment=DeploymentPlan(
service="aios-silent-release-controller", release_path="/srv/release/r1",
release_root="/srv/release", cutover_link="/srv/release/current",
dropin_path="/srv/release/r1/deploy/systemd/controller.service",
dropin_sha256=digest("dropin"), systemd_unit_sha256=digest("unit"),
rollback_path="/srv/release/rollback-r1", rollback_sha256=digest("rollback"),
source_commit=base_oid, tree_oid=candidate.tree_oid,
artifact_sha256=candidate.artifact_sha256, manifest_sha256=candidate.manifest_sha256,
package_closure_sha256=digest("closure"), authoritative_git_dir="/data/aios-gitops/repo.git",
repository_identity="aios/aios", baseline_before_sha256=digest("baseline"),
registry_before_sha256=digest("registry"), production_tag_refset_before_sha256=digest("tagset"),
health_contract_version="trusted-builder/v1", timeout_policy={"seconds": 10},
retry_policy={"attempts": 1}, policy_sha256=policy_sha, health_contract={"checked": True},
),
tag=TagPlan(
version="trusted-v2r2c2-001", tag_type="annotated", expected_tag_oid=digest("tag")[:40],
expected_target_oid=base_oid, evidence_closure_oid=digest("closure-oid")[:40],
approval_plan_id="owner-plan-trusted-v2r2c2", repository_identity="aios/aios",
gitea_owner="aios", gitea_repository="aios", current_remote_tag_oid=None,
remote_absent_evidence_sha256=digest("tag-absence"), verification_contract="trusted-builder/v1",
),
finalization=FinalizationPlan(
version="trusted-v2r2c2-001", expected_tag_oid=digest("tag")[:40], expected_target_oid=base_oid,
expected_previous_baseline_sha256=digest("baseline"), registry_expected_record={"base": base_oid},
baseline_expected_record={"base": base_oid}, final_consistency_contract="trusted-builder/v1",
), policy_sha256=policy_sha,
)
checks = {name: True for name in [
"authoritative_git", "artifact", "canary", "gitea", "manifest",
"package_closure", "rollback", "secret_scan", "tests",
]}
TrustedCandidateBundle(candidate, plan, checks).validate()
canonical_digest = lambda value: hashlib.sha256(
json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
evidence_digests = {
"artifact": candidate.artifact_sha256,
"manifest": candidate.manifest_sha256,
"package_closure": plan.deployment.package_closure_sha256,
"tests": canonical_digest(candidate.tests),
"canary": canonical_digest(candidate.canary),
"gitea": canonical_digest(candidate.gitea_consistency),
"rollback": canonical_digest({
"ready": True,
"rollback_sha256": plan.deployment.rollback_sha256,
"source_commit": candidate.source_commit,
}),
"policy": plan.policy_sha256,
"secret_scan": digest("secret-scan"),
}
return {
"candidate": candidate.to_dict(),
"release_plan": plan.to_dict(),
"checks": checks,
"evidence_digests": evidence_digests,
}
def rehmac(request: dict) -> None:
unsigned = {key: value for key, value in request.items() if key != "hmac"}
request["hmac"] = hmac.new(
b"h" * 32,
json.dumps(unsigned, sort_keys=True, separators=(",", ":")).encode(),
hashlib.sha256,
).hexdigest()
def test_public_build_seam_accepts_only_the_fixed_v2r2c2_recipe_and_returns_server_owned_evidence() -> None:
server, client, signer = make_seam()
request = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="a" * 32)
result = server.handle_request(request, connection=object())
assert result["schema"] == "aios.trusted-candidate-builder.response.v1"
assert result["status"] == "BUILT"
assert result["recipe"] == "V2R2C2"
assert result["repository"] == "aios/aios"
assert result["task_id"] == "r42-release-candidate-v2r2c2-live-20260819"
assert result["silent_release_bundle"]["candidate"]["candidate_id"] == "r42-release-candidate-v2r2c2-20260819"
assert result["base_oid"] == "71e736b05fab13efc06af7a4fe2dc022c1cf9a85"
assert set(result["checks"]) == set(server.config.required_checks)
assert all(result["checks"].values())
assert result["attestation"]["trust_domain"] == "aios.trusted-builder.attestation.v1"
assert signer.calls and signer.calls[0][0] == "builder_attestation"
assert result["attestation"]["fingerprint"] == hashlib.sha256(signer.calls[0][1]).hexdigest()
assert BuilderAttestation.from_dict(result["attestation"]).canonical_bytes() == signer.calls[0][1]
silent_bundle = result["silent_release_bundle"]
round_trip = TrustedCandidateBundle(
ReleaseCandidate.from_dict(silent_bundle["candidate"]),
ReleasePlan.from_dict(silent_bundle["release_plan"]),
silent_bundle["checks"],
)
assert round_trip.validate().candidate.candidate_id == result["evidence"]["candidate_id"]
@pytest.mark.parametrize(
"mutate, error",
[
(lambda request: request.update({"base_oid": "f" * 40}), "REQUEST_HMAC_INVALID"),
(lambda request: request.update({"artifact_sha256": "0" * 64}), "REQUEST_SCHEMA_INVALID"),
],
)
def test_public_build_seam_rejects_tampering_and_caller_owned_evidence(mutate, error) -> None:
server, client, _ = make_seam()
request = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="b" * 32)
mutate(request)
with pytest.raises(BuilderError, match=error):
server.handle_request(request, connection=object())
@pytest.mark.parametrize("field,value", [
("repository", "other/repository"),
("task_id", "other-task"),
("base_oid", "f" * 40),
("purpose", "owner_approval"),
])
def test_public_build_seam_rejects_reauthenticated_scope_rebinding(field, value) -> None:
server, client, _ = make_seam()
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="s" * 32,
)
request[field] = value
rehmac(request)
with pytest.raises(BuilderError, match="REQUEST_SCOPE_INVALID"):
server.handle_request(request, connection=object())
def test_public_build_seam_rejects_wrong_hmac() -> None:
server, client, _ = make_seam()
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="w" * 32,
)
request["hmac"] = "0" * 64
with pytest.raises(BuilderError, match="REQUEST_HMAC_INVALID"):
server.handle_request(request, connection=object())
def test_public_build_seam_returns_the_original_result_for_exact_nonce_replay_and_rejects_conflicts() -> None:
server, client, _ = make_seam()
request = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="c" * 32)
first = server.handle_request(request, connection=object())
assert server.handle_request(request, connection=object()) == first
conflict = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="c" * 32)
conflict["issued_at"] += 1
unsigned = {key: value for key, value in conflict.items() if key != "hmac"}
conflict["hmac"] = hmac.new(b"h" * 32, json.dumps(unsigned, sort_keys=True, separators=(",", ":")).encode(), hashlib.sha256).hexdigest()
with pytest.raises(BuilderError, match="REPLAY_CONFLICT"):
server.handle_request(conflict, connection=object())
def test_exact_replay_survives_restart_and_the_original_freshness_window(tmp_path) -> None:
ledger = tmp_path / "nonce-ledger.json"
server, client, _ = make_seam(nonce_ledger_path=ledger)
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="r" * 32,
)
first = server.handle_request(request, connection=object())
restarted, _, _ = make_seam(
server_clock=lambda: NOW + 3600,
nonce_ledger_path=ledger,
)
assert restarted.handle_request(request, connection=object()) == first
def test_readiness_probe_checks_collector_and_signer_without_signing() -> None:
server, client, signer = make_seam()
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="q" * 32,
operation=server.config.readiness_operation,
)
result = server.handle_request(request, connection=object())
assert result["status"] == "READY"
assert result["collector_ready"] is True
assert result["signer_ready"] is True
assert result["builder_key_id"] == signer.key_id
assert result["policy_sha256"] == server.config.policy_sha256
assert "signature" not in result
assert "silent_release_bundle" not in result
assert signer.calls == []
def test_readiness_fails_closed_when_signer_is_unavailable() -> None:
class UnavailableSigner:
@property
def key_id(self):
raise BuilderError("SIGNER_UNAVAILABLE")
@property
def key_generation(self):
return 1
def sign_for_purpose(self, purpose, payload):
raise AssertionError("readiness must not attempt signing")
server, client, _ = make_seam(signer_override=UnavailableSigner())
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="u" * 32,
operation=server.config.readiness_operation,
)
with pytest.raises(BuilderError, match="SIGNER_UNAVAILABLE"):
server.handle_request(request, connection=object())
def test_unix_client_applies_bounded_timeout_and_fails_closed(monkeypatch) -> None:
observed = {}
class TimeoutSocket:
def __enter__(self):
return self
def __exit__(self, *_args):
return False
def settimeout(self, seconds):
observed["timeout"] = seconds
def connect(self, _path):
raise TimeoutError("bounded test timeout")
monkeypatch.setattr(builder_runtime.socket, "socket", lambda *_args: TimeoutSocket())
monkeypatch.setattr(builder_runtime.socket, "AF_UNIX", 1, raising=False)
monkeypatch.setattr(builder_runtime.socket, "SOCK_STREAM", 1, raising=False)
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH))
client = UnixTrustedCandidateBuilderClient(
config,
BuilderKeyring(builder_hmac=b"h" * 32),
socket_path=Path("/run/aios-trusted-candidate-builder.sock"),
clock=lambda: NOW,
)
with pytest.raises(BuilderError, match="BUILDER_TRANSPORT_UNAVAILABLE"):
client.build(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="t" * 32,
)
assert observed["timeout"] == config.request_timeout_seconds
def test_backend_attestation_round_trip_verifies_with_production_keyring() -> None:
private = Ed25519PrivateKey.generate()
class Ed25519Signer:
key_id = "builder-key-001"
key_generation = 1
def sign_for_purpose(self, purpose, payload):
assert purpose == "builder_attestation"
return private.sign(payload)
server, client, _ = make_seam(signer_override=Ed25519Signer())
response = server.handle_request(
client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="v" * 32,
),
connection=object(),
)
public_key = private.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing(
[BuilderAttestationKeyRecord(
key_id="builder-key-001",
public_key=public_key,
algorithm="Ed25519",
purpose="builder_attestation",
generation=1,
not_before="2020-01-01T00:00:00Z",
expires_at="2099-01-01T00:00:00Z",
status="ACTIVE",
)],
require_attestation_metadata=True,
)
)
raw = response["silent_release_bundle"]
bundle = TrustedCandidateBundle(
ReleaseCandidate.from_dict(raw["candidate"]),
ReleasePlan.from_dict(raw["release_plan"]),
raw["checks"],
BuilderAttestation.from_dict(raw["attestation"], require_fingerprint=True),
)
assert bundle.validate(verifier).candidate.candidate_id == response["evidence"]["candidate_id"]
def test_public_build_seam_rejects_direct_unbound_v2r2c3_and_authentication_boundaries() -> None:
server, client, _ = make_seam()
with pytest.raises(BuilderError, match="GOVERNED_V2R2C3_DERIVATION_REQUIRED"):
client.create_request("V2R2C3", "unknown", "f" * 40, nonce="d" * 32)
denied_peer, denied_client, _ = make_seam(peer=PeerIdentity(pid=77, uid=0, gid=0))
with pytest.raises(BuilderError, match="PEER_IDENTITY_DENIED"):
denied_peer.handle_request(denied_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="e" * 32), connection=object())
denied_cgroup, denied_client, _ = make_seam(cgroup="/system.slice/untrusted.service")
with pytest.raises(BuilderError, match="PEER_CGROUP_DENIED"):
denied_cgroup.handle_request(denied_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="f" * 32), connection=object())
denied_process, denied_client, _ = make_seam(
process=("/usr/bin/python3.12", ("/usr/bin/python3", "/tmp/aios_model_api_staging.py"))
)
with pytest.raises(BuilderError, match="PEER_PROCESS_DENIED"):
denied_process.handle_request(denied_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="p" * 32), connection=object())
stale_server, stale_client, _ = make_seam(client_clock=lambda: NOW - 61)
with pytest.raises(BuilderError, match="REQUEST_STALE"):
stale_server.handle_request(stale_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="g" * 32), connection=object())
def test_public_build_seam_fails_closed_when_server_owned_collector_is_incomplete() -> None:
def incomplete_evidence(recipe_name, recipe):
value = passing_bundle(recipe_name, recipe)
value["checks"]["canary"] = False
return value
policy = load_builder_policy(POLICY_PATH)
config = TrustedCandidateBuilderConfig.from_policy(policy)
server, client, _ = make_seam(
collector=DeterministicEvidenceCollector(config, evidence_source=incomplete_evidence)
)
with pytest.raises(BuilderError, match="COLLECTOR_CHECKS_INCOMPLETE"):
server.handle_request(client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="h" * 32), connection=object())
def test_server_owned_source_rehashes_every_fixed_evidence_file_and_rejects_tampering(tmp_path) -> None:
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH))
recipe = config.fixed_recipe("V2R2C2")
raw = passing_bundle("V2R2C2", recipe)
candidate = ReleaseCandidate.from_dict(raw["candidate"])
plan = ReleasePlan.from_dict(raw["release_plan"])
evidence_root = tmp_path / "evidence"
version_root = evidence_root / "V2R2C2"
version_root.mkdir(parents=True)
def write_bytes(name: str, value: bytes) -> str:
path = version_root / name
path.write_bytes(value)
path.chmod(0o600)
return hashlib.sha256(value).hexdigest()
def canonical(value: dict) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
tests = {"passed": True, "receipt": "tests-fixed"}
canary = {"passed": True, "receipt": "canary-fixed"}
gitea = {
"consistent": True,
"remote_ref_oid": recipe["source_commit"],
"repository": "aios/aios",
"source_commit": recipe["source_commit"],
"tree_oid": candidate.tree_oid,
}
git_consistency = {
"consistent": True,
"repository": "aios/aios",
"source_commit": recipe["source_commit"],
"tree_oid": candidate.tree_oid,
}
rollback = {
"ready": True,
"rollback_sha256": plan.deployment.rollback_sha256,
"source_commit": recipe["source_commit"],
}
artifact_bytes = b"immutable-artifact"
manifest_bytes = b'{"manifest":"fixed"}\n'
closure_bytes = b"closure artifact.tar.gz\n"
policy_bytes = b'{"policy":"fixed"}\n'
artifact_sha = write_bytes("artifact.tar.gz", artifact_bytes)
manifest_sha = write_bytes("manifest.json", manifest_bytes)
closure_sha = write_bytes("checksums.sha256", closure_bytes)
tests_sha = write_bytes("tests.json", canonical(tests))
canary_sha = write_bytes("canary.json", canonical(canary))
gitea_sha = write_bytes("gitea.json", canonical(gitea))
rollback_sha = write_bytes("rollback.json", canonical(rollback))
policy_sha = write_bytes("silent-release-policy.json", policy_bytes)
secret_scan = {"artifact_sha256": artifact_sha, "passed": True}
secret_sha = write_bytes("secret-scan.json", canonical(secret_scan))
candidate.artifact_sha256 = artifact_sha
candidate.manifest_sha256 = manifest_sha
candidate.tests = tests
candidate.canary = canary
candidate.git_consistency = git_consistency
candidate.gitea_consistency = gitea
deployment = dataclasses.replace(
plan.deployment,
artifact_sha256=artifact_sha,
manifest_sha256=manifest_sha,
package_closure_sha256=closure_sha,
policy_sha256=policy_sha,
)
plan = dataclasses.replace(plan, deployment=deployment, policy_sha256=policy_sha)
receipt = {
"schema": "aios.trusted-candidate-builder.evidence.v1",
"version": "V2R2C2",
"candidate": candidate.to_dict(),
"release_plan": plan.to_dict(),
}
write_bytes("receipt.json", canonical(receipt))
git_state = {"tree": candidate.tree_oid, "ancestry": 0}
def git_runner(argv, **_kwargs):
if "rev-parse" in argv:
return SimpleNamespace(stdout=git_state["tree"] + "\n", returncode=0)
return SimpleNamespace(stdout="", returncode=git_state["ancestry"])
source = ServerOwnedEvidenceSource(
config,
tmp_path / "repo.git",
evidence_root,
expected_owner_uid=None,
git_runner=git_runner,
)
collected = source("V2R2C2", recipe)
assert collected["candidate"]["artifact_sha256"] == artifact_sha
assert collected["evidence_digests"] == {
"artifact": artifact_sha,
"manifest": manifest_sha,
"package_closure": closure_sha,
"tests": tests_sha,
"canary": canary_sha,
"gitea": gitea_sha,
"rollback": rollback_sha,
"policy": policy_sha,
"secret_scan": secret_sha,
}
(version_root / "manifest.json").write_bytes(b"tampered")
with pytest.raises(BuilderError, match="COLLECTOR_DIGEST_MISMATCH"):
source("V2R2C2", recipe)
(version_root / "manifest.json").write_bytes(manifest_bytes)
for name, tampered, expected in (
("artifact.tar.gz", b"tampered", "COLLECTOR_DIGEST_MISMATCH"),
("checksums.sha256", b"tampered", "COLLECTOR_DIGEST_MISMATCH"),
("silent-release-policy.json", b"tampered", "COLLECTOR_DIGEST_MISMATCH"),
("tests.json", canonical({"passed": True, "receipt": "wrong"}), "COLLECTOR_TESTS_MISMATCH"),
("canary.json", canonical({"passed": True, "receipt": "wrong"}), "COLLECTOR_CANARY_MISMATCH"),
("gitea.json", canonical({**gitea, "consistent": False}), "COLLECTOR_GITEA_MISMATCH"),
("rollback.json", canonical({**rollback, "ready": False}), "COLLECTOR_ROLLBACK_MISMATCH"),
("secret-scan.json", canonical({**secret_scan, "passed": False}), "COLLECTOR_SECRET_SCAN_MISMATCH"),
):
path = version_root / name
original = path.read_bytes()
path.write_bytes(tampered)
with pytest.raises(BuilderError, match=expected):
source("V2R2C2", recipe)
path.write_bytes(original)
git_state["tree"] = "f" * 40
with pytest.raises(BuilderError, match="COLLECTOR_GIT_MISMATCH"):
source("V2R2C2", recipe)
git_state["tree"] = candidate.tree_oid
git_state["ancestry"] = 1
with pytest.raises(BuilderError, match="COLLECTOR_GIT_MISMATCH"):
source("V2R2C2", recipe)
def test_systemd_socket_and_service_are_af_unix_hardened_and_credential_bound() -> None:
socket_unit = (ROOT / "deploy/systemd/aios-trusted-candidate-builder.socket").read_text()
service_unit = (ROOT / "deploy/systemd/aios-trusted-candidate-builder.service").read_text()
assert "ListenStream=/run/aios-trusted-candidate-builder.sock" in socket_unit
assert "SocketMode=0660" in socket_unit
assert "LoadCredentialEncrypted=signing_private_key:" in service_unit
assert "LoadCredentialEncrypted=builder_hmac:" in service_unit
assert "NoNewPrivileges=true" in service_unit
assert "ProtectSystem=strict" in service_unit
assert "PrivateNetwork=true" in service_unit
assert "RestrictAddressFamilies=AF_UNIX" in service_unit
assert "StateDirectory=aios-trusted-candidate-builder" in service_unit
assert "AIOS_TRUSTED_BUILDER_KEY_GENERATION=1" in service_unit
assert "aios-approved-release-executor" not in service_unit
assert "--socket-fd 3" in service_unit
policy = load_builder_policy(POLICY_PATH)
assert policy["allowed_peer_uid"] == 1000
assert policy["allowed_peer_cgroups"] == [
"/system.slice/aios-gitops-model-api.service"
]
+215
View File
@@ -0,0 +1,215 @@
import ast
import json
import os
import struct
from collections.abc import Mapping
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
from aios_agent_task_protocol import TaskProtocolError, TaskStore
from aios_chatgpt_bridge_v2_server import BridgeHandler
import aios_durable_owner_authorization as owner_auth
from aios_online_agent_execution import GovernedExecutionBinding, project_online_task
from aios_online_agent_worker import WorkerError, WorkerSupervisor
CAPABILITIES = ["git.read", "git.test", "governed.write.branch", "governed.write.file"]
def binding(task_id="unified-test-task-20260824"):
return GovernedExecutionBinding.from_mapping({
"task_id": task_id,
"job_id": "job-" + "a" * 32,
"repository": "aios/aios",
"base_oid": "0" * 40,
"expected_branch_oid": "0" * 40,
"target_branch": "agent/unified-test-20260824",
"branch_mode": "CREATE_NEW",
"generation": 1,
"idempotency_key": "unified-test-task-20260824-v1",
})
def projected(task_id="unified-test-task-20260824"):
return project_online_task(
binding(task_id),
{
"goal": "test governed task",
"instructions": "test only",
"success_criteria": [],
"required_capabilities": CAPABILITIES,
"priority": 50,
"max_attempts": 3,
},
{
"action": "source_capture",
"adapter": "source_capture",
"capability_scope": CAPABILITIES,
"source_signals": ["durable_policy"],
},
)
def test_write_task_rejected_before_persistence(tmp_path):
store = TaskStore(tmp_path / "state.sqlite3")
with pytest.raises(TaskProtocolError) as error:
store.create_task(
task_id="bare-write-task-20260824",
goal="bare",
instructions="must reject",
success_criteria=[],
required_capabilities=CAPABILITIES,
metadata={},
idempotency_key="bare-write-task-20260824-v1",
)
assert error.value.code == "GOVERNED_EXECUTION_BINDING_REQUIRED"
with pytest.raises(TaskProtocolError) as missing:
store.get_task("bare-write-task-20260824")
assert missing.value.code == "TASK_NOT_FOUND"
def test_complete_projection_persists_envelope(tmp_path):
store = TaskStore(tmp_path / "state.sqlite3")
task = projected()
created = store.create_task(
task_id=task["task_id"],
goal=task["goal"],
instructions=task["instructions"],
success_criteria=task["success_criteria"],
required_capabilities=task["required_capabilities"],
constraints={},
metadata=task["metadata"],
priority=task["priority"],
max_attempts=task["max_attempts"],
idempotency_key=task["idempotency_key"],
)
execution = created["metadata"]["execution"]
assert created["status"] == "QUEUED"
assert execution["schema"] == "aios.online-agent.execution.v2"
assert execution["job_id"].startswith("job-")
assert execution["execution_digest"]
assert created["metadata"]["execution_projection"]["execution_digest"] == execution["execution_digest"]
def test_online_route_requires_governed_creation_for_write_caps():
source = Path(__file__).parents[1] / "active-runtime" / "app" / "aios_model_api_staging.py"
tree = ast.parse(source.read_text(encoding="utf-8"))
function = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "requires_governed_execution")
namespace = {
"Mapping": Mapping,
"Any": Any,
"GOVERNED_EXECUTION_CAPABILITIES": {"governed.write.branch", "governed.write.file"},
}
exec(compile(ast.Module(body=[function], type_ignores=[]), str(source), "exec"), namespace)
guard = namespace["requires_governed_execution"]
assert guard({"required_capabilities": CAPABILITIES})
assert not guard({"required_capabilities": ["git.read", "git.test"]})
def test_bridge_requires_claim_for_projection_and_job(monkeypatch):
handler = object.__new__(BridgeHandler)
handler.headers = {"Authorization": "Bearer agent-credential"}
handler.control_token = None
handler.agent_task_api_url = "http://task-api"
class Response:
def __init__(self, value):
self.value = value
def __enter__(self):
return self
def __exit__(self, *_args):
return False
def read(self):
return json.dumps(self.value).encode()
responses = iter([
{"agent_id": "worker-1"},
{"task_id": "task-1", "claimed_by": None, "status": "QUEUED"},
])
monkeypatch.setattr("aios_chatgpt_bridge_v2_server.urllib.request.urlopen", lambda *_args, **_kwargs: Response(next(responses)))
assert not handler._authorized("projectOnlineAgentExecution", {"task_id": "task-1"})
responses = iter([
{"agent_id": "worker-1"},
{"task_id": "task-1", "claimed_by": "worker-1", "status": "CLAIMED"},
])
assert handler._authorized("getGitOpsJob", {"task_id": "task-1"})
def test_broker_uses_so_peercred_and_canonical_group(monkeypatch):
class Connection:
def getsockopt(self, *_args):
return struct.pack("3i", os.getpid(), os.getuid(), os.getgid())
monkeypatch.setattr(owner_auth, "_peer_process_status", lambda _pid: (os.getuid(), os.getgid(), {os.getgid(), 4242}))
monkeypatch.setattr(owner_auth, "CANONICAL_BROKER_GROUP", "aios-autonomous-agent-worker")
monkeypatch.setattr(owner_auth.grp, "getgrnam", lambda _name: SimpleNamespace(gr_gid=4242))
assert owner_auth.authorize_broker_peer(Connection())
monkeypatch.setattr(owner_auth, "_peer_process_status", lambda _pid: (os.getuid(), os.getgid(), {os.getgid()}))
with pytest.raises(owner_auth.AuthorizationError) as error:
owner_auth.authorize_broker_peer(Connection())
assert error.value.code == "SOCKGROUP_NOT_AUTHORIZED"
def test_bootstrap_rejects_bad_projection_before_job(tmp_path):
from aios_governed_task_bootstrap import BootstrapError, GovernedTaskBootstrap
class Authority:
def commit_exists(self, _oid):
return True
def read_ref(self, _ref):
return None
bootstrap = GovernedTaskBootstrap(
Authority(), tmp_path, remote_name="gitea-http",
token_path=tmp_path / "token", ssh_key_path=tmp_path / "key",
)
with pytest.raises(BootstrapError) as error:
bootstrap.create({
"task_id": "atomic-projection-failure-20260824",
"repository": "aios/aios",
"base_oid": "0" * 40,
"expected_branch_oid": "0" * 40,
"target_branch": "agent/atomic-projection-failure-20260824",
"risk": "engineering",
"description": "invalid deterministic placeholder",
"idempotency_key": "atomic-projection-failure-20260824-v1",
"online_task": {
"goal": "invalid",
"instructions": "invalid",
"success_criteria": [],
"required_capabilities": CAPABILITIES,
},
"execution": {
"action": "deterministic",
"adapter": "deterministic",
"capability_scope": CAPABILITIES,
"change_set": [],
"verification": [],
},
"require_online_task_projection": True,
})
assert error.value.args[0] == "EXECUTION_BINDING_INVALID"
assert not list((tmp_path / "jobs").rglob("state.sqlite3"))
assert not list((tmp_path / "task-bindings").glob("*.json"))
def test_worker_rejects_bare_write_task():
supervisor = object.__new__(WorkerSupervisor)
with pytest.raises(WorkerError) as error:
supervisor.resolve_envelope({
"task_id": "bare-worker-task-20260824",
"required_capabilities": CAPABILITIES,
"metadata": {},
})
assert error.value.code == "TASK_SKIPPED_PROJECTION_MISSING"
def test_continuation_scope_allows_existing_subset():
source = Path(__file__).parents[1] / "active-runtime" / "app" / "aios_continuation_executor.py"
text = source.read_text(encoding="utf-8")
assert "if dirty and not dirty.issubset(expected_paths):" in text
assert "if dirty and dirty != expected_paths:" not in text