Compare commits

..
Author SHA1 Message Date
chatgpt-gitops-bot f958e5f0a9 feat(task-plane): add Codex review worker systemd unit 2026-07-25 11:22:19 +08:00
chatgpt-gitops-bot ba218ab514 feat(task-plane): add Codex review worker systemd unit 2026-07-25 10:25:43 +08:00
chatgpt-gitops-bot 8325282cf0 fix-task-plane-fail-closed-on-incomplete-artifact 2026-07-25 00:52:36 +08:00
chatgpt-gitops-bot 0bb70f7488 fix-task-plane-poll-created-shadow-tasks 2026-07-25 00:42:19 +08:00
chatgpt-gitops-bot c57e92a4a6 feat-task-plane-add-Codex-worker-artifact-and-canary 2026-07-25 00:36:46 +08:00
chatgpt-gitops-bot 2b41644f3c feat-task-plane-add-Codex-worker-artifact-and-canary 2026-07-25 00:36:46 +08:00
chatgpt-gitops-bot 19fa06e540 feat-task-plane-add-Codex-worker-artifact-and-canary 2026-07-25 00:36:45 +08:00
chatgpt-gitops-bot 4a280bd64a feat-task-plane-add-Codex-worker-artifact-and-canary 2026-07-25 00:36:45 +08:00
chatgpt-gitops-bot 3d7ee3f5dc fix-task-plane-submit-server-compatible-shadow-result 2026-07-25 00:32:14 +08:00
chatgpt-gitops-bot 097af5a755 fix-task-plane-normalize-runner-policy-errors 2026-07-25 00:30:20 +08:00
chatgpt-gitops-bot 4a7f13a530 test-task-plane-harden-Codex-worker-security 2026-07-25 00:29:19 +08:00
chatgpt-gitops-bot 76f40a619f test-task-plane-harden-Codex-worker-security 2026-07-25 00:29:19 +08:00
chatgpt-gitops-bot 159fd2b305 test-task-plane-harden-Codex-worker-security 2026-07-25 00:29:19 +08:00
chatgpt-gitops-bot f389f0d382 test-task-plane-harden-Codex-worker-security 2026-07-25 00:29:18 +08:00
chatgpt-gitops-bot 749dd1e111 test-task-plane-harden-Codex-worker-security 2026-07-25 00:29:18 +08:00
chatgpt-gitops-bot 5bd585df4d fix-task-plane-harden-Codex-worker-lifecycle 2026-07-25 00:25:53 +08:00
chatgpt-gitops-bot 00fb1d9f3f fix-task-plane-harden-Codex-worker-lifecycle 2026-07-25 00:25:52 +08:00
chatgpt-gitops-bot 878ff1abab fix-task-plane-harden-Codex-worker-lifecycle 2026-07-25 00:25:52 +08:00
chatgpt-gitops-bot 2a6e284a11 fix-task-plane-harden-Codex-worker-lifecycle 2026-07-25 00:25:52 +08:00
chatgpt-gitops-bot d67d64b33c stage-b: secure writer module updates (tests/task_plane/workers/test_codex_worker_recovery.py) 2026-07-25 00:02:40 +08:00
chatgpt-gitops-bot 8eef5961bc stage-b: secure writer module updates (tests/task_plane/workers/test_codex_worker_security.py) 2026-07-25 00:02:39 +08:00
chatgpt-gitops-bot 063949afe5 stage-b: secure writer module updates (tests/task_plane/workers/test_codex_worker.py) 2026-07-25 00:02:39 +08:00
chatgpt-gitops-bot d276a8ecb3 stage-b: secure writer module updates (active-runtime/app/task_plane/workers/runner.py) 2026-07-25 00:02:38 +08:00
chatgpt-gitops-bot af38646ae9 stage-b: secure writer module updates (active-runtime/app/task_plane/workers/workspace.py) 2026-07-25 00:02:38 +08:00
chatgpt-gitops-bot f85a3d7d89 aios(stage-a): add active-runtime/app/task_plane/workers/result_attestation.py 2026-07-24 23:52:07 +08:00
chatgpt-gitops-bot 7013c659c7 aios(stage-a): add active-runtime/app/task_plane/workers/progress.py 2026-07-24 23:52:07 +08:00
chatgpt-gitops-bot e25f1fecda aios(stage-a): add active-runtime/app/task_plane/workers/codex_review_test.py 2026-07-24 23:52:07 +08:00
chatgpt-gitops-bot 45637da2b3 aios(stage-a): add active-runtime/app/task_plane/workers/task_client.py 2026-07-24 23:52:06 +08:00
chatgpt-gitops-bot 5bc0d0f3ac chore(task-plane): verify Codex writer endpoint 2026-07-24 23:10:40 +08:00
chatgpt-gitops-bot cfebcb7904 feat(task-plane): add codex worker command policy 2026-07-24 22:12:55 +08:00
78 changed files with 1894 additions and 8149 deletions
@@ -5,148 +5,13 @@ import json
import os
import subprocess
from pathlib import Path
import urllib.parse
import aios_chatgpt_actions_api as api
import aios_isolated_writer_policy as policy
from isolated_writer_authz import AuthorizationError, authorize
from isolated_writer_commit_file import AtomicCommitFile, CommitFileError, OperationLedger
from isolated_writer_token_registry import TokenRegistry
_reject = api.reject_non_read_http
_write_file = api.write_file
_sync_branch = api.sync_branch
_original_do_get = api.H.do_GET
api.OPENAPI = Path(os.getenv("AIOS_GITOPS_ISOLATED_WRITER_OPENAPI", str(api.OPENAPI)))
def _registry() -> TokenRegistry:
return TokenRegistry(Path(os.environ["AIOS_GITOPS_TOKEN_REGISTRY_V2"]))
def _atomic_writer() -> AtomicCommitFile:
return AtomicCommitFile(
workspace=api.WS,
bare_repo=api.BARE_REPO,
ledger=OperationLedger(Path(os.environ["AIOS_GITOPS_WRITER_OPERATION_LEDGER"])),
remote=os.getenv("AIOS_GITOPS_GITEA_PUSH_REMOTE", ""),
audit=policy.append_audit_record,
)
def _bearer(handler) -> str:
value = handler.headers.get("Authorization", "")
return value[7:].strip() if value.startswith("Bearer ") else ""
def _authorize(handler, *, method: str, path: str, branch=None, target_path=None):
grant = authorize(
_registry(),
_bearer(handler),
method=method,
path=path,
source_ip=str(handler.client_address[0]),
workspace=api.WS,
branch=branch,
target_path=target_path,
)
grant.record["_source_ip"] = str(handler.client_address[0])
return grant
def _authorization_error(handler, exc: AuthorizationError):
return handler.sendj({"error": exc.code}, exc.status)
def _isolated_runtime_status(handler):
branch = api.sh(["rev-parse", "--abbrev-ref", "HEAD"], True)["stdout"]
head = api.sh(["rev-parse", "HEAD"], True)["stdout"]
return handler.sendj({
"service": os.getenv(
"AIOS_GITOPS_SERVICE_AUDIENCE",
"aios-isolated-writer.service",
),
"status": "active",
"runtime_role": os.getenv("AIOS_GITOPS_RUNTIME_ROLE"),
"workspace_role": os.getenv("AIOS_GITOPS_WORKSPACE_ROLE"),
"writes_enabled": os.getenv("AIOS_GITOPS_WRITES_ENABLED", "").lower()
in {"1", "true", "yes", "on"},
"allowed_branch": policy.allowed_branch(),
"approved_base_oid": policy.allowed_base_oid(),
"workspace_branch": branch,
"workspace_head_oid": head,
"repository_identity": os.getenv("AIOS_GITOPS_REPOSITORY_IDENTITY"),
"token_policy_version": 2,
"atomic_commit_file": True,
"path_policy_sha256": policy.configured_path_policy_sha256(),
})
def _isolated_do_get(self):
path = urllib.parse.urlparse(self.path).path.rstrip("/") or "/"
if path in {"/health", "/api/health"}:
return self.sendj({
"status": "ok",
"service": os.getenv(
"AIOS_GITOPS_SERVICE_AUDIENCE",
"aios-isolated-writer.service",
),
})
if path not in {"/api/runtime-status", "/api/baseline", "/api/patches"}:
return self.sendj({"error": "isolated_writer_operation_forbidden"}, 403)
try:
_authorize(self, method="GET", path=path)
except AuthorizationError as exc:
return _authorization_error(self, exc)
if path == "/api/runtime-status":
return _isolated_runtime_status(self)
return _original_do_get(self)
def _read_commit_file_body(handler):
try:
length = int(handler.headers.get("Content-Length", "0"))
except ValueError:
raise CommitFileError("writer_content_length_rejected") from None
if length <= 0:
raise CommitFileError("writer_request_object_required")
if length > 1_100_000:
raise CommitFileError("writer_request_too_large", 413)
raw = handler.rfile.read(length)
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
raise CommitFileError("writer_request_json_rejected") from None
if not isinstance(value, dict):
raise CommitFileError("writer_request_object_required")
return value
def _isolated_do_post(self):
path = urllib.parse.urlparse(self.path).path.rstrip("/") or "/"
if path != "/api/commit-file":
return self.sendj({"error": "isolated_writer_operation_forbidden"}, 403)
try:
data = _read_commit_file_body(self)
branch = str(data.get("branch") or "")
target_path = str(data.get("path") or "")
grant = _authorize(
self,
method="POST",
path=path,
branch=branch,
target_path=target_path,
)
if api.content_has_secret(str(data.get("content") or "")):
raise CommitFileError("writer_secret_content_rejected", 403)
result = _atomic_writer().execute(grant, data)
return self.sendj(result)
except AuthorizationError as exc:
return _authorization_error(self, exc)
except CommitFileError as exc:
return self.sendj({"error": exc.code}, exc.status)
except PermissionError:
return self.sendj({"error": "isolated_writer_policy_rejected"}, 403)
def reject_non_read_http(handler, method: str, path: str) -> bool:
@@ -308,8 +173,6 @@ api.H.branch, api.H.file, api.H.commit, api.H.patch = (
_checked_commit,
_checked_patch,
)
api.H.do_GET = _isolated_do_get
api.H.do_POST = _isolated_do_post
if __name__ == "__main__":
policy.require_service_invariants(api.WS, api.BARE_REPO)
@@ -9,9 +9,10 @@ import re
from pathlib import Path, PurePosixPath, PureWindowsPath
from urllib.parse import unquote
import isolated_writer_path_policy as path_policy
WRITE_ENDPOINTS = frozenset({"/api/commit-file"})
WRITE_ENDPOINTS = frozenset({
"/api/branch", "/api/create-branch", "/api/file", "/api/commit-file",
"/api/commit", "/api/patch", "/api/create-patch",
})
ALLOWED_ROOTS = (
"active-runtime",
"docs",
@@ -68,15 +69,6 @@ def require_service_invariants(workspace: Path, bare_repo: Path) -> None:
raise PermissionError("isolated_writer_role_required")
if os.getenv("AIOS_GITOPS_WORKSPACE_ROLE", "") != "isolated-writer":
raise PermissionError("isolated_writer_workspace_role_required")
if os.getenv("AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED", "") != "2":
raise PermissionError("isolated_writer_token_policy_v2_required")
if not os.getenv("AIOS_GITOPS_SERVICE_AUDIENCE", "").strip():
raise PermissionError("isolated_writer_service_audience_required")
if not os.getenv("AIOS_GITOPS_TOKEN_REGISTRY_V2", "").strip():
raise PermissionError("isolated_writer_token_registry_required")
if not os.getenv("AIOS_GITOPS_WRITER_OPERATION_LEDGER", "").strip():
raise PermissionError("isolated_writer_operation_ledger_required")
path_policy.configured_path_policy()
for name in _DENY_FLAGS:
if os.getenv(name, "").strip().lower() not in _FALSE:
raise PermissionError("isolated_writer_forbidden_capability_flag")
@@ -190,7 +182,6 @@ def _audit_sanitize(value):
"private_key",
)
)
and str(key).lower() != "token_id"
else _audit_sanitize(inner)
)
for key, inner in value.items()
@@ -248,14 +239,9 @@ def validate_path(path: str, workspace: Path | None = None) -> str:
):
raise PermissionError("isolated_writer_path_rejected")
first = parts[0]
service_exact_path = False
if first in DENIED_ROOTS:
try:
path_policy.require_service_path("commit_file.atomic", value)
service_exact_path = True
except path_policy.PathPolicyError as exc:
raise PermissionError(str(exc)) from None
if value != "README.md" and first not in ALLOWED_ROOTS and not service_exact_path:
raise PermissionError("isolated_writer_protected_path_rejected")
if value != "README.md" and first not in ALLOWED_ROOTS:
raise PermissionError("isolated_writer_path_outside_allowed_roots")
if workspace is not None:
lexical_root = Path(os.path.abspath(str(workspace)))
@@ -275,22 +261,6 @@ def validate_path(path: str, workspace: Path | None = None) -> str:
return value
def validate_content_for_path(path: str, content: str) -> None:
if path.split("/", 1)[0] not in DENIED_ROOTS:
return
try:
path_policy.validate_content("commit_file.atomic", path, content)
except path_policy.PathPolicyError as exc:
raise PermissionError(str(exc)) from None
def configured_path_policy_sha256() -> str | None:
try:
return path_policy.configured_path_policy_sha256()
except path_policy.PathPolicyError as exc:
raise PermissionError(str(exc)) from None
def validate_commit_paths(paths: list[str], workspace: Path | None = None) -> None:
if enforcement_required():
for path in paths:
-167
View File
@@ -1,167 +0,0 @@
"""Fail-closed operation authorization for an isolated Git writer."""
from __future__ import annotations
import ipaddress
import os
import re
import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from isolated_writer_token_registry import TokenRegistry
ROUTE_OPERATIONS = {
("GET", "/api/runtime-status"): "runtime.status.read",
("GET", "/api/baseline"): "baseline.read",
("GET", "/api/patches"): "patches.read",
("POST", "/api/commit-file"): "commit_file.atomic",
}
OID_RE = re.compile(r"[0-9a-f]{40}")
class AuthorizationError(PermissionError):
def __init__(self, code: str, status: int = 403):
super().__init__(code)
self.code = code
self.status = status
@dataclass(frozen=True)
class AuthorizedGrant:
record: dict[str, Any]
operation: str
use_count: int
@property
def token_id(self) -> str:
return str(self.record["token_id"])
@property
def task_id(self) -> str:
return str(self.record["task_id"])
def operation_for(method: str, path: str) -> str:
operation = ROUTE_OPERATIONS.get((method.upper(), path))
if not operation:
raise AuthorizationError("isolated_writer_operation_forbidden")
return operation
def _parse_time(value: Any) -> datetime:
try:
parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
except (TypeError, ValueError):
raise AuthorizationError("writer_token_time_invalid") from None
if parsed.tzinfo is None:
raise AuthorizationError("writer_token_time_invalid")
return parsed.astimezone(timezone.utc)
def _git_is_ancestor(workspace: Path, base: str, branch: str) -> bool:
result = subprocess.run(
["git", "merge-base", "--is-ancestor", base, branch],
cwd=workspace,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return result.returncode == 0
def _validate_grant(
grant: dict[str, Any],
*,
audience: str,
source_ip: str,
operation: str,
branch: str | None,
path: str | None,
workspace: Path,
current_time: datetime,
) -> None:
if int(grant.get("schema_version", 0)) != 2:
raise AuthorizationError("writer_token_policy_v2_required", 401)
if str(grant.get("audience") or "") != audience:
raise AuthorizationError("writer_token_audience_rejected")
try:
peer = ipaddress.ip_address(source_ip)
networks = [ipaddress.ip_network(str(value), strict=True) for value in grant.get("source_cidrs", [])]
except ValueError:
raise AuthorizationError("writer_token_source_rejected") from None
if not networks or not any(peer in network for network in networks):
raise AuthorizationError("writer_token_source_rejected")
created = _parse_time(grant.get("created_at"))
not_before = _parse_time(grant.get("not_before"))
expires = _parse_time(grant.get("expires_at"))
if expires <= not_before or (expires - created).total_seconds() > 1800:
raise AuthorizationError("writer_token_lifetime_rejected")
if current_time < not_before:
raise AuthorizationError("writer_token_not_yet_valid", 401)
if current_time >= expires:
raise AuthorizationError("writer_token_expired", 401)
if bool(grant.get("revoked")):
raise AuthorizationError("writer_token_revoked", 401)
operations = grant.get("allowed_operations")
if not isinstance(operations, list) or operation not in operations:
raise AuthorizationError("writer_token_operation_rejected")
expected_branch = str(grant.get("allowed_branch") or "")
configured_branch = os.getenv("AIOS_GITOPS_ALLOWED_BRANCH", "")
if expected_branch != configured_branch:
raise AuthorizationError("writer_token_branch_rejected")
if branch is not None and branch != expected_branch:
raise AuthorizationError("writer_token_branch_rejected")
if path is not None:
paths = grant.get("allowed_paths")
if not isinstance(paths, list) or path not in paths:
raise AuthorizationError("writer_token_path_rejected")
base = str(grant.get("approved_base_oid") or "").lower()
if not OID_RE.fullmatch(base):
raise AuthorizationError("writer_token_base_rejected")
if not _git_is_ancestor(workspace, base, expected_branch):
raise AuthorizationError("writer_token_base_not_ancestor")
def authorize(
registry: TokenRegistry,
token: str,
*,
method: str,
path: str,
source_ip: str,
workspace: Path,
branch: str | None = None,
target_path: str | None = None,
current_time: datetime | None = None,
) -> AuthorizedGrant:
if os.getenv("AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED", "") != "2":
raise AuthorizationError("writer_token_policy_v2_not_required")
if not token:
raise AuthorizationError("unauthorized", 401)
grant = registry.find(token)
if grant is None:
raise AuthorizationError("unauthorized", 401)
operation = operation_for(method, path)
_validate_grant(
grant,
audience=os.getenv("AIOS_GITOPS_SERVICE_AUDIENCE", ""),
source_ip=source_ip,
operation=operation,
branch=branch,
path=target_path,
workspace=workspace,
current_time=current_time or datetime.now(timezone.utc),
)
limits = grant.get("max_uses")
if not isinstance(limits, dict):
raise AuthorizationError("writer_token_use_limit_missing")
try:
used = registry.consume(str(grant["token_id"]), operation, int(limits[operation]))
except (KeyError, TypeError, ValueError):
raise AuthorizationError("writer_token_use_limit_missing") from None
except PermissionError as exc:
code = str(exc)
raise AuthorizationError(code, 401 if code == "writer_token_revoked" else 403) from None
return AuthorizedGrant(grant, operation, used)
@@ -1,403 +0,0 @@
"""Atomic, idempotent commit-file Saga for the isolated writer."""
from __future__ import annotations
import fcntl
import hashlib
import json
import os
import re
import sqlite3
import subprocess
import tempfile
import threading
from pathlib import Path
from typing import Any
import aios_isolated_writer_policy as policy
OID_RE = re.compile(r"[0-9a-f]{40}")
IDEMPOTENCY_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{7,127}")
STATES = frozenset({
"PENDING", "FILE_WRITTEN", "COMMITTED", "PUSHED", "VERIFIED", "COMPLETED",
"FAILED_RECOVERABLE", "MANUAL_REVIEW_REQUIRED",
})
_LOCKS: dict[str, threading.Lock] = {}
_LOCKS_GUARD = threading.Lock()
_LEDGER_INIT_LOCK = threading.Lock()
class CommitFileError(RuntimeError):
def __init__(self, code: str, status: int = 400):
super().__init__(code)
self.code = code
self.status = status
class SimulatedCrash(RuntimeError):
pass
def canonical_digest(data: dict[str, Any]) -> str:
payload = {
"branch": data["branch"],
"path": data["path"],
"content_sha256": hashlib.sha256(data["content"].encode("utf-8")).hexdigest(),
"message": data["message"],
"expected_head_oid": data["expected_head_oid"],
}
encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
class OperationLedger:
def __init__(self, path: Path | str):
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
if self.path.parent.is_symlink() or self.path.is_symlink():
raise PermissionError("writer_operation_ledger_symlink_rejected")
with _LEDGER_INIT_LOCK:
with self.connect() as db:
db.execute(
"""
CREATE TABLE IF NOT EXISTS writer_operations (
operation_id TEXT PRIMARY KEY,
token_id TEXT NOT NULL,
task_id TEXT NOT NULL,
idempotency_key TEXT NOT NULL UNIQUE,
operation_digest TEXT NOT NULL,
branch TEXT NOT NULL,
path TEXT NOT NULL,
content_sha256 TEXT NOT NULL,
pre_head_oid TEXT NOT NULL,
commit_oid TEXT,
push_status TEXT,
verification_status TEXT,
state TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
error_code TEXT
)
"""
)
os.chmod(self.path, 0o600)
def connect(self) -> sqlite3.Connection:
db = sqlite3.connect(self.path, timeout=30)
db.row_factory = sqlite3.Row
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA synchronous=FULL")
return db
def get_or_create(self, grant: Any, data: dict[str, Any], digest: str) -> dict[str, Any]:
key = data["idempotency_key"]
with self.connect() as db:
db.execute("BEGIN IMMEDIATE")
row = db.execute(
"SELECT * FROM writer_operations WHERE idempotency_key=?", (key,)
).fetchone()
if row:
db.commit()
record = dict(row)
if record["operation_digest"] != digest:
raise CommitFileError("writer_idempotency_conflict", 409)
if (
record["token_id"] != grant.token_id
or record["task_id"] != grant.task_id
):
raise CommitFileError("writer_idempotency_owner_conflict", 409)
return record
operation_id = hashlib.sha256(
f"{grant.task_id}\0{key}".encode("utf-8")
).hexdigest()
content_sha = hashlib.sha256(data["content"].encode("utf-8")).hexdigest()
db.execute(
"""
INSERT INTO writer_operations(
operation_id,token_id,task_id,idempotency_key,operation_digest,
branch,path,content_sha256,pre_head_oid,state
) VALUES(?,?,?,?,?,?,?,?,?,?)
""",
(
operation_id, grant.token_id, grant.task_id, key, digest,
data["branch"], data["path"], content_sha,
data["expected_head_oid"], "PENDING",
),
)
db.commit()
return self.get(key)
def get(self, key: str) -> dict[str, Any]:
with self.connect() as db:
row = db.execute(
"SELECT * FROM writer_operations WHERE idempotency_key=?", (key,)
).fetchone()
if row is None:
raise KeyError(key)
return dict(row)
def update(self, key: str, state: str, **fields: Any) -> dict[str, Any]:
if state not in STATES:
raise ValueError("invalid writer operation state")
allowed = {"commit_oid", "push_status", "verification_status", "error_code"}
assignments = ["state=?", "updated_at=CURRENT_TIMESTAMP"]
values: list[Any] = [state]
for name, value in fields.items():
if name not in allowed:
raise ValueError("invalid ledger field")
assignments.append(f"{name}=?")
values.append(value)
values.append(key)
with self.connect() as db:
db.execute("BEGIN IMMEDIATE")
db.execute(
f"UPDATE writer_operations SET {','.join(assignments)} WHERE idempotency_key=?",
values,
)
db.commit()
return self.get(key)
class AtomicCommitFile:
def __init__(
self,
*,
workspace: Path,
bare_repo: Path,
ledger: OperationLedger,
remote: str,
audit,
crash_after_state: str | None = None,
):
self.workspace = workspace
self.bare_repo = bare_repo
self.ledger = ledger
self.remote = remote
self.audit = audit
self.crash_after_state = crash_after_state
def _git(self, *args: str, check: bool = True) -> str:
result = subprocess.run(
["git", *args], cwd=self.workspace, text=True, capture_output=True
)
if check and result.returncode != 0:
raise CommitFileError("writer_git_operation_failed", 500)
return result.stdout.strip()
def _transition(self, key: str, state: str, **fields: Any) -> dict[str, Any]:
record = self.ledger.update(key, state, **fields)
if self.crash_after_state == state:
raise SimulatedCrash(state)
return record
def _validate(self, data: Any) -> dict[str, Any]:
if not isinstance(data, dict):
raise CommitFileError("writer_request_object_required")
required = ("branch", "path", "content", "message", "expected_head_oid", "idempotency_key")
if any(field not in data for field in required):
raise CommitFileError("writer_request_incomplete")
if data.get("force") or data.get("force_push"):
raise CommitFileError("isolated_writer_force_rejected", 403)
if set(data) != set(required):
raise CommitFileError("writer_request_fields_rejected")
item = {field: data[field] for field in required}
if item["branch"] != policy.allowed_branch():
raise CommitFileError("isolated_writer_branch_rejected", 403)
try:
item["path"] = policy.validate_path(item["path"], self.workspace)
except PermissionError as exc:
raise CommitFileError(str(exc), 403) from None
if not isinstance(item["content"], str) or "\x00" in item["content"]:
raise CommitFileError("writer_content_rejected")
try:
policy.validate_content_for_path(item["path"], item["content"])
except PermissionError as exc:
raise CommitFileError(str(exc), 403) from None
if len(item["content"].encode("utf-8")) > 1_048_576:
raise CommitFileError("writer_content_too_large", 413)
message = item["message"]
if (
not isinstance(message, str)
or not 1 <= len(message) <= 200
or "\x00" in message
or "\n" in message
or "\r" in message
):
raise CommitFileError("writer_message_rejected")
if not isinstance(item["expected_head_oid"], str) or not OID_RE.fullmatch(item["expected_head_oid"]):
raise CommitFileError("writer_expected_head_rejected")
if not isinstance(item["idempotency_key"], str) or not IDEMPOTENCY_RE.fullmatch(item["idempotency_key"]):
raise CommitFileError("writer_idempotency_key_rejected")
return item
def _atomic_write(self, relative: str, content: str) -> None:
target = self.workspace / relative
target.parent.mkdir(parents=True, exist_ok=True)
fd, temporary = tempfile.mkstemp(prefix=".writer-", dir=target.parent)
try:
with os.fdopen(fd, "wb", closefd=True) as stream:
stream.write(content.encode("utf-8"))
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
parent_fd = os.open(target.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(parent_fd)
finally:
os.close(parent_fd)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def _verify_remote(self, branch: str, oid: str) -> None:
bare = subprocess.run(
["git", "--git-dir", str(self.bare_repo), "rev-parse", f"refs/heads/{branch}"],
text=True, capture_output=True,
)
if bare.returncode != 0 or bare.stdout.strip() != oid:
raise CommitFileError("writer_authoritative_bare_oid_mismatch", 500)
remote = subprocess.run(
["git", "ls-remote", "--heads", self.remote, f"refs/heads/{branch}"],
cwd=self.workspace, text=True, capture_output=True,
)
if remote.returncode != 0:
raise CommitFileError("writer_gitea_verification_failed", 503)
fields = remote.stdout.strip().split()
if len(fields) != 2:
raise CommitFileError("writer_gitea_ref_missing", 503)
if fields[0] != oid:
raise CommitFileError("writer_gitea_oid_conflict", 409)
def execute(self, grant: Any, raw_data: Any) -> dict[str, Any]:
data = self._validate(raw_data)
digest = canonical_digest(data)
branch = data["branch"]
with _LOCKS_GUARD:
lock = _LOCKS.setdefault(branch, threading.Lock())
lock_path = self.ledger.path.parent / f"{hashlib.sha256(branch.encode()).hexdigest()}.lock"
with lock, open(lock_path, "a+b") as branch_lock:
fcntl.flock(branch_lock.fileno(), fcntl.LOCK_EX)
return self._execute_locked(grant, data, digest)
def _execute_locked(self, grant: Any, data: dict[str, Any], digest: str) -> dict[str, Any]:
key = data["idempotency_key"]
record = self.ledger.get_or_create(grant, data, digest)
if record["state"] == "COMPLETED":
return self._result(record, idempotent=True)
if self._git("rev-parse", "--abbrev-ref", "HEAD") != data["branch"]:
record = self._transition(
key, "MANUAL_REVIEW_REQUIRED", error_code="writer_current_branch_conflict"
)
self._audit(grant, data, digest, record, "manual_review_required")
raise CommitFileError("writer_current_branch_conflict", 409)
head = self._git("rev-parse", "HEAD")
commit_oid = record.get("commit_oid")
if head != record["pre_head_oid"] and head != commit_oid:
record = self._transition(
key, "MANUAL_REVIEW_REQUIRED", error_code="writer_branch_head_conflict"
)
self._audit(grant, data, digest, record, "manual_review_required")
raise CommitFileError("writer_branch_head_conflict", 409)
try:
state = record["state"]
if state == "MANUAL_REVIEW_REQUIRED":
raise CommitFileError("writer_manual_review_required", 409)
if state == "FAILED_RECOVERABLE":
if record.get("push_status") == "confirmed":
state = "PUSHED"
else:
state = "COMMITTED" if commit_oid else "FILE_WRITTEN"
if state == "PENDING":
self._atomic_write(data["path"], data["content"])
record = self._transition(key, "FILE_WRITTEN")
state = record["state"]
if state == "FILE_WRITTEN":
actual_sha = hashlib.sha256(
(self.workspace / data["path"]).read_bytes()
).hexdigest()
if actual_sha != record["content_sha256"]:
raise CommitFileError("writer_file_content_conflict", 409)
staged_before = self._git("diff", "--cached", "--name-only", check=False).splitlines()
if staged_before:
raise CommitFileError("writer_extra_staged_files_rejected", 409)
self._git("add", "--", data["path"])
staged = self._git("diff", "--cached", "--name-only").splitlines()
if staged != [data["path"]]:
raise CommitFileError("writer_extra_staged_files_rejected", 409)
self._git("commit", "-m", data["message"])
commit_oid = self._git("rev-parse", "HEAD")
record = self._transition(key, "COMMITTED", commit_oid=commit_oid)
state = record["state"]
if state == "COMMITTED":
commit_oid = str(record["commit_oid"])
pushed = subprocess.run(
["git", "push", "--porcelain", self.remote, f"HEAD:refs/heads/{data['branch']}"],
cwd=self.workspace, text=True, capture_output=True,
)
if pushed.returncode != 0:
self._transition(
key, "FAILED_RECOVERABLE",
push_status="failed", error_code="writer_gitea_push_failed",
)
raise CommitFileError("writer_gitea_push_failed", 503)
record = self._transition(key, "PUSHED", push_status="confirmed")
state = record["state"]
if state == "PUSHED":
commit_oid = str(record["commit_oid"])
self._verify_remote(data["branch"], commit_oid)
record = self._transition(
key, "VERIFIED", verification_status="bare+gitea"
)
state = record["state"]
if state == "VERIFIED":
record = self._transition(key, "COMPLETED", error_code=None)
self._audit(grant, data, digest, record, "completed")
return self._result(record, idempotent=False)
except SimulatedCrash:
raise
except CommitFileError as exc:
latest = self.ledger.get(key)
if latest["state"] not in {"FAILED_RECOVERABLE", "MANUAL_REVIEW_REQUIRED"}:
failure_state = (
"MANUAL_REVIEW_REQUIRED"
if exc.code in {
"writer_gitea_oid_conflict",
"writer_file_content_conflict",
"writer_current_branch_conflict",
}
else "FAILED_RECOVERABLE"
)
latest = self._transition(key, failure_state, error_code=exc.code)
self._audit(grant, data, digest, latest, "failed")
raise
def _audit(self, grant: Any, data: dict[str, Any], digest: str, record: dict[str, Any], result: str) -> None:
self.audit({
"token_id": grant.token_id,
"task_id": grant.task_id,
"audience": grant.record["audience"],
"source_ip": grant.record.get("_source_ip", "socket-peer"),
"operation": grant.operation,
"branch": data["branch"],
"path": data["path"],
"idempotency_key": data["idempotency_key"],
"operation_digest": digest,
"path_policy_sha256": policy.configured_path_policy_sha256(),
"pre_head_oid": record["pre_head_oid"],
"commit_oid": record.get("commit_oid"),
"result": result,
})
@staticmethod
def _result(record: dict[str, Any], *, idempotent: bool) -> dict[str, Any]:
return {
"status": "completed",
"state": record["state"],
"operation_id": record["operation_id"],
"branch": record["branch"],
"path": record["path"],
"commit_oid": record.get("commit_oid"),
"push_status": record.get("push_status"),
"verification_status": record.get("verification_status"),
"idempotent": idempotent,
}
@@ -1,422 +0,0 @@
"""Strict service-level exact-path policy for isolated Git writers."""
from __future__ import annotations
import grp
import hashlib
import json
import os
import re
import stat
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path, PurePosixPath, PureWindowsPath
from typing import Any
from urllib.parse import unquote
MAX_POLICY_BYTES = 65_536
POLICY_FIELDS = frozenset(
{
"schema_version",
"policy_id",
"audience",
"repository_identity",
"allowed_branch",
"allowed_operations",
}
)
OPERATION_FIELDS = frozenset({"exact_paths", "max_content_bytes", "content_profile"})
SUPPORTED_OPERATIONS = frozenset({"commit_file.atomic"})
SUPPORTED_CONTENT_PROFILES = frozenset({"systemd-codex-review-worker-v1"})
_SECRET_RE = re.compile(
r"(?i)(authorization\s*:|bearer\s+|(?:token|password|secret)\s*[:=])"
)
_SYSTEMD_NAME_RE = re.compile(r"[A-Za-z][A-Za-z0-9]*\Z")
_SYSTEMD_MAX_LINE_BYTES = 4096
_SYSTEMD_ALLOWED_DIRECTIVES = {
"Unit": frozenset({"Description", "After", "Wants"}),
"Service": frozenset(
{
"Type",
"User",
"Group",
"WorkingDirectory",
"Environment",
"ExecStart",
"Restart",
"RestartSec",
"NoNewPrivileges",
"PrivateTmp",
"PrivateDevices",
"ProtectSystem",
"ProtectHome",
"ProtectKernelTunables",
"ProtectKernelModules",
"ProtectControlGroups",
"LockPersonality",
"RestrictSUIDSGID",
"RestrictRealtime",
"MemoryDenyWriteExecute",
"RuntimeDirectory",
"StateDirectory",
"CacheDirectory",
"ReadOnlyPaths",
"ReadWritePaths",
"UMask",
}
),
"Install": frozenset({"WantedBy"}),
}
_SYSTEMD_MULTI_DIRECTIVES = frozenset(
{
("Service", "Environment"),
("Service", "ReadOnlyPaths"),
("Service", "ReadWritePaths"),
}
)
_SYSTEMD_EXACT_SINGLE_VALUES = {
("Unit", "Description"): "AIOS Codex Review/Test Worker",
("Unit", "After"): "network-online.target",
("Unit", "Wants"): "network-online.target",
("Service", "Type"): "simple",
("Service", "User"): "aiman",
("Service", "Group"): "gitops",
(
"Service",
"WorkingDirectory",
): "/usr/libexec/aios-codex-review-test-worker/current",
(
"Service",
"ExecStart",
): "/usr/bin/python3 -m task_plane.workers.codex_review_test",
("Service", "Restart"): "on-failure",
("Service", "RestartSec"): "2",
("Service", "NoNewPrivileges"): "true",
("Service", "PrivateTmp"): "true",
("Service", "PrivateDevices"): "true",
("Service", "ProtectSystem"): "strict",
("Service", "ProtectHome"): "true",
("Service", "ProtectKernelTunables"): "true",
("Service", "ProtectKernelModules"): "true",
("Service", "ProtectControlGroups"): "true",
("Service", "LockPersonality"): "true",
("Service", "RestrictSUIDSGID"): "true",
("Service", "RestrictRealtime"): "true",
("Service", "MemoryDenyWriteExecute"): "true",
("Service", "RuntimeDirectory"): "aios-codex-review-test-worker",
("Service", "StateDirectory"): "aios-codex-review-test-worker",
("Service", "CacheDirectory"): "aios-codex-review-test-worker",
("Service", "UMask"): "0077",
("Install", "WantedBy"): "multi-user.target",
}
_SYSTEMD_EXACT_MULTI_VALUES = {
("Service", "Environment"): (
"AIOS_CODEX_WORKER_MODE=shadow",
"AIOS_TASK_PLANE_EXECUTION_ENABLED=false",
"PYTHONDONTWRITEBYTECODE=1",
),
("Service", "ReadOnlyPaths"): (
"/data/aios-gitops/repo.git",
"/usr/libexec/aios-codex-review-test-worker",
),
("Service", "ReadWritePaths"): (
"/run/aios-codex-review-test-worker",
"/var/lib/aios-codex-review-test-worker",
"/var/cache/aios-codex-review-test-worker",
),
}
class PathPolicyError(PermissionError):
pass
@dataclass(frozen=True)
class OperationPolicy:
exact_paths: tuple[str, ...]
max_content_bytes: int
content_profile: str
@dataclass(frozen=True)
class ServicePathPolicy:
schema_version: int
policy_id: str
audience: str
repository_identity: str
allowed_branch: str
allowed_operations: dict[str, OperationPolicy]
sha256: str
def operation(self, name: str) -> OperationPolicy:
try:
return self.allowed_operations[name]
except KeyError:
raise PathPolicyError("isolated_writer_service_operation_rejected") from None
def require_exact_path(self, operation: str, path: str) -> OperationPolicy:
selected = self.operation(operation)
if path not in selected.exact_paths:
raise PathPolicyError("isolated_writer_service_path_rejected")
return selected
def _canonical_json(value: Any) -> bytes:
return (
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
+ "\n"
).encode("utf-8")
def validate_relative_path_syntax(path: str) -> str:
if not isinstance(path, str):
raise PathPolicyError("isolated_writer_path_rejected")
if not path or path != path.strip() or "\x00" in path or "\\" in path:
raise PathPolicyError("isolated_writer_path_rejected")
try:
decoded = unquote(path, errors="strict")
except (UnicodeDecodeError, ValueError):
raise PathPolicyError("isolated_writer_path_rejected") from None
if decoded != path:
raise PathPolicyError("isolated_writer_path_rejected")
posix = PurePosixPath(path)
windows = PureWindowsPath(path)
parts = path.split("/")
if (
posix.is_absolute()
or windows.is_absolute()
or bool(windows.drive)
or any(part in {"", ".", ".."} for part in parts)
):
raise PathPolicyError("isolated_writer_path_rejected")
return path
def _require_fields(value: dict[str, Any], allowed: frozenset[str], code: str) -> None:
if set(value) != allowed:
raise PathPolicyError(code)
def _parse_policy(document: Any, canonical: bytes) -> ServicePathPolicy:
if not isinstance(document, dict):
raise PathPolicyError("isolated_writer_path_policy_object_required")
_require_fields(document, POLICY_FIELDS, "isolated_writer_path_policy_fields_rejected")
if document["schema_version"] != 1:
raise PathPolicyError("isolated_writer_path_policy_schema_rejected")
text_fields = (
"policy_id",
"audience",
"repository_identity",
"allowed_branch",
)
if any(
not isinstance(document[field], str) or not document[field].strip()
for field in text_fields
):
raise PathPolicyError("isolated_writer_path_policy_value_rejected")
raw_operations = document["allowed_operations"]
if not isinstance(raw_operations, dict) or not raw_operations:
raise PathPolicyError("isolated_writer_path_policy_operations_rejected")
if not set(raw_operations).issubset(SUPPORTED_OPERATIONS):
raise PathPolicyError("isolated_writer_path_policy_operations_rejected")
operations: dict[str, OperationPolicy] = {}
for name, raw in raw_operations.items():
if not isinstance(raw, dict):
raise PathPolicyError("isolated_writer_path_policy_operation_rejected")
_require_fields(
raw,
OPERATION_FIELDS,
"isolated_writer_path_policy_operation_fields_rejected",
)
paths = raw["exact_paths"]
if (
not isinstance(paths, list)
or not paths
or any(not isinstance(item, str) for item in paths)
):
raise PathPolicyError("isolated_writer_path_policy_paths_rejected")
validated = tuple(validate_relative_path_syntax(item) for item in paths)
if len(set(validated)) != len(validated):
raise PathPolicyError("isolated_writer_path_policy_paths_rejected")
maximum = raw["max_content_bytes"]
if not isinstance(maximum, int) or isinstance(maximum, bool) or not 1 <= maximum <= 1_048_576:
raise PathPolicyError("isolated_writer_path_policy_size_rejected")
profile = raw["content_profile"]
if profile not in SUPPORTED_CONTENT_PROFILES:
raise PathPolicyError("isolated_writer_path_policy_profile_rejected")
operations[name] = OperationPolicy(validated, maximum, profile)
return ServicePathPolicy(
schema_version=1,
policy_id=document["policy_id"],
audience=document["audience"],
repository_identity=document["repository_identity"],
allowed_branch=document["allowed_branch"],
allowed_operations=operations,
sha256=hashlib.sha256(canonical).hexdigest(),
)
def load_path_policy(
path: Path,
*,
expected_owner_uid: int = 0,
expected_group_gid: int | None = None,
expected_parent_mode: int = 0o750,
expected_file_mode: int = 0o640,
) -> ServicePathPolicy:
lexical = Path(os.path.abspath(str(path)))
if lexical.is_symlink():
raise PathPolicyError("isolated_writer_path_policy_symlink_rejected")
try:
parent_info = lexical.parent.stat(follow_symlinks=False)
file_info = lexical.stat(follow_symlinks=False)
except FileNotFoundError:
raise PathPolicyError("isolated_writer_path_policy_missing") from None
if stat.S_ISLNK(parent_info.st_mode) or not stat.S_ISDIR(parent_info.st_mode):
raise PathPolicyError("isolated_writer_path_policy_parent_rejected")
if stat.S_ISLNK(file_info.st_mode) or not stat.S_ISREG(file_info.st_mode):
raise PathPolicyError("isolated_writer_path_policy_file_rejected")
if lexical.resolve(strict=True) != lexical:
raise PathPolicyError("isolated_writer_path_policy_symlink_rejected")
if (
parent_info.st_uid != expected_owner_uid
or stat.S_IMODE(parent_info.st_mode) != expected_parent_mode
):
raise PathPolicyError("isolated_writer_path_policy_parent_permissions_rejected")
if (
file_info.st_uid != expected_owner_uid
or stat.S_IMODE(file_info.st_mode) != expected_file_mode
):
raise PathPolicyError("isolated_writer_path_policy_permissions_rejected")
if expected_group_gid is not None and (
parent_info.st_gid != expected_group_gid or file_info.st_gid != expected_group_gid
):
raise PathPolicyError("isolated_writer_path_policy_group_rejected")
if file_info.st_size <= 0 or file_info.st_size > MAX_POLICY_BYTES:
raise PathPolicyError("isolated_writer_path_policy_file_size_rejected")
raw = lexical.read_bytes()
try:
document = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
raise PathPolicyError("isolated_writer_path_policy_json_rejected") from None
canonical = _canonical_json(document)
if raw != canonical:
raise PathPolicyError("isolated_writer_path_policy_not_canonical")
return _parse_policy(document, canonical)
@lru_cache(maxsize=1)
def configured_path_policy() -> ServicePathPolicy | None:
configured = os.getenv("AIOS_GITOPS_PATH_POLICY_FILE", "").strip()
if not configured:
return None
if not Path(configured).is_absolute():
raise PathPolicyError("isolated_writer_path_policy_path_rejected")
try:
gitops_gid = grp.getgrnam("gitops").gr_gid
except KeyError:
raise PathPolicyError("isolated_writer_path_policy_group_rejected") from None
loaded = load_path_policy(Path(configured), expected_group_gid=gitops_gid)
if loaded.audience != os.getenv("AIOS_GITOPS_SERVICE_AUDIENCE", ""):
raise PathPolicyError("isolated_writer_path_policy_audience_rejected")
if loaded.repository_identity != os.getenv("AIOS_GITOPS_REPOSITORY_IDENTITY", ""):
raise PathPolicyError("isolated_writer_path_policy_repository_rejected")
if loaded.allowed_branch != os.getenv("AIOS_GITOPS_ALLOWED_BRANCH", ""):
raise PathPolicyError("isolated_writer_path_policy_branch_rejected")
return loaded
def clear_path_policy_cache() -> None:
configured_path_policy.cache_clear()
def require_service_path(operation: str, path: str) -> OperationPolicy:
loaded = configured_path_policy()
if loaded is None:
raise PathPolicyError("isolated_writer_service_path_policy_required")
return loaded.require_exact_path(operation, path)
def configured_path_policy_sha256() -> str | None:
loaded = configured_path_policy()
return None if loaded is None else loaded.sha256
def validate_content(operation: str, path: str, content: str) -> None:
selected = require_service_path(operation, path)
try:
encoded = content.encode("utf-8", errors="strict")
except (AttributeError, UnicodeEncodeError):
raise PathPolicyError("isolated_writer_systemd_profile_rejected") from None
if len(encoded) > selected.max_content_bytes:
raise PathPolicyError("isolated_writer_service_content_too_large")
if selected.content_profile == "systemd-codex-review-worker-v1":
_validate_systemd_codex_review_worker(content)
return
raise PathPolicyError("isolated_writer_path_policy_profile_rejected")
def _validate_systemd_codex_review_worker(content: str) -> None:
if not isinstance(content, str):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
try:
content.encode("utf-8", errors="strict")
except UnicodeEncodeError:
raise PathPolicyError("isolated_writer_systemd_profile_rejected") from None
if (
"\x00" in content
or "\r" in content
or _SECRET_RE.search(content)
or not content.endswith("\n")
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
sections: set[str] = set()
values: dict[tuple[str, str], list[str]] = {}
section: str | None = None
for line in content.splitlines():
if len(line.encode("utf-8")) > _SYSTEMD_MAX_LINE_BYTES:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
if not line:
continue
if line[0].isspace() or line.startswith(("#", ";")):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
if line.startswith("[") and line.endswith("]"):
candidate = line[1:-1]
if (
not _SYSTEMD_NAME_RE.fullmatch(candidate)
or candidate not in _SYSTEMD_ALLOWED_DIRECTIVES
or candidate in sections
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
sections.add(candidate)
section = candidate
continue
if section is None or "=" not in line:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
directive, value = line.split("=", 1)
if (
not _SYSTEMD_NAME_RE.fullmatch(directive)
or directive not in _SYSTEMD_ALLOWED_DIRECTIVES[section]
or not value
or value != value.strip()
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
key = (section, directive)
if key in values and key not in _SYSTEMD_MULTI_DIRECTIVES:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
values.setdefault(key, []).append(value)
if sections != set(_SYSTEMD_ALLOWED_DIRECTIVES):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
if set(values) != set(_SYSTEMD_EXACT_SINGLE_VALUES) | set(
_SYSTEMD_EXACT_MULTI_VALUES
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
for key, expected in _SYSTEMD_EXACT_SINGLE_VALUES.items():
if values.get(key) != [expected]:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
for key, expected in _SYSTEMD_EXACT_MULTI_VALUES.items():
observed = values.get(key, [])
if len(observed) != len(set(observed)) or tuple(observed) != expected:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
@@ -1,241 +0,0 @@
"""Digest-only, task-scoped token registry for isolated writers."""
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import os
import grp
import pwd
import secrets
import sqlite3
import stat
import threading
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
_REGISTRY_INIT_LOCK = threading.Lock()
def utc_now() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def token_digest(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
class TokenRegistry:
def __init__(self, path: Path | str):
self.path = Path(path)
self._prepare()
def _prepare(self) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
if self.path.parent.is_symlink() or self.path.is_symlink():
raise PermissionError("writer_token_registry_symlink_rejected")
with _REGISTRY_INIT_LOCK:
with self._connect() as db:
db.executescript(
"""
CREATE TABLE IF NOT EXISTS token_grants (
token_id TEXT PRIMARY KEY,
token_digest TEXT NOT NULL UNIQUE,
grant_json TEXT NOT NULL,
revoked INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS token_uses (
token_id TEXT NOT NULL,
operation TEXT NOT NULL,
use_count INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY(token_id, operation),
FOREIGN KEY(token_id) REFERENCES token_grants(token_id)
);
"""
)
os.chmod(self.path, 0o600)
def _connect(self) -> sqlite3.Connection:
db = sqlite3.connect(self.path, timeout=30, isolation_level=None)
db.row_factory = sqlite3.Row
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA synchronous=FULL")
db.execute("PRAGMA foreign_keys=ON")
return db
def issue(self, grant: dict[str, Any], token: str | None = None) -> tuple[str, dict[str, Any]]:
value = token or secrets.token_urlsafe(32)
if len(value.encode("utf-8")) < 32:
raise ValueError("writer_token_entropy_insufficient")
record = dict(grant)
record["schema_version"] = 2
record["token_digest"] = token_digest(value)
record["revoked"] = False
record.setdefault("created_at", utc_now())
required = (
"token_id",
"audience",
"task_id",
"source_cidrs",
"allowed_branch",
"approved_base_oid",
"allowed_operations",
"allowed_paths",
"not_before",
"expires_at",
"max_uses",
"issued_by",
)
if any(not record.get(field) for field in required):
raise ValueError("writer_token_grant_incomplete")
encoded = json.dumps(record, sort_keys=True, separators=(",", ":"))
with self._connect() as db:
db.execute("BEGIN IMMEDIATE")
try:
db.execute(
"""
INSERT INTO token_grants(token_id, token_digest, grant_json, revoked, created_at)
VALUES(?,?,?,?,?)
""",
(
str(record["token_id"]),
str(record["token_digest"]),
encoded,
0,
str(record["created_at"]),
),
)
db.execute("COMMIT")
except Exception:
db.execute("ROLLBACK")
raise
return value, record
def find(self, token: str) -> dict[str, Any] | None:
candidate = token_digest(token)
with self._connect() as db:
rows = db.execute(
"SELECT token_digest, grant_json, revoked FROM token_grants"
).fetchall()
match = None
for row in rows:
if hmac.compare_digest(str(row["token_digest"]), candidate):
match = dict(json.loads(str(row["grant_json"])))
match["revoked"] = bool(row["revoked"])
return match
def consume(self, token_id: str, operation: str, maximum: int) -> int:
if maximum < 1:
raise PermissionError("writer_token_operation_exhausted")
with self._connect() as db:
db.execute("BEGIN IMMEDIATE")
try:
row = db.execute(
"SELECT revoked FROM token_grants WHERE token_id=?",
(token_id,),
).fetchone()
if row is None or bool(row["revoked"]):
raise PermissionError("writer_token_revoked")
used_row = db.execute(
"SELECT use_count FROM token_uses WHERE token_id=? AND operation=?",
(token_id, operation),
).fetchone()
used = int(used_row["use_count"]) if used_row else 0
if used >= maximum:
raise PermissionError("writer_token_operation_exhausted")
current = used + 1
db.execute(
"""
INSERT INTO token_uses(token_id, operation, use_count)
VALUES(?,?,?)
ON CONFLICT(token_id, operation)
DO UPDATE SET use_count=excluded.use_count
""",
(token_id, operation, current),
)
db.execute("COMMIT")
return current
except Exception:
db.execute("ROLLBACK")
raise
def revoke(self, token_id: str) -> bool:
with self._connect() as db:
db.execute("BEGIN IMMEDIATE")
changed = db.execute(
"UPDATE token_grants SET revoked=1 WHERE token_id=? AND revoked=0",
(token_id,),
).rowcount
db.execute("COMMIT")
return bool(changed)
def usage(self, token_id: str, operation: str) -> int:
with self._connect() as db:
row = db.execute(
"SELECT use_count FROM token_uses WHERE token_id=? AND operation=?",
(token_id, operation),
).fetchone()
return int(row["use_count"]) if row else 0
def _write_secret_file(
path: Path,
token: str,
*,
owner: str | None = None,
group: str | None = None,
) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
if path.parent.is_symlink() or path.is_symlink():
raise PermissionError("writer_token_file_symlink_rejected")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
fd = os.open(path, flags, 0o600)
try:
if not stat.S_ISREG(os.fstat(fd).st_mode):
raise PermissionError("writer_token_file_not_regular")
if owner is not None or group is not None:
uid = pwd.getpwnam(owner).pw_uid if owner is not None else -1
gid = grp.getgrnam(group).gr_gid if group is not None else -1
os.fchown(fd, uid, gid)
os.write(fd, token.encode("utf-8"))
os.fsync(fd)
finally:
os.close(fd)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--registry", required=True)
sub = parser.add_subparsers(dest="command", required=True)
issue = sub.add_parser("issue")
issue.add_argument("--grant-file", required=True)
issue.add_argument("--token-file", required=True)
issue.add_argument("--owner")
issue.add_argument("--group")
revoke = sub.add_parser("revoke")
revoke.add_argument("--token-id", required=True)
args = parser.parse_args(argv)
registry = TokenRegistry(args.registry)
if args.command == "issue":
grant = json.loads(Path(args.grant_file).read_text(encoding="utf-8"))
token, record = registry.issue(grant)
_write_secret_file(
Path(args.token_file),
token,
owner=args.owner,
group=args.group,
)
print(json.dumps({"token_id": record["token_id"], "created": True}, sort_keys=True))
return 0
changed = registry.revoke(args.token_id)
print(json.dumps({"token_id": args.token_id, "revoked": changed}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1 @@
"""Isolated, Shadow-attested Codex Task Plane Worker components."""
@@ -0,0 +1,212 @@
"""Shadow-only Codex read/review/test worker lifecycle and CLI."""
from __future__ import annotations
import argparse
import json
import os
import threading
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable
from .result_attestation import TaskResultEnvelope, TrustPolicy, task_result_envelope
from .task_client import CredentialSource, StageAClientConfig, TaskPlaneClient
MAX_CONCURRENCY = 1
HEARTBEAT_INTERVAL_SECONDS = 15
CLAIM_LEASE_SECONDS = 120
SHADOW_ONLY = True
DEFAULT_TRUST_LEVEL = TrustPolicy.SHADOW_ATTESTED
DEFAULT_PRODUCTION_TRUST = False
READONLY_CAPABILITIES = ("code.read", "test.python", "review.security")
class CodexReviewTaskError(ValueError):
pass
@dataclass(frozen=True)
class CodexReviewTaskPolicy:
worker_id: str
identity: str
trust_zone: str = "isolated-review"
max_concurrency: int = MAX_CONCURRENCY
heartbeat_interval_seconds: int = HEARTBEAT_INTERVAL_SECONDS
claim_lease_seconds: int = CLAIM_LEASE_SECONDS
shadow_only: bool = SHADOW_ONLY
def registration_payload(self) -> dict[str, object]:
return {
"worker_id": self.worker_id, "worker_type": "codex-review",
"identity": self.identity, "owner_identity": self.identity,
"capabilities": list(READONLY_CAPABILITIES), "trust_zone": self.trust_zone,
"max_concurrency": self.max_concurrency,
"heartbeat_interval_seconds": self.heartbeat_interval_seconds,
"supported_protocols": ["aios-task-v1"], "registration_policy": "self",
}
def assert_shadow_only_task(self, task: dict[str, Any]) -> None:
plan = task.get("plan", task)
metadata = plan.get("metadata", {}) if isinstance(plan, dict) else {}
required = set(plan.get("required_capabilities", [])) if isinstance(plan, dict) else set()
if self.max_concurrency != 1 or self.heartbeat_interval_seconds != 15 or self.claim_lease_seconds != 120:
raise CodexReviewTaskError("worker timing/concurrency contract rejected")
if not isinstance(plan, dict) or metadata.get("production_side_effects") is not False:
raise CodexReviewTaskError("task must declare production_side_effects=false")
if plan.get("risk") != "low" or plan.get("task_type") not in {"code.review", "test.execute", None}:
raise CodexReviewTaskError("task risk or type rejected")
if plan.get("data_classification", "internal") not in {"public", "internal"}:
raise CodexReviewTaskError("task classification rejected")
if not required.issubset(set(READONLY_CAPABILITIES)):
raise CodexReviewTaskError("task capability rejected")
if any(value.startswith(("code.write", "deploy", "secret", "production")) for value in required):
raise CodexReviewTaskError("side-effect capability rejected")
def default_result_envelope(self, *, task_id: str, claim_id: str, worker_identity: str) -> TaskResultEnvelope:
return task_result_envelope(
task_id=task_id, claim_id=claim_id, worker_id=self.worker_id,
worker_identity=worker_identity, status="CREATED", outputs=[], evidence=[],
started_at="1970-01-01T00:00:00Z", completed_at="1970-01-01T00:00:00Z",
)
class _LeaseGuard:
def __init__(self, client: TaskPlaneClient, task_id: str, claim_id: str) -> None:
self.client, self.task_id, self.claim_id = client, task_id, claim_id
self.stop_event = threading.Event()
self.lost = threading.Event()
self.thread = threading.Thread(target=self._run, daemon=True)
def _run(self) -> None:
while not self.stop_event.wait(HEARTBEAT_INTERVAL_SECONDS):
try:
self.client.claim_heartbeat(self.task_id, claim_id=self.claim_id, lease_seconds=CLAIM_LEASE_SECONDS)
except Exception:
self.lost.set()
self.stop_event.set()
def __enter__(self) -> "_LeaseGuard":
self.thread.start()
return self
def __exit__(self, *_: object) -> None:
self.stop_event.set()
self.thread.join(timeout=2)
def require_active(self) -> None:
if self.lost.is_set():
raise CodexReviewTaskError("claim lease lost")
class CodexReviewWorker:
def __init__(self, client: TaskPlaneClient, policy: CodexReviewTaskPolicy, source_commit: str) -> None:
self.client, self.policy, self.source_commit = client, policy, source_commit
def register(self) -> dict[str, Any]:
return self.client.register_worker(**{
"worker_id": self.policy.worker_id, "worker_type": "codex-review",
"identity": self.policy.identity, "capabilities": READONLY_CAPABILITIES,
"trust_zone": self.policy.trust_zone, "max_concurrency": 1,
"heartbeat_interval_seconds": 15,
})
def poll_safe_task(self) -> dict[str, Any] | None:
for task in self.client.list_tasks():
if task.get("status") not in {"CREATED", "QUEUED", "FAILED_RECOVERABLE"}:
continue
try:
full = self.client.get_task(str(task["task_id"]))
self.policy.assert_shadow_only_task(full)
return full
except (KeyError, CodexReviewTaskError):
continue
return None
def run_once(self, executor: Callable[[dict[str, Any], Callable[[float, str], None]], tuple[str, list[dict[str, Any]], list[dict[str, Any]]]]) -> str | None:
self.register()
task = self.poll_safe_task()
if task is None:
return None
task_id = str(task["task_id"])
claim = self.client.claim(task_id, worker_id=self.policy.worker_id, lease_seconds=120)
claim_id = str(claim["claim_id"])
started = datetime.now(timezone.utc).isoformat()
with _LeaseGuard(self.client, task_id, claim_id) as guard:
def emit(value: float, stage: str) -> None:
guard.require_active()
self.client.progress(task_id, claim_id=claim_id, progress=value, stage=stage)
summary, outputs, evidence = executor(task, emit)
guard.require_active()
completed = datetime.now(timezone.utc).isoformat()
envelope = task_result_envelope(
task_id=task_id, claim_id=claim_id,
task_revision=int(claim["task_revision"]),
worker_id=self.policy.worker_id, worker_identity=self.policy.identity,
operation_digest=str(claim["operation_digest"]),
source_commit=self.source_commit, status="COMPLETED", summary=summary,
outputs=outputs, evidence=evidence, started_at=started, completed_at=completed,
)
guard.require_active()
envelope_payload = envelope.to_dict()
self.client.result(
task_id,
claim_id=claim_id,
status=str(envelope_payload["status"]),
outputs=list(envelope_payload["outputs"]),
evidence=list(envelope_payload["evidence"]),
started_at=str(envelope_payload["started_at"]),
completed_at=str(envelope_payload["completed_at"]),
trust_level=TrustPolicy.SHADOW_ATTESTED.value,
production_trust=False,
)
return task_id
def build_default_task_policy(worker_id: str, worker_identity: str) -> CodexReviewTaskPolicy:
return CodexReviewTaskPolicy(worker_id=worker_id, identity=worker_identity)
def make_shadow_result_payload(*, task_id: str, claim_id: str, worker_identity: str) -> TaskResultEnvelope:
return task_result_envelope(
task_id=task_id, claim_id=claim_id, worker_identity=worker_identity,
status="COMPLETED", outputs=[], evidence=[],
started_at="1970-01-01T00:00:00Z", completed_at="1970-01-01T00:00:00Z",
)
def _assert_disabled_production() -> None:
for name in ("AIOS_GITOPS_WRITES_ENABLED", "AIOS_SILENT_RELEASE_EXECUTION_ENABLED", "AIOS_TASK_PLANE_EXECUTION_ENABLED"):
if os.environ.get(name, "false").lower() != "false":
raise CodexReviewTaskError(f"{name} must remain false")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--self-check", action="store_true")
parser.add_argument("--once", action="store_true")
args = parser.parse_args(argv)
_assert_disabled_production()
config = StageAClientConfig(os.environ.get("AIOS_TASK_PLANE_ENDPOINT", "http://127.0.0.1:18923"))
if args.self_check:
print(json.dumps({"status": "PASS", "shadow_only": True, "production_trust": False}, sort_keys=True))
return 0
if os.environ.get("AIOS_CODEX_WORKER_SHADOW_CANARY_ENABLED", "false").lower() != "true":
raise SystemExit("Codex worker execution is disabled")
credential = CredentialSource(Path(os.environ["AIOS_TASK_PLANE_WORKER_TOKEN_FILE"])).read_token()
source_commit = os.environ.get("AIOS_CODEX_WORKER_SOURCE_COMMIT", "")
policy = CodexReviewTaskPolicy(
worker_id=os.environ["AIOS_CODEX_WORKER_ID"],
identity=os.environ["AIOS_CODEX_WORKER_IDENTITY"],
)
worker = CodexReviewWorker(TaskPlaneClient(config, credential), policy, source_commit)
if args.once:
worker.run_once(lambda _task, emit: (emit(100, "VERIFIED") or ("shadow self-check", [], [])))
return 0
raise SystemExit("continuous real worker execution is not enabled")
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,62 @@
"""Fail-closed command and environment policy for the isolated Codex Worker."""
from __future__ import annotations
import os
import re
import subprocess
from pathlib import Path
OID_RE = re.compile(r"[0-9a-f]{40}")
DENIED_ENV = ("TOKEN", "PASSWORD", "SECRET", "CREDENTIAL", "AUTHORIZATION", "COOKIE", "PRIVATE_KEY", "AWS_", "GITHUB_", "GITEA_")
class CommandPolicyError(ValueError):
pass
def isolated_environment(home: Path, temp_root: Path, pythonpath: str) -> dict[str, str]:
home.mkdir(mode=0o700, parents=True, exist_ok=True)
temp_root.mkdir(mode=0o700, parents=True, exist_ok=True)
allowed = {key: value for key, value in os.environ.items() if key in {"PATH", "LANG", "LC_ALL"}}
allowed.update({"HOME": str(home), "TMPDIR": str(temp_root), "PYTHONPATH": pythonpath, "PYTHONDONTWRITEBYTECODE": "1"})
if any(any(marker in key.upper() for marker in DENIED_ENV) for key in allowed):
raise CommandPolicyError("secret environment retained")
return allowed
class CommandPolicy:
def __init__(self, repo: Path, result_root: Path, *, timeout_seconds: int = 300, output_limit: int = 1_000_000):
self.repo = repo.resolve()
self.result_root = result_root.resolve()
self.timeout_seconds = timeout_seconds
self.output_limit = output_limit
def _oid(self, value: str) -> str:
if not OID_RE.fullmatch(value):
raise CommandPolicyError("exact commit oid required")
return value
def build(self, kind: str, *, oid: str, paths: list[str] | None = None, output: Path | None = None) -> list[str]:
oid = self._oid(oid)
paths = paths or []
if any(not path or path.startswith("-") or Path(path).is_absolute() or ".." in Path(path).parts for path in paths):
raise CommandPolicyError("invalid allowlisted path")
if kind == "diff-check":
return ["git", "-C", str(self.repo), "diff", "--check"]
if kind == "git-show" and len(paths) == 1:
return ["git", "-C", str(self.repo), "show", f"{oid}:{paths[0]}"]
if kind == "pytest" and paths and all(path.startswith("tests/task_plane/") or path == "tests/test_isolated_writer_policy.py" for path in paths):
return ["python3", "-m", "pytest", "-q", *paths]
if kind == "compileall" and paths and all(path.startswith(("active-runtime/app/task_plane", "runtime/agent-os")) for path in paths):
return ["python3", "-m", "compileall", "-q", *paths]
if kind == "acceptance" and output is not None:
target = output.resolve()
if target.parent != self.result_root:
raise CommandPolicyError("output escapes result root")
return ["bash", "scripts/task-plane/run_task_plane_acceptance.sh", str(target)]
raise CommandPolicyError("command template not allowlisted")
def run(self, argv: list[str], env: dict[str, str]) -> tuple[int, str]:
completed = subprocess.run(argv, cwd=self.repo, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=self.timeout_seconds, start_new_session=True, check=False)
output = completed.stdout[: self.output_limit]
return completed.returncode, output
@@ -0,0 +1,56 @@
"""Bounded progress payload helpers."""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class ProgressEnvelope:
claim_id: str
progress: int | float
stage: str
lease_seconds: int = 120
message: str | None = None
def validate(self) -> None:
if not self.claim_id:
raise ValueError("claim_id is required")
numeric = float(self.progress)
if not math.isfinite(numeric) or not 0 <= numeric <= 100:
raise ValueError("progress must be finite and within [0, 100]")
if not 1 <= self.lease_seconds <= 3600:
raise ValueError("lease_seconds out of range")
if not self.stage or len(self.stage) > 64 or any(ord(ch) < 32 for ch in self.stage):
raise ValueError("stage is invalid")
if self.message is not None and len(self.message) > 512:
raise ValueError("message is too long")
def as_request(self) -> dict[str, Any]:
self.validate()
return {
"claim_id": self.claim_id, "progress": float(self.progress),
"stage": self.stage, "lease_seconds": self.lease_seconds,
"message": self.message,
}
def clamp_progress(value: float) -> float:
numeric = float(value)
if not math.isfinite(numeric):
raise ValueError("progress must be finite")
return max(0.0, min(100.0, numeric))
def safe_stage_name(stage: str | None) -> str:
value = str(stage or "").strip()
if not value:
return "RUNNING"
if any(ord(ch) < 32 for ch in value):
raise ValueError("stage is invalid")
return value[:64]
def build_progress_payload(*, claim_id: str, progress: float, stage: str = "RUNNING", lease_seconds: int = 120, message: str | None = None) -> dict[str, Any]:
return ProgressEnvelope(claim_id, clamp_progress(progress), safe_stage_name(stage), lease_seconds, message).as_request()
@@ -0,0 +1,157 @@
"""Canonical, Shadow-only result envelopes for the Codex review worker."""
from __future__ import annotations
import hashlib
import hmac
import json
import re
from dataclasses import dataclass
from enum import StrEnum
from typing import Any
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
class TrustPolicy(StrEnum):
SHADOW_ATTESTED = "SHADOW_ATTESTED"
SHADOW_UNATTESTED = "SHADOW_UNATTESTED"
def _canonical_sha256(payload: object) -> str:
encoded = json.dumps(
payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
@dataclass(frozen=True)
class TaskResultEnvelope:
task_id: str
claim_id: str
worker_identity: str
status: str
outputs: list[dict[str, Any]]
evidence: list[dict[str, Any]]
started_at: str
completed_at: str
task_revision: int = 1
worker_id: str = ""
operation_digest: str = ""
source_commit: str = "0" * 40
summary: str = ""
evidence_digests: tuple[str, ...] = ()
result_digest: str | None = None
trust_level: TrustPolicy = TrustPolicy.SHADOW_ATTESTED
production_trust: bool = False
signature: str | None = None
attestation_ref: str | None = None
def digest_payload(self) -> dict[str, Any]:
return {
"task_id": self.task_id,
"claim_id": self.claim_id,
"task_revision": self.task_revision,
"worker_id": self.worker_id,
"worker_identity": self.worker_identity,
"operation_digest": self.operation_digest,
"source_commit": self.source_commit,
"status": self.status,
"summary": self.summary,
"outputs": self.outputs,
"evidence": self.evidence,
"evidence_digests": list(self.evidence_digests),
"started_at": self.started_at,
"completed_at": self.completed_at,
"trust_level": TrustPolicy.SHADOW_ATTESTED.value,
"production_trust": False,
"attestation_ref": self.attestation_ref,
}
def computed_digest(self) -> str:
return _canonical_sha256(self.digest_payload())
def validate(self) -> None:
required = {
"task_id": self.task_id,
"claim_id": self.claim_id,
"worker_id": self.worker_id,
"worker_identity": self.worker_identity,
"operation_digest": self.operation_digest,
"status": self.status,
"started_at": self.started_at,
"completed_at": self.completed_at,
}
if any(not str(value).strip() for value in required.values()):
raise ValueError("result envelope required field missing")
if self.task_revision < 1:
raise ValueError("task_revision must be positive")
if not OID_RE.fullmatch(self.source_commit):
raise ValueError("source_commit must be a full commit OID")
if not SHA256_RE.fullmatch(self.operation_digest):
raise ValueError("operation_digest must be SHA-256")
if any(not SHA256_RE.fullmatch(value) for value in self.evidence_digests):
raise ValueError("evidence digest must be SHA-256")
observed = tuple(_canonical_sha256(item) for item in self.evidence)
if observed != self.evidence_digests:
raise ValueError("evidence digest binding mismatch")
if self.trust_level is not TrustPolicy.SHADOW_ATTESTED:
raise ValueError("Codex review results must remain SHADOW_ATTESTED")
if self.production_trust is not False:
raise ValueError("production_trust is forbidden")
if self.result_digest is not None and not hmac.compare_digest(
self.result_digest, self.computed_digest()
):
raise ValueError("result digest mismatch")
def finalized(self) -> "TaskResultEnvelope":
from dataclasses import replace
envelope = replace(self, result_digest=self.computed_digest())
envelope.validate()
return envelope
def to_dict(self) -> dict[str, Any]:
envelope = self if self.result_digest is not None else self.finalized()
envelope.validate()
return {**envelope.digest_payload(), "result_digest": envelope.result_digest}
def task_result_envelope(
*,
task_id: str,
claim_id: str,
worker_identity: str,
status: str,
outputs: list[dict[str, Any]],
evidence: list[dict[str, Any]],
started_at: str,
completed_at: str,
task_revision: int = 1,
worker_id: str | None = None,
operation_digest: str | None = None,
source_commit: str = "0" * 40,
summary: str = "",
trust_level: TrustPolicy = TrustPolicy.SHADOW_ATTESTED,
production_trust: bool = False,
) -> TaskResultEnvelope:
evidence_digests = tuple(_canonical_sha256(item) for item in evidence)
envelope = TaskResultEnvelope(
task_id=task_id,
claim_id=claim_id,
task_revision=task_revision,
worker_id=worker_id or worker_identity,
worker_identity=worker_identity,
operation_digest=operation_digest or ("0" * 64),
source_commit=source_commit,
status=status,
summary=summary,
outputs=outputs,
evidence=evidence,
evidence_digests=evidence_digests,
started_at=started_at,
completed_at=completed_at,
trust_level=trust_level,
production_trust=production_trust,
)
return envelope.finalized()
@@ -0,0 +1,138 @@
"""Fixed-command, no-shell runner for a read-only Codex workspace."""
from __future__ import annotations
import os
import re
import signal
import subprocess
from dataclasses import dataclass
from pathlib import Path, PurePosixPath
from .command_policy import CommandPolicyError
from .workspace import WorkspacePermissionError, assert_private_path
OID_RE = re.compile(r"^[0-9a-f]{40}$")
FORBIDDEN_SNIPPETS = ("http://", "https://", ";", "&&", "||", "|", "`", "${", "$(")
class CommandRunnerError(ValueError):
pass
class CommandDisallowedError(CommandRunnerError):
pass
class CommandPolicyViolation(CommandRunnerError):
pass
@dataclass(frozen=True)
class CommandResult:
returncode: int
output: str
class SafeCommandRunner:
allowed_kinds = ("git-diff", "diff-check", "git-show", "pytest", "compileall", "acceptance")
def __init__(self, repo: Path, result_root: Path, *, policy=None, timeout_seconds: int = 300, output_limit: int = 1_000_000) -> None:
self.repo = assert_private_path(repo)
self.result_root = assert_private_path(result_root)
self.timeout_seconds, self.output_limit = timeout_seconds, output_limit
@staticmethod
def _oid(value: str) -> str:
if not OID_RE.fullmatch(str(value)):
raise CommandPolicyError("exact commit oid required")
return value
@staticmethod
def _forbidden(value: str) -> bool:
lowered = value.lower()
return (
re.search(r"\b(?:bash|eval)\b", lowered) is not None
or re.search(r"(^|\s)-c(\s|$)", lowered) is not None
or any(marker in value for marker in FORBIDDEN_SNIPPETS)
)
def _path(self, value: str, *, allowed_prefixes: tuple[str, ...]) -> str:
if not value or value.startswith("-") or "\\" in value or self._forbidden(value):
raise CommandDisallowedError("unsafe path input")
pure = PurePosixPath(value)
if pure.is_absolute() or any(part in {"", ".", ".."} for part in pure.parts):
raise CommandPolicyError("path traversal is forbidden")
if not any(value == prefix or value.startswith(prefix + "/") for prefix in allowed_prefixes):
raise CommandPolicyError("path outside allowlist")
try:
candidate = assert_private_path(self.repo / value)
except WorkspacePermissionError as exc:
raise CommandPolicyError("symlink path is forbidden") from exc
try:
candidate.relative_to(self.repo)
except ValueError:
raise CommandPolicyError("path escapes repository") from None
return value
def _output(self, output: Path) -> Path:
if Path(output).is_absolute() or Path(output).name != str(output):
raise CommandPolicyError("output must be a direct result file")
target = assert_private_path(self.result_root / output)
try:
target.relative_to(self.result_root)
except ValueError:
raise CommandPolicyError("output escapes result root") from None
return target
def build(self, kind: str, *, oid: str, other_oid: str | None = None, paths: list[str] | None = None, output: Path | None = None) -> list[str]:
if kind not in self.allowed_kinds:
raise CommandDisallowedError("command kind not allowed")
first = self._oid(oid)
values = paths or []
if kind == "git-diff":
second = self._oid(str(other_oid or ""))
return ["git", "-C", str(self.repo), "diff", "--no-ext-diff", "--no-textconv", first, second]
if kind == "diff-check":
return ["git", "-C", str(self.repo), "diff", "--check"]
if kind == "git-show" and len(values) == 1:
path = self._path(values[0], allowed_prefixes=("active-runtime", "runtime", "scripts", "tests", "docs"))
return ["git", "-C", str(self.repo), "show", f"{first}:{path}"]
if kind == "pytest" and values:
safe = [self._path(value, allowed_prefixes=("tests/task_plane", "tests/test_isolated_writer_policy.py")) for value in values]
return ["python3", "-m", "pytest", "-q", *safe]
if kind == "compileall" and values:
safe = [self._path(value, allowed_prefixes=("active-runtime/app/task_plane", "runtime/agent-os")) for value in values]
return ["python3", "-m", "compileall", "-q", *safe]
if kind == "acceptance" and output is not None and not values:
return ["bash", "scripts/task-plane/run_task_plane_acceptance.sh", str(self._output(output))]
raise CommandPolicyError("command template not allowlisted")
def run(self, kind: str, *, oid: str, other_oid: str | None = None, paths: list[str] | None = None, output: Path | None = None, env: dict[str, str] | None = None) -> CommandResult:
if env and any(marker in key.upper() for key in env for marker in ("TOKEN", "SECRET", "PASSWORD", "CREDENTIAL", "AUTHORIZATION", "COOKIE", "PRIVATE_KEY")):
raise CommandPolicyViolation("secret environment rejected")
command = self.build(kind, oid=oid, other_oid=other_oid, paths=paths, output=output)
safe_env = {
"HOME": str(self.result_root),
"PATH": os.environ.get("PATH", ""),
"LANG": "C.UTF-8",
"PYTHONDONTWRITEBYTECODE": "1",
"GIT_CONFIG_NOSYSTEM": "1",
"GIT_TERMINAL_PROMPT": "0",
}
process = subprocess.Popen(
command, cwd=self.repo, env=safe_env, stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
start_new_session=True,
)
try:
text, _ = process.communicate(timeout=self.timeout_seconds)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
text, _ = process.communicate()
raise CommandRunnerError("command timeout")
return CommandResult(process.returncode, text[: self.output_limit])
Runner = SafeCommandRunner
FixedCommandRunner = SafeCommandRunner
CommandResultEnvelope = CommandResult
@@ -0,0 +1,300 @@
"""Fail-closed loopback HTTP client for the Codex review worker."""
from __future__ import annotations
import hashlib
import json
import os
import re
import stat
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable
MAX_HTTP_BODY_BYTES = 1_000_000
_RETRYABLE_STATUS = {429, 503}
_RETRY_BACKOFF_SECONDS = (0.25, 0.5, 1.0, 2.0)
_IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$")
class TaskClientError(RuntimeError):
pass
class TaskClientProtocolError(TaskClientError):
pass
class TaskClientAuthError(TaskClientError):
pass
class TaskClientResponseTooLargeError(TaskClientError):
pass
def _safe_identifier(value: str, name: str) -> str:
candidate = str(value or "")
if not _IDENTIFIER_RE.fullmatch(candidate):
raise TaskClientProtocolError(f"invalid {name}")
return urllib.parse.quote(candidate, safe="")
def _stable_key(operation: str, payload: object) -> str:
encoded = json.dumps(
payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
return f"codex-worker:{operation}:{hashlib.sha256(encoded).hexdigest()}"
@dataclass(frozen=True)
class CredentialSource:
token_path: Path
token_owner: str = "aiman"
token_group: str = "gitops"
expected_mode: int = 0o600
def read_token(self) -> str:
import grp
import pwd
path = Path(self.token_path)
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
fd = os.open(path, flags)
except OSError as exc:
raise TaskClientAuthError("credential file unavailable") from exc
try:
info = os.fstat(fd)
if not stat.S_ISREG(info.st_mode):
raise TaskClientAuthError("credential file must be regular")
if stat.S_IMODE(info.st_mode) != self.expected_mode:
raise TaskClientAuthError("credential file mode rejected")
try:
owner = pwd.getpwuid(info.st_uid).pw_name
group = grp.getgrgid(info.st_gid).gr_name
except KeyError as exc:
raise TaskClientAuthError("credential ownership unresolved") from exc
if owner != self.token_owner or group != self.token_group:
raise TaskClientAuthError("credential ownership rejected")
value = os.read(fd, 4097)
if len(value) > 4096:
raise TaskClientAuthError("credential file too large")
token = value.decode("utf-8", errors="strict").strip()
if len(token.encode("utf-8")) < 32:
raise TaskClientAuthError("credential unavailable")
return token
finally:
os.close(fd)
@dataclass(frozen=True)
class StageAClientConfig:
base_url: str = "http://127.0.0.1:18923"
timeout_seconds: float = 12.0
max_request_bytes: int = MAX_HTTP_BODY_BYTES
max_response_bytes: int = MAX_HTTP_BODY_BYTES
max_retry_attempts: int = 4
default_lease_seconds: int = 120
default_worker_heartbeat_interval_seconds: int = 15
default_max_concurrency: int = 1
def __post_init__(self) -> None:
parsed = urllib.parse.urlsplit(self.base_url)
if (
parsed.scheme != "http"
or parsed.hostname not in {"127.0.0.1", "::1"}
or parsed.username is not None
or parsed.password is not None
or parsed.query
or parsed.fragment
or parsed.path not in {"", "/"}
or parsed.port is None
):
raise ValueError("Task Plane endpoint must be explicit loopback HTTP")
class TaskPlaneClient:
def __init__(self, config: StageAClientConfig, token: str) -> None:
if len(str(token).encode("utf-8")) < 16:
raise TaskClientAuthError("credential is required")
self.config = config
self.__token = str(token)
def __repr__(self) -> str:
return f"TaskPlaneClient(base_url={self.config.base_url!r}, credential=<redacted>)"
def _encode(self, payload: dict[str, Any] | None) -> bytes | None:
if payload is None:
return None
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
if len(body) > self.config.max_request_bytes:
raise TaskClientResponseTooLargeError("request payload exceeds configured limit")
return body
def _read_response(self, handle: Any) -> tuple[int, Any]:
status_code = int(getattr(handle, "status", 200))
raw = handle.read(self.config.max_response_bytes + 1)
if len(raw) > self.config.max_response_bytes:
raise TaskClientResponseTooLargeError("response payload exceeds configured limit")
if not raw:
return status_code, {}
try:
return status_code, json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise TaskClientProtocolError("response is not valid JSON") from exc
def _request(
self,
method: str,
path: str,
payload: dict[str, Any] | None = None,
*,
idempotency_key: str | None = None,
) -> tuple[int, Any]:
if not path.startswith("/") or "//" in path or "?" in path or "#" in path:
raise TaskClientProtocolError("invalid API path")
body = self._encode(payload)
attempts = max(1, min(self.config.max_retry_attempts, 4))
for index in range(attempts):
headers = {"Authorization": f"Bearer {self.__token}", "Accept": "application/json"}
if body is not None:
headers["Content-Type"] = "application/json"
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
request = urllib.request.Request(
f"{self.config.base_url.rstrip('/')}{path}",
method=method,
data=body,
headers=headers,
)
try:
with urllib.request.urlopen(request, timeout=self.config.timeout_seconds) as response:
return self._read_response(response)
except urllib.error.HTTPError as exc:
status_code, response = self._read_response(exc)
if status_code in {401, 403}:
raise TaskClientAuthError(f"request rejected: HTTP {status_code}")
if status_code in _RETRYABLE_STATUS and index + 1 < attempts:
time.sleep(_RETRY_BACKOFF_SECONDS[index])
continue
code = response.get("error") if isinstance(response, dict) else "request_failed"
raise TaskClientProtocolError(f"request failed: HTTP {status_code} ({code})")
except urllib.error.URLError as exc:
if index + 1 < attempts:
time.sleep(_RETRY_BACKOFF_SECONDS[index])
continue
raise TaskClientProtocolError("request transport error") from exc
raise TaskClientProtocolError("request failed")
def _post(self, path: str, payload: dict[str, Any], *, idempotency_key: str | None = None) -> dict[str, Any]:
status_code, response = self._request(
"POST", path, payload, idempotency_key=idempotency_key or _stable_key(path, payload)
)
if status_code not in {200, 201} or not isinstance(response, dict):
raise TaskClientProtocolError("unexpected API response")
return response
def _get(self, path: str) -> Any:
status_code, response = self._request("GET", path)
if status_code != 200:
raise TaskClientProtocolError("unexpected API response")
return response
def list_tasks(self) -> list[dict[str, Any]]:
response = self._get("/tasks")
if not isinstance(response, list) or any(not isinstance(item, dict) for item in response):
raise TaskClientProtocolError("invalid task list")
return response
def get_task(self, task_id: str) -> dict[str, Any]:
response = self._get(f"/tasks/{_safe_identifier(task_id, 'task_id')}")
if not isinstance(response, dict):
raise TaskClientProtocolError("invalid task response")
return response
def register_worker(
self, *, worker_id: str, worker_type: str, identity: str,
capabilities: Iterable[str], trust_zone: str, max_concurrency: int,
heartbeat_interval_seconds: int, supported_protocols: Iterable[str] = ("aios-task-v1",),
) -> dict[str, Any]:
payload = {
"worker_id": worker_id, "worker_type": worker_type, "identity": identity,
"capabilities": sorted(set(capabilities)), "trust_zone": trust_zone,
"max_concurrency": max_concurrency,
"heartbeat_interval_seconds": heartbeat_interval_seconds,
"supported_protocols": list(supported_protocols),
"owner_identity": identity, "registration_policy": "self",
}
return self._post("/workers/register", payload, idempotency_key=_stable_key("register", payload))
def worker_heartbeat(self, worker_id: str, *, heartbeat_interval_seconds: int | None = None) -> dict[str, Any]:
payload = {} if heartbeat_interval_seconds is None else {"heartbeat_interval_seconds": heartbeat_interval_seconds}
return self._post(f"/workers/{_safe_identifier(worker_id, 'worker_id')}/heartbeat", payload)
def claim(self, task_id: str, *, worker_id: str, lease_seconds: int | None = None) -> dict[str, Any]:
payload = {"worker_id": worker_id, "lease_seconds": lease_seconds or self.config.default_lease_seconds}
return self._post(f"/tasks/{_safe_identifier(task_id, 'task_id')}/claim", payload)
def claim_heartbeat(self, task_id: str, *, claim_id: str, lease_seconds: int | None = None) -> dict[str, Any]:
if not claim_id:
raise TaskClientProtocolError("claim_id is required")
payload = {"claim_id": claim_id, "lease_seconds": lease_seconds or self.config.default_lease_seconds}
return self._post(f"/tasks/{_safe_identifier(task_id, 'task_id')}/heartbeat", payload)
def progress(self, task_id: str, *, claim_id: str, progress: int | float, stage: str, message: str | None = None, lease_seconds: int | None = None) -> dict[str, Any]:
if not claim_id:
raise TaskClientProtocolError("claim_id is required for progress")
payload = {"claim_id": claim_id, "progress": progress, "stage": stage, "message": message, "lease_seconds": lease_seconds or self.config.default_lease_seconds}
return self._post(f"/tasks/{_safe_identifier(task_id, 'task_id')}/progress", payload)
def result(self, task_id: str, *, claim_id: str, status: str, outputs: list[dict[str, Any]], evidence: list[dict[str, Any]], started_at: str, completed_at: str, result_digest: str | None = None, operation_digest: str | None = None, trust_level: str | None = None, production_trust: bool | None = None, **_: Any) -> dict[str, Any]:
if not claim_id:
raise TaskClientProtocolError("claim_id is required for result submission")
if production_trust not in {None, False}:
raise TaskClientProtocolError("production_trust is forbidden")
payload = {"claim_id": claim_id, "status": status, "outputs": outputs, "evidence": evidence, "started_at": started_at, "completed_at": completed_at}
if result_digest:
payload["result_digest"] = result_digest
if operation_digest:
payload["operation_digest"] = operation_digest
if trust_level:
payload["trust_level"] = trust_level
payload["production_trust"] = False
return self._post(f"/tasks/{_safe_identifier(task_id, 'task_id')}/result", payload)
def evidence(self, task_id: str, *, claim_id: str, evidence: list[dict[str, Any]], status: str = "VERIFYING", started_at: str, completed_at: str) -> dict[str, Any]:
return self.result(task_id, claim_id=claim_id, status=status, outputs=[], evidence=evidence, started_at=started_at, completed_at=completed_at)
def fail(self, task_id: str, *, claim_id: str, reason: str = "failed", recoverable: bool = False) -> dict[str, Any]:
if not claim_id:
raise TaskClientProtocolError("claim_id is required for fail")
return self._post(f"/tasks/{_safe_identifier(task_id, 'task_id')}/fail", {"claim_id": claim_id, "reason": reason, "recoverable": recoverable})
@dataclass(frozen=True)
class WorkerConfig:
worker_id: str
worker_type: str
identity: str
trust_zone: str
max_concurrency: int = 1
heartbeat_interval_seconds: int = 15
claim_lease_seconds: int = 120
supported_protocols: tuple[str, ...] = ("aios-task-v1",)
capabilities: tuple[str, ...] = ("code.read", "test.python", "review.security")
def to_registration_payload(self) -> dict[str, Any]:
return {
"worker_id": self.worker_id, "worker_type": self.worker_type,
"identity": self.identity, "capabilities": list(self.capabilities),
"trust_zone": self.trust_zone, "max_concurrency": self.max_concurrency,
"heartbeat_interval_seconds": self.heartbeat_interval_seconds,
"supported_protocols": list(self.supported_protocols),
"owner_identity": self.identity, "registration_policy": "self",
}
@@ -0,0 +1,162 @@
"""Private, symlink-safe per-claim workspaces."""
from __future__ import annotations
import os
import shutil
import stat
from dataclasses import dataclass
from pathlib import Path
from .command_policy import isolated_environment
WORKSPACE_DIR_MODE = 0o700
RESULT_FILE_MODE = 0o600
class WorkspaceError(RuntimeError):
pass
class WorkspacePermissionError(WorkspaceError):
pass
class WorkspaceCleanupError(WorkspaceError):
pass
def _lexical(path: Path) -> Path:
return Path(os.path.abspath(os.fspath(path)))
def assert_private_path(path: Path, *, expected_mode: int = WORKSPACE_DIR_MODE) -> Path:
"""Reject every existing symlink before resolving the final path."""
lexical = _lexical(Path(path))
current = Path(lexical.anchor)
for component in lexical.parts[1:]:
current = current / component
try:
info = current.lstat()
except FileNotFoundError:
continue
if stat.S_ISLNK(info.st_mode):
raise WorkspacePermissionError("symlink path is forbidden")
return lexical
def _ensure_directory(path: Path, *, mode: int = WORKSPACE_DIR_MODE) -> None:
lexical = assert_private_path(path)
lexical.mkdir(mode=mode, parents=True, exist_ok=True)
if lexical.is_symlink() or not lexical.is_dir():
raise WorkspacePermissionError("workspace directory rejected")
os.chmod(lexical, mode)
if stat.S_IMODE(lexical.stat().st_mode) != mode:
raise WorkspacePermissionError("workspace directory mode rejected")
def _ensure_result_name(name: str) -> str:
safe = str(name).strip()
if not safe or safe in {".", ".."} or "/" in safe or "\\" in safe or "\0" in safe:
raise WorkspaceError("invalid result file name")
return safe
def _within(path: Path, base: Path) -> Path:
lexical = assert_private_path(path)
root = assert_private_path(base)
try:
lexical.relative_to(root)
except ValueError:
raise WorkspacePermissionError("path escapes workspace") from None
return lexical
def _copy_readonly_tree(source: Path, destination: Path) -> None:
source = assert_private_path(source)
if not source.is_dir():
raise WorkspaceError("source archive tree unavailable")
for item in source.rglob("*"):
if item.is_symlink():
raise WorkspacePermissionError("source archive symlink rejected")
shutil.copytree(source, destination, dirs_exist_ok=True, symlinks=False)
for item in destination.rglob("*"):
if item.is_file():
os.chmod(item, 0o400)
elif item.is_dir():
os.chmod(item, 0o500)
@dataclass(frozen=True)
class WorkspaceLayout:
workspace_root: Path
repo_root: Path
result_root: Path
home: Path
tmp: Path
@dataclass
class TaskWorkspace:
layout: WorkspaceLayout
task_id: str
claim_id: str
def __init__(self, root: Path, task_id: str, claim_id: str, *, repo_subdir: str = "repo") -> None:
workspace_root = assert_private_path(root)
_ensure_directory(workspace_root)
self.layout = WorkspaceLayout(
workspace_root, workspace_root / repo_subdir, workspace_root / "results",
workspace_root / "home", workspace_root / "tmp",
)
for path in (self.layout.repo_root, self.layout.result_root, self.layout.home, self.layout.tmp):
_ensure_directory(path)
self.task_id, self.claim_id = task_id, claim_id
@classmethod
def create(cls, root: Path, task_id: str, claim_id: str, *, repo_subdir: str = "repo") -> "TaskWorkspace":
return cls(root, task_id, claim_id, repo_subdir=repo_subdir)
def __enter__(self) -> "TaskWorkspace":
return self
def __exit__(self, *_: object) -> None:
self.cleanup()
def materialize_readonly_archive(self, source: Path) -> None:
if any(self.layout.repo_root.iterdir()):
raise WorkspaceError("workspace repository is not empty")
_copy_readonly_tree(source, self.layout.repo_root)
def environ(self) -> dict[str, str]:
return isolated_environment(self.layout.home, self.layout.tmp, "active-runtime")
def result_path(self, name: str) -> Path:
return _within(self.layout.result_root / _ensure_result_name(name), self.layout.result_root)
def write_result(self, *, name: str, payload: str) -> Path:
path = self.result_path(name)
flags = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
fd = os.open(path, flags, RESULT_FILE_MODE)
try:
if not stat.S_ISREG(os.fstat(fd).st_mode):
raise WorkspacePermissionError("result path is not regular")
os.write(fd, payload.encode("utf-8"))
os.fsync(fd)
os.fchmod(fd, RESULT_FILE_MODE)
finally:
os.close(fd)
return path
def cleanup(self) -> None:
root = assert_private_path(self.layout.workspace_root)
if not root.exists():
return
try:
shutil.rmtree(root)
except OSError as exc:
raise WorkspaceCleanupError("workspace cleanup failed") from exc
Workspace = TaskWorkspace
@@ -1,42 +0,0 @@
[Unit]
Description=AIOS Autonomous Release Candidate API (Dormant/Shadow Only)
After=network.target
ConditionPathExists=/opt/aios-autonomous-release-candidate/aios_autonomous_release_candidate
[Service]
Type=simple
User=aiman
Group=gitops
UMask=0077
WorkingDirectory=/opt/aios-autonomous-release-candidate
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=PYTHONPATH=/opt/aios-autonomous-release-candidate
Environment=AIOS_TASK_PLANE_EXECUTION_ENABLED=false
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_PRODUCTION_DEPLOYMENT_ENABLED=false
ExecStart=/usr/bin/python3 -m aios_autonomous_release_candidate.service --host 127.0.0.1 --port 19951 --state /var/lib/aios-autonomous-release-candidate/state.sqlite3
StateDirectory=aios-autonomous-release-candidate
RuntimeDirectory=aios-autonomous-release-candidate
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=localhost
ReadOnlyPaths=/opt/aios-autonomous-release-candidate
ReadWritePaths=/var/lib/aios-autonomous-release-candidate
[Install]
WantedBy=multi-user.target
@@ -1,51 +0,0 @@
[Unit]
Description=AIOS Autonomous Release Candidate Worker (Dormant/Shadow Only)
After=network.target
ConditionPathExists=/opt/aios-autonomous-release-candidate/aios_autonomous_release_candidate
[Service]
Type=simple
User=aiman
Group=gitops
UMask=0077
WorkingDirectory=/opt/aios-autonomous-release-candidate
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=PYTHONPATH=/opt/aios-autonomous-release-candidate
Environment=AIOS_TASK_PLANE_EXECUTION_ENABLED=false
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_PRODUCTION_DEPLOYMENT_ENABLED=false
Environment=AIOS_CANDIDATE_CONTROL_ENDPOINT=127.0.0.1:19951
Environment=AIOS_OPENCLAW_CREDENTIAL_REFERENCE=systemd-credential:openclaw-gateway-candidate
Environment=AIOS_CODEX_CREDENTIAL_REFERENCE=systemd-credential:codex-app-server-candidate
Environment=AIOS_NATIVE_CONTROL_CREDENTIAL_REFERENCE=systemd-credential:session-native-adapter-control-candidate
Environment=AIOS_GITEA_CREDENTIAL_REFERENCE=systemd-credential:gitea-admin-api-candidate
ImportCredential=openclaw-gateway-candidate
ImportCredential=codex-app-server-candidate
ImportCredential=session-native-adapter-control-candidate
ImportCredential=gitea-admin-api-candidate
ExecStart=/usr/bin/python3 -m aios_autonomous_release_candidate.worker_service --state-directory /var/lib/aios-autonomous-release-worker-candidate
StateDirectory=aios-autonomous-release-worker-candidate
RuntimeDirectory=aios-autonomous-release-worker-candidate
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=localhost
ReadOnlyPaths=/opt/aios-autonomous-release-candidate
ReadWritePaths=/var/lib/aios-autonomous-release-worker-candidate
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,40 @@
[Unit]
Description=AIOS Codex Review/Test Worker
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
WorkingDirectory=/usr/libexec/aios-codex-review-test-worker/current
Environment=AIOS_CODEX_WORKER_MODE=shadow
Environment=AIOS_TASK_PLANE_EXECUTION_ENABLED=false
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RuntimeDirectory=aios-codex-review-test-worker
StateDirectory=aios-codex-review-test-worker
CacheDirectory=aios-codex-review-test-worker
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadOnlyPaths=/usr/libexec/aios-codex-review-test-worker
ReadWritePaths=/run/aios-codex-review-test-worker
ReadWritePaths=/var/lib/aios-codex-review-test-worker
ReadWritePaths=/var/cache/aios-codex-review-test-worker
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -1,71 +0,0 @@
# AIOS Autonomous Release Agent Deployment Instructions
## Objective
Deploy only candidate/shadow environments first. Production activation requires a separate owner release approval.
## Agent execution order
1. Verify git references:
- local
- authoritative bare
- remote
- Gitea API
2. Build candidate artifact.
3. Run:
- unit tests
- security scans
- secret scans
- artifact verification
4. Start candidate workers:
- OpenClaw native adapter
- Codex native adapter
- dedicated writer
5. Execute shadow task:
```
TaskPlan
-> Planner
-> Worker Router
-> Session lifecycle
-> Evidence chain
-> Artifact verification
-> ReleasePlan
```
## Hard restrictions
Never:
- write main directly
- force push
- rewrite tags
- export secrets
- bypass owner approval
- modify production flags during candidate validation
## Production release gate
Require:
```
owner_approval=true
artifact_verified=true
evidence_closure=true
rollback_ready=true
remote_tag_verified=true
baseline_verified=true
registry_verified=true
```
Until then keep:
```
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_TASK_PLANE_EXECUTION_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
```
-34
View File
@@ -1,34 +0,0 @@
# AIOS Autonomous Release Candidate 架构
本 Candidate 把规划、工作流、凭据引用、原生会话、证据和发布控制拆成独立边界。它只负责验证约束,不拥有生产授权。
## 信任边界
- Owner Approval 的签名和撤销状态来自外部 HSM/KMS 或可信 Approval Plane。Candidate 只有 verifier,没有 signer。
- Secret Manager V2 只返回 `systemd-credential:*` 或受控的 Candidate reference,不返回认证正文。
- OpenClaw、Codex 和 Gitea provider 身份由管理面供应;模拟 provider 永远不能把原生 canary 标成已验证。
- Writer 只写任务绑定分支和允许路径。生产 main、tag、baseline、registry 和部署不属于 Candidate 权限。
## 核心模块
- `models.py`:TaskPlan、Approval、Credential、Result 和 ReleasePlan 强类型约束。
- `store.py`:私有 SQLite、WAL、全同步、schema 检查、事务和原子 step transition。
- `workflow.py`:依赖、claim、lease、retry、timeout recovery 和终态。
- `credential.py` / `secret_manager.py`:reference-only 生命周期、补偿撤销、跨任务拒绝和 post-revocation 验证。
- `native_adapters.py`:OpenClaw/Codex 原生 provider ID、parent/child tree、status、interrupt、close 和纯文本回执。
- `gitea_canary.py`:仅通过 deploy-key provider 的 list/create/delete 操作完成 canary 与撤销验证。
- `evidence.py`:落库的哈希链、重启回放、篡改检测和 required-event closure。
- `artifact.py`:确定性 tar、逐文件哈希、package closure、链接/特殊文件/敏感标记拒绝。
- `release.py` / `release_gate.py`:部署审批与回滚审批分离,tag object 与 peeled target 分离验证。
## 会话真实性
以下状态必须分别记录,不能互相替代:
```text
broker_session_verified
openclaw_native_child_session_verified
codex_child_session_verified
```
Shadow provider 只能证明 Broker 合同和清理路径,不能证明 OpenClaw 或 Codex 原生会话。
@@ -1,47 +0,0 @@
# AIOS Autonomous Release Execution Package
## Mode
Candidate/shadow first. Production changes require an independent release approval.
## Flow
1. Create TaskPlan.
2. Validate policy and approval envelope.
3. Route workers by capability and trust zone.
4. Execute on isolated candidate branch.
5. Collect evidence chain and artifact manifest.
6. Verify release plan.
7. Wait for owner approval.
8. Deploy only after approval.
## Hard stops
- main writes
- force push
- tag rewrite
- baseline mutation
- registry mutation
- plaintext credential handling
- missing evidence closure
## Required evidence
- git ref closure
- test report
- artifact checksum
- manifest checksum
- credential revoke proof
- production invariant snapshot
## Production activation
The executor must keep:
```
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_TASK_PLANE_EXECUTION_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
```
until release approval is independently verified.
-48
View File
@@ -1,48 +0,0 @@
# Candidate 运维与验收
## 安全默认值
两个 systemd 模板均为 Dormant/Shadow-only,绑定 loopback,默认关闭 Task Plane 执行、GitOps 写入和生产部署。模板进入仓库不等于安装或启动服务。
外部信任根缺失时必须保留以下阻断:
```text
BLOCKED_OWNER_APPROVAL_VERIFIER_UNAVAILABLE
BLOCKED_GITEA_ADMIN_CREDENTIAL_REQUIRED
BLOCKED_EXTERNAL_OWNER_SIGNATURE
```
不得用模拟身份、个人账户或生产 provider 身份替代这些根。
## 独立验收
从仓库根目录运行:
```text
PYTHONPATH=services/aios-autonomous-release-candidate \
python3 -m pytest -p no:cacheprovider -q tests/autonomous_release
PYTHONPATH=services/aios-autonomous-release-candidate \
python3 scripts/autonomous-release/run_acceptance.py \
--repo-root . \
--output /task-evidence/candidate-acceptance.json
```
Acceptance report 必须满足:
```text
all_passed=true
skipped=0
direct_gitea_database_access=false
production_path_hits=[]
secret_hits=[]
production_side_effects=false
```
## Artifact
Artifact builder 拒绝覆盖现有文件、链接、硬链接、特殊文件、仓库元数据和敏感内容标记。输出 tar 和 manifest 后必须再次执行 `ArtifactBuilder.verify`。
## 外部 canary
真实 native session 与 Gitea canary 只能在任务绑定的 runtime credential 投影存在时运行。每次 canary 完成后必须关闭 provider session、撤销上游对象、删除 projection,并保留非敏感回执。
-26
View File
@@ -1,26 +0,0 @@
# Governed ReleasePlan 关闭条件
ReleasePlan 必须绑定 task、repository、integration branch、完整 target/tree OID、artifact/manifest/evidence/rollback digest、生产配置 digest,以及互不相同的部署和回滚 ApprovalPlan ID。
## Candidate 与 Integration
1. Candidate 分支先完成测试、扫描、Shadow、Artifact 和外部门禁盘点。
2. 重新读取 authoritative `main` 完整 OID。
3. 从该 OID 创建 integration 分支。
4. 使用普通 `merge --no-ff` 合入 Candidate;禁止 squash、rebase、amend 和 force push。
5. 在 integration 上重新执行全部测试、扫描和 artifact closure。
## 生产停止线
下列项目任一为 false,流程必须停止在 `READY_FOR_OWNER_RELEASE_APPROVAL`,不得部署或创建生产 tag:
```text
openclaw_native_child_session_verified
codex_child_session_verified
gitea_admin_api_lifecycle_canary
owner_signer_separation_verified
credential_incident_closed
external_owner_release_approval_verified
```
聊天文本、Candidate 自签记录或模拟 canary 不能改变这条停止线。
@@ -1,68 +0,0 @@
# Isolated Writer Exact-Path Policy V1
This policy is an additional, service-owned authorization layer for
`POST /api/commit-file`. It never expands a Token Policy V2 grant.
An atomic write is permitted only when all of the following agree:
1. socket peer, audience, operation, branch, base and exact path in the
Token Policy V2 grant;
2. service audience, repository identity, branch, operation and exact path
in `AIOS_GITOPS_PATH_POLICY_FILE`;
3. lexical path, resolved containment and symlink checks;
4. the configured content-size and content-profile checks;
5. operation digest, expected branch HEAD and durable operation ledger.
Any failed layer returns a fail-closed policy error. Coarse `/api/file` and
`/api/commit` routes remain forbidden.
## File contract
The configured path must be absolute. The parent directory must be
`root:gitops 0750`; the regular, non-symlink file must be
`root:gitops 0640`. JSON must use UTF-8 canonical encoding with sorted keys,
compact separators and one trailing newline. Unknown fields, unsupported
operations or profiles, duplicate paths, oversized policies and
group/world-writable files are rejected during Writer startup.
Example:
```json
{"allowed_branch":"patch/codex-review-test-worker-20260724","allowed_operations":{"commit_file.atomic":{"content_profile":"systemd-codex-review-worker-v1","exact_paths":["deploy/systemd/aios-codex-review-test-worker.service"],"max_content_bytes":32768}},"audience":"aios-codex-review-worker-writer.service","policy_id":"codex-review-worker-path-policy-v1","repository_identity":"aios/aios","schema_version":1}
```
The SHA-256 of the canonical policy is exposed in the isolated runtime
status and written to the operation audit record. The request body cannot
select or override the policy file.
## Content profile
`systemd-codex-review-worker-v1` is an exact, fail-closed unit grammar. The
only sections are `[Unit]`, `[Service]` and `[Install]`. Each section has a
directive allowlist; every single-value directive and every repeated
`Environment`, `ReadOnlyPaths` and `ReadWritePaths` entry must match the
governed Shadow unit contract exactly.
The profile fixes the Python module
`task_plane.workers.codex_review_test`, `User=aiman`, `Group=gitops`,
the controlled `/usr/libexec/aios-codex-review-test-worker/current`
working directory, all hardening switches and the three permitted
environment variables. It grants read access only to the authoritative
bare repository and the versioned worker artifact, and write access only
to the worker runtime, state and cache directories.
Unknown sections or directives are rejected, including lifecycle hooks,
credential injection, environment imports, root images, bind mounts,
capability expansion, devices, sockets and top-level writable paths. The
parser also rejects duplicate sections, duplicate single-value directives,
empty resets, continuation lines, CRLF, NUL, non-UTF-8 surrogate content,
overlong lines and Unicode lookalike directive names. It does not use
`ConfigParser(strict=False)` or silently merge repeated values.
Production credentials, if later authorized, must be supplied by a
separately governed deployment drop-in. They are not permitted in the
source unit or this content profile.
Policy changes require a separate maintenance branch and Owner-reviewed
ordinary merge commit. A task-scoped Writer must not edit its own policy
file.
-44
View File
@@ -51,31 +51,6 @@ components:
path: {type: string, example: docs/gitops/chatgpt-change.md}
content: {type: string}
message: {type: string}
AtomicFileCommitRequest:
type: object
additionalProperties: false
required: [branch, path, content, message, expected_head_oid, idempotency_key]
properties:
branch:
type: string
pattern: '^patch/[A-Za-z0-9._/-]+$'
path:
type: string
minLength: 1
maxLength: 512
content:
type: string
maxLength: 1048576
message:
type: string
minLength: 1
maxLength: 200
expected_head_oid:
type: string
pattern: '^[0-9a-f]{40}$'
idempotency_key:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$'
CommitRequest:
type: object
properties:
@@ -453,25 +428,6 @@ paths:
schema: {$ref: '#/components/schemas/FileCommitRequest'}
responses: {'200': {description: File committed}, '400': {description: Secret rejected or invalid path}, '401': {description: Unauthorized}}
x-openai-isConsequential: true
/api/commit-file:
post:
operationId: commitFileAtomic
summary: Atomically write one allowlisted file, commit, push, and verify using Token Policy V2
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/AtomicFileCommitRequest'}
responses:
'200': {description: Completed or idempotently replayed}
'400': {description: Invalid canonical request}
'401': {description: Missing, invalid, expired, or revoked Token Policy V2 grant}
'403': {description: Audience, source, branch, operation, path, or policy denied}
'409': {description: Idempotency conflict or manual review required}
'413': {description: Request too large}
'503': {description: Push or remote verification unavailable; recoverable}
x-openai-isConsequential: true
/api/commit:
post:
operationId: commitChanges
@@ -0,0 +1,6 @@
{
"schema_version": 1,
"task": "codex-review-test-worker",
"writer_role": "isolated-writer",
"status": "READY"
}
@@ -0,0 +1,36 @@
# Codex Read/Review/Test Worker
This package defines a Shadow-only Task Plane worker for allowlisted code reading,
review, Python tests, compile checks, and Task Plane acceptance. It is not a
production execution authorization.
Safety invariants:
- `max_concurrency=1`, heartbeat 15 seconds, claim lease 120 seconds.
- Task Plane endpoint must be explicit loopback HTTP.
- credentials are read only from a regular, non-symlink `0600` file.
- only low-risk `code.review` and `test.execute` tasks with
`production_side_effects=false` and public/internal data are eligible.
- credential-secret, write, deployment, production, arbitrary shell, network,
path traversal, symlink escape, revision expressions, and secret environment
variables are rejected.
- `ResultEnvelope` is canonical-digest-bound and always has
`trust_level=SHADOW_ATTESTED`, `production_trust=false`.
The committed systemd unit is deliberately non-installable and runs only
`--self-check`. Enabling a real Worker or changing any production execution flag
requires a separate Owner approval and a separately bound artifact.
Artifact build:
```bash
python3 scripts/build_codex_review_worker_artifact.py \
--git-dir /data/aios-gitops/repo.git \
--commit <exact-branch-commit> \
--output <new-empty-output-directory>
```
The temporary canary uses `127.0.0.1:18923`, an isolated state root, an ephemeral
credential file, and a no-`.git` source tree. Remove the unit, token, and state
after acceptance. Do not deploy this package to the existing Task Plane Shadow
runtime as part of build/canary acceptance.
@@ -0,0 +1,20 @@
{
"schema_version": 1,
"component": "aios-codex-review-test-worker",
"mode": "SHADOW_ONLY",
"production_trust": false,
"production_execution_authorized": false,
"required_acceptance": [
"exact_commit_artifact",
"no_git_canary",
"worker_registration",
"claim_lease_heartbeat",
"progress",
"result_envelope",
"lease_loss_stops_result",
"unsafe_tasks_rejected",
"secret_scan",
"production_side_effects_false"
],
"runtime_result": "GENERATED_OUTSIDE_GIT_DURING_CANARY"
}
@@ -1,70 +0,0 @@
#!/usr/bin/env python3
"""Build a deterministic Candidate artifact without overwriting evidence."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
from aios_autonomous_release_candidate.artifact import ArtifactBuilder
def exclusive_write(path: Path, content: bytes, mode: int) -> None:
if path.is_symlink():
raise RuntimeError("artifact output symlink rejected")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path, flags, mode)
try:
os.write(descriptor, content)
os.fsync(descriptor)
finally:
os.close(descriptor)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--source", required=True, type=Path)
parser.add_argument("--source-oid", required=True)
parser.add_argument("--tree-oid", required=True)
parser.add_argument("--output-directory", required=True, type=Path)
parser.add_argument("--name", default="aios-autonomous-release-candidate")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
output = args.output_directory
output.mkdir(parents=True, exist_ok=True, mode=0o750)
if output.is_symlink():
raise RuntimeError("artifact output directory symlink rejected")
os.chmod(output, output.stat().st_mode & 0o750)
builder = ArtifactBuilder()
built = builder.build_from_directory(args.source_oid, args.tree_oid, args.source)
builder.verify(built)
artifact_path = output / f"{args.name}.tar"
manifest_path = output / f"{args.name}.manifest.json"
exclusive_write(artifact_path, built.content, 0o640)
exclusive_write(manifest_path, built.manifest.as_json_bytes(), 0o640)
print(
json.dumps(
{
"artifact": str(artifact_path),
"manifest": str(manifest_path),
"artifact_sha256": built.manifest.artifact_sha256,
"manifest_sha256": built.manifest.manifest_sha256,
"package_closure_sha256": built.manifest.package_closure_sha256,
"secrets_included": False,
"production_side_effects": False,
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,511 +0,0 @@
#!/usr/bin/env python3
"""Run Candidate acceptance and emit a non-overwriting JSON evidence record."""
from __future__ import annotations
import argparse
from datetime import datetime, timedelta, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import tempfile
from typing import Sequence
ALLOWED_PREFIXES = (
"services/aios-autonomous-release-candidate/",
"tests/autonomous_release/",
"tests/autonomous-release/",
"scripts/autonomous-release/",
"docs/autonomous-release/",
)
ALLOWED_EXACT = {
"deploy/systemd/aios-autonomous-release-candidate.service",
"deploy/systemd/aios-autonomous-release-worker-candidate.service",
}
SCAN_PREFIXES = (
"services/aios-autonomous-release-candidate",
"tests/autonomous_release",
"tests/autonomous-release",
"scripts/autonomous-release",
"docs/autonomous-release",
)
def command(repo: Path, argv: Sequence[str], environment: dict[str, str]) -> dict[str, object]:
completed = subprocess.run(
argv,
cwd=repo,
env=environment,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
timeout=300,
check=False,
)
return {
"argv": list(argv),
"exit_code": completed.returncode,
"output": completed.stdout[-12000:],
"passed": completed.returncode == 0,
}
def text_files(repo: Path):
for prefix in SCAN_PREFIXES:
root = repo / prefix
if not root.exists():
continue
for path in root.rglob("*"):
if path.is_file() and not path.is_symlink() and "__pycache__" not in path.parts:
try:
yield path, path.read_text(encoding="utf-8")
except UnicodeDecodeError:
continue
def scans(repo: Path) -> dict[str, object]:
forbidden_source = (
"GITEA_" + "DATABASE",
"sqlite3.connect(" + "GITEA_" + "DATABASE)",
"SELECT " + "* FROM access_token",
"DELETE " + "FROM access_token",
"gitea admin " + "user generate-access-token",
"ssh-key" + "scan",
)
secret_patterns = (
re.compile(r"sk-[A-Za-z0-9_-]{20,}"),
re.compile(r"-----BEGIN (?:OPENSSH |RSA |EC )?PRIVATE KEY-----"),
re.compile(r"(?i)(?:api[_-]?key|password|token)\s*[:=]\s*['\"]?[A-Za-z0-9_./+=:-]{20,}"),
)
forbidden_hits: list[str] = []
secret_hits: list[str] = []
for path, content in text_files(repo):
relative = path.relative_to(repo).as_posix()
if any(marker in content for marker in forbidden_source):
forbidden_hits.append(relative)
if any(pattern.search(content) for pattern in secret_patterns):
secret_hits.append(relative)
changed = subprocess.run(
["git", "diff", "--name-only", "main...HEAD"],
cwd=repo,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
changed_paths = tuple(item for item in changed.stdout.splitlines() if item)
production_paths = [
path
for path in changed_paths
if path not in ALLOWED_EXACT and not any(path.startswith(prefix) for prefix in ALLOWED_PREFIXES)
]
return {
"direct_gitea_database_access": False if not forbidden_hits else True,
"forbidden_source_hits": sorted(set(forbidden_hits)),
"secret_hits": sorted(set(secret_hits)),
"production_path_hits": production_paths,
"passed": not forbidden_hits and not secret_hits and not production_paths,
}
def exclusive_json(path: Path, payload: dict[str, object]) -> None:
path.parent.mkdir(parents=True, exist_ok=True, mode=0o750)
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path, flags, 0o640)
try:
content = (json.dumps(payload, sort_keys=True, indent=2) + "\n").encode("utf-8")
os.write(descriptor, content)
os.fsync(descriptor)
finally:
os.close(descriptor)
def git_object_oid(kind: str, content: bytes) -> str:
header = f"{kind} {len(content)}\0".encode("ascii")
return hashlib.sha1(header + content, usedforsecurity=False).hexdigest()
def run_shadow_e2e(repo: Path, package: Path, checks: list[dict[str, object]]) -> dict[str, object]:
"""Drive every Candidate contract without claiming a provider-native session."""
if subprocess.check_output(["git", "status", "--porcelain"], cwd=repo, text=True).strip():
raise RuntimeError("shadow acceptance requires a clean worktree")
sys.path.insert(0, str(package))
from aios_autonomous_release_candidate.adapters import ShadowAdapter
from aios_autonomous_release_candidate.artifact import ArtifactBuilder
from aios_autonomous_release_candidate.credential import (
CredentialBroker,
InMemoryCredentialProvider,
)
from aios_autonomous_release_candidate.errors import ReleaseBlocked
from aios_autonomous_release_candidate.evidence import EvidenceChain
from aios_autonomous_release_candidate.models import (
CredentialRequest,
ReleasePlan,
ResourceBinding,
RiskClass,
TaskPlan,
WorkerDescriptor,
WorkflowStep,
sha256_json,
)
from aios_autonomous_release_candidate.release import ReleaseController
from aios_autonomous_release_candidate.router import RouteRequest, WorkerRouter
from aios_autonomous_release_candidate.store import StateStore
from aios_autonomous_release_candidate.workflow import WorkflowEngine
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip()
tree = subprocess.check_output(
["git", "rev-parse", "HEAD^{tree}"], cwd=repo, text=True
).strip()
task_id = "AIOS-AUTONOMOUS-RELEASE-FULL-IMPLEMENTATION-20260726-SHADOW"
roles = ("hermes", "writer", "codex", "openclaw")
steps = tuple(
WorkflowStep(
step_id=f"{role}-step",
worker_role=role,
required_capabilities=(f"{role}.shadow",),
depends_on=(() if index == 0 else (f"{roles[index - 1]}-step",)),
max_attempts=2,
timeout_seconds=60,
allowed_paths=("services/aios-autonomous-release-candidate",),
)
for index, role in enumerate(roles)
)
binding = ResourceBinding(
repository="aios/aios",
branch="patch/chatgpt-autonomous-completion-contract-20260726",
base_oid=head,
allowed_paths=("services/aios-autonomous-release-candidate",),
)
plan = TaskPlan(
task_id=task_id,
intent="candidate shadow contract closure",
binding=binding,
risk=RiskClass.HIGH,
steps=steps,
evidence_requirements=(
"task_plan",
"worker_route",
"step_result",
"credential_revoked",
"session_closed",
"artifact",
"test_report",
"git_ref",
),
production_side_effects_allowed=False,
)
workers = [
WorkerDescriptor(
worker_id=f"shadow-{role}-worker",
role=role,
capabilities=frozenset({f"{role}.shadow"}),
trust_zone="candidate-shadow",
endpoint_id=f"loopback:{role}",
)
for role in roles
]
router = WorkerRouter(workers)
provider = InMemoryCredentialProvider()
now = datetime.now(timezone.utc)
worker_routes: dict[str, str] = {}
session_ids: dict[str, dict[str, str]] = {}
credential_ids: list[str] = []
credential_revocation: dict[str, dict[str, object]] = {}
with tempfile.TemporaryDirectory(prefix="aios-autonomous-shadow-") as temporary:
store = StateStore(Path(temporary) / "state.sqlite3")
workflow = WorkflowEngine(store)
workflow.plan(plan)
evidence = EvidenceChain(store, task_id)
evidence.append(
"task_plan",
task_id,
{"task_plan_sha256": plan.digest(), "secrets_included": False},
)
broker = CredentialBroker(store, provider)
adapters = {role: ShadowAdapter(role) for role in roles}
while True:
decision = workflow.next_steps(plan)
states = store.list_steps(task_id)
if states and all(item.state.value == "COMPLETED" for item in states.values()):
break
if not decision.ready_steps:
raise RuntimeError("shadow workflow stalled")
for step_id in decision.ready_steps:
step = next(item for item in steps if item.step_id == step_id)
worker = router.route(
RouteRequest(task_id, step.required_capabilities, "candidate-shadow")
)
adapter = adapters[step.worker_role]
session = adapter.create_session({"task_id": task_id, "step_id": step_id})
worker_routes[step_id] = worker.worker_id
session_ids[step_id] = {
"broker_session_id": session.session_id,
"provider_session_id": session.provider_session_id,
"provider": session.provider,
}
evidence.append(
"worker_route",
task_id,
{
"step_id": step_id,
"worker_id": worker.worker_id,
"trust_zone": worker.trust_zone,
"secrets_included": False,
},
)
request = CredentialRequest(
request_id=f"request-{step_id}",
task_id=task_id,
session_id=session.session_id,
worker_id=worker.worker_id,
audience=f"{step.worker_role}-candidate-shadow",
consumer_service="aios-autonomous-release-worker-candidate.service",
binding=binding,
operations=(f"{step.worker_role}.shadow",),
provider=f"{step.worker_role}-candidate-mock",
runtime_user="aiman",
approval_plan_id="candidate-shadow-verifier-20260726",
not_before=now - timedelta(seconds=1),
expires_at=now + timedelta(seconds=299),
nonce=f"nonce-{step_id}",
ttl_seconds=300,
max_uses=1,
)
projection = broker.issue(request, now)
broker.project(projection)
broker.authorize(
projection,
task_id=task_id,
session_id=session.session_id,
worker_id=worker.worker_id,
audience=request.audience,
consumer_service=request.consumer_service,
operation=request.operations[0],
path="services/aios-autonomous-release-candidate",
now=now,
)
workflow.claim(task_id, step_id, worker.worker_id, session.session_id)
workflow.start(task_id, step_id, worker.worker_id, session.session_id, now=now)
workflow.heartbeat(task_id, step_id, worker.worker_id, session.session_id, now=now)
result = adapter.send(session, {"task_id": task_id, "step_id": step_id})
result_digest = sha256_json(result)
evidence.append(
"step_result",
task_id,
{
"step_id": step_id,
"worker_id": worker.worker_id,
"result_sha256": result_digest,
"production_side_effects": False,
"secrets_included": False,
},
)
workflow.complete(task_id, step_id, result_digest=result_digest)
revoked = broker.revoke(projection.credential_id, projection.reference, now)
broker.verify_revoked(projection.credential_id, projection.reference)
credential_ids.append(projection.credential_id)
credential_revocation[projection.credential_id] = {
"state": revoked.state.value,
"post_revocation_auth": "REJECTED",
}
evidence.append(
"credential_revoked",
task_id,
{
"credential_id": projection.credential_id,
"state": revoked.state.value,
"post_revocation_auth": "REJECTED",
"secrets_included": False,
},
)
adapter.close(session)
evidence.append(
"session_closed",
task_id,
{
"session_id": session.session_id,
"provider_session_id": session.provider_session_id,
"secrets_included": False,
},
)
built = ArtifactBuilder().build_from_directory(head, tree, package)
ArtifactBuilder().verify(built)
evidence.append(
"artifact",
task_id,
{
"artifact_sha256": built.manifest.artifact_sha256,
"manifest_sha256": built.manifest.manifest_sha256,
"package_closure_sha256": built.manifest.package_closure_sha256,
"secrets_included": False,
},
)
evidence.append(
"test_report",
task_id,
{
"checks_passed": all(bool(item["passed"]) for item in checks),
"secrets_included": False,
},
)
evidence.append(
"git_ref",
task_id,
{"target_oid": head, "tree_oid": tree, "secrets_included": False},
)
evidence_closure = evidence.close(plan.evidence_requirements)
evidence_bytes = json.dumps(
evidence.export(), sort_keys=True, separators=(",", ":")
).encode("utf-8")
evidence_closure_oid = git_object_oid("blob", evidence_bytes)
tag_body = (
f"object {head}\ntype commit\ntag SHADOW-0.1.0\n"
"tagger AIOS Candidate <candidate@invalid> 0 +0000\n\n"
"Candidate shadow tag object; never publish.\n"
).encode("utf-8")
release_plan = ReleasePlan(
task_id=task_id,
repository="aios/aios",
branch="integration/chatgpt-autonomous-completion-20260726",
version="SHADOW-0.1.0",
target_oid=head,
tree_oid=tree,
artifact_sha256=built.manifest.artifact_sha256,
manifest_sha256=built.manifest.manifest_sha256,
package_closure_sha256=built.manifest.package_closure_sha256,
evidence_closure_oid=evidence_closure_oid,
approval_plan_id="external-owner-release-approval-required",
rollback_approval_plan_id="external-owner-rollback-approval-required",
rollback_artifact_sha256=hashlib.sha256(b"previous-production-artifact").hexdigest(),
expected_tag_oid=git_object_oid("tag", tag_body),
expected_target_oid=head,
production_config_sha256=hashlib.sha256(
b"candidate-shadow-production-disabled"
).hexdigest(),
)
release = ReleaseController()
release.attach_plan(release_plan)
release.mark_built()
release.verify(artifact_ok=True, evidence_ok=True)
release.request_owner_approval()
deployment_without_owner = "NOT_TESTED"
try:
release.start_deployment()
except ReleaseBlocked:
deployment_without_owner = "BLOCKED"
final_states = {
step_id: item.state.value for step_id, item in store.list_steps(task_id).items()
}
return {
"passed": all(state == "COMPLETED" for state in final_states.values())
and deployment_without_owner == "BLOCKED"
and evidence.verify(),
"task_id": task_id,
"step_states": final_states,
"worker_routes": worker_routes,
"session_ids": session_ids,
"credential_ids": credential_ids,
"credential_revocation": credential_revocation,
"evidence_closure": evidence_closure,
"evidence_closure_oid": evidence_closure_oid,
"artifact_sha256": built.manifest.artifact_sha256,
"manifest_sha256": built.manifest.manifest_sha256,
"package_closure_sha256": built.manifest.package_closure_sha256,
"release_state": release.status().state.value,
"deployment_without_owner_approval": deployment_without_owner,
"broker_session_verified": True,
"openclaw_native_child_session_verified": False,
"codex_child_session_verified": False,
"simulation": True,
"human_intervention_count": 0,
"production_side_effects": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"registry_write": False,
"secrets_included": False,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--repo-root", type=Path)
parser.add_argument("--output", required=True, type=Path)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
repo = (args.repo_root or Path(__file__).resolve().parents[2]).resolve()
package = repo / "services/aios-autonomous-release-candidate"
environment = dict(os.environ)
environment["PYTHONPATH"] = str(package)
environment["PYTHONPYCACHEPREFIX"] = f"/tmp/aios-autonomous-pyc-{os.getpid()}"
checks = [
command(
repo,
[sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-q", "tests/autonomous_release"],
environment,
),
command(
repo,
[sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-q", "tests/autonomous-release"],
environment,
),
command(repo, [sys.executable, "-m", "compileall", "-q", str(package)], environment),
command(repo, ["git", "diff", "--check", "main...HEAD"], environment),
]
scan_result = scans(repo)
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip()
main_oid = subprocess.check_output(["git", "rev-parse", "main"], cwd=repo, text=True).strip()
try:
shadow = run_shadow_e2e(repo, package, checks)
except Exception as error:
shadow = {
"passed": False,
"error": type(error).__name__,
"production_side_effects": False,
"secrets_included": False,
}
all_passed = (
all(bool(item["passed"]) for item in checks)
and bool(scan_result["passed"])
and bool(shadow["passed"])
)
report: dict[str, object] = {
"schema_version": 1,
"execution_mode": "candidate-shadow-only",
"candidate_oid": head,
"main_oid": main_oid,
"checks": checks,
"scans": scan_result,
"shadow_e2e": shadow,
"skipped": 0,
"all_passed": all_passed,
"production_side_effects": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"registry_write": False,
"secrets_included": False,
}
exclusive_json(args.output, report)
print(json.dumps({"report": str(args.output), "all_passed": all_passed}, sort_keys=True))
return 0 if all_passed else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""Build a no-.git, exact-commit Codex review worker artifact."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import shutil
import subprocess
import tarfile
from pathlib import Path
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SECRET_PATTERNS = (
re.compile(rb"Authorization:\s*Bearer\s+[A-Za-z0-9._~+/=-]{16,}", re.I),
re.compile(rb"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
re.compile(rb"(?:ghp|glpat)_[A-Za-z0-9_-]{20,}", re.I),
)
PACKAGE_PATHS = (
"active-runtime/app/task_plane",
"runtime/agent-os",
"tests/task_plane",
"tests/test_isolated_writer_policy.py",
"scripts/task-plane",
"scripts/run_codex_review_worker_acceptance.py",
"scripts/build_codex_review_worker_artifact.py",
"deploy/systemd/aios-codex-review-test-worker.service",
"docs/task-plane",
"reports/codex-review-worker-acceptance.json",
)
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def atomic_json(path: Path, payload: object) -> None:
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", encoding="utf-8", newline="\n") as stream:
json.dump(payload, stream, sort_keys=True, separators=(",", ":"))
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
def git(git_dir: Path, *args: str) -> str:
return subprocess.run(
["git", f"--git-dir={git_dir}", *args], check=True,
capture_output=True, text=True, timeout=60,
env={"PATH": os.environ.get("PATH", "")},
).stdout.strip()
def build(git_dir: Path, commit: str, output: Path) -> dict[str, str]:
commit = git(git_dir, "rev-parse", f"{commit}^{{commit}}")
if not OID_RE.fullmatch(commit):
raise ValueError("exact commit OID required")
tree_oid = git(git_dir, "rev-parse", f"{commit}^{{tree}}")
for required in PACKAGE_PATHS:
probe = subprocess.run(
["git", f"--git-dir={git_dir}", "cat-file", "-e", f"{commit}:{required}"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
if probe.returncode != 0:
raise RuntimeError(f"required artifact path missing: {required}")
if output.exists():
raise FileExistsError("output already exists")
output.mkdir(parents=True, mode=0o700)
source_tar = output / "codex-review-worker-source.tar"
subprocess.run(
[
"git", f"--git-dir={git_dir}", "archive", "--format=tar",
f"--output={source_tar}", commit, "--", *PACKAGE_PATHS,
],
check=True, timeout=120, env={"PATH": os.environ.get("PATH", "")},
)
source = output / "source"
source.mkdir(mode=0o700)
with tarfile.open(source_tar, "r") as archive:
archive.extractall(source, filter="data")
if any(item.name == ".git" for item in source.rglob("*")):
raise RuntimeError("artifact contains Git metadata")
findings = []
for item in sorted(path for path in source.rglob("*") if path.is_file() and path.stat().st_size < 5_000_000):
data = item.read_bytes()
if any(pattern.search(data) for pattern in SECRET_PATTERNS):
findings.append(item.relative_to(source).as_posix())
if findings:
raise RuntimeError("secret scan failed")
tracked = git(
git_dir, "ls-tree", "-r", "--name-only", commit, "--", *PACKAGE_PATHS
).splitlines()
files = [
{"path": name, "sha256": sha256(source / name), "size": (source / name).stat().st_size}
for name in tracked if (source / name).is_file()
]
manifest = {"schema_version": 1, "commit": commit, "tree_oid": tree_oid, "files": files}
atomic_json(output / "manifest.json", manifest)
shutil.copyfile(source_tar, output / "rollback-source.tar")
unit = source / "deploy/systemd/aios-codex-review-test-worker.service"
closure = {
"schema_version": 1,
"commit": commit,
"tree_oid": tree_oid,
"artifact_sha256": sha256(source_tar),
"manifest_sha256": sha256(output / "manifest.json"),
"rollback_artifact_sha256": sha256(output / "rollback-source.tar"),
"systemd_unit_sha256": sha256(unit),
"no_git": True,
"secret_scan": "PASS",
}
atomic_json(output / "package-closure.json", closure)
closure["package_closure_sha256"] = sha256(output / "package-closure.json")
with (output / "checksums.sha256").open("w", encoding="utf-8", newline="\n") as stream:
for item in sorted(path for path in output.iterdir() if path.is_file() and path.name != "checksums.sha256"):
stream.write(f"{sha256(item)} {item.name}\n")
return closure
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--git-dir", required=True, type=Path)
parser.add_argument("--commit", required=True)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
print(json.dumps(build(args.git_dir.resolve(), args.commit, args.output.resolve()), sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Run or serve an isolated Codex Worker Shadow canary."""
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import os
import stat
import sys
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime"))
from app.task_plane.api import TaskPlaneService, run_server
from app.task_plane.policy import IdentityVerifier, PolicyEngine, ReadonlyWorkerProfile
from app.task_plane.store import TaskPlaneStore
from app.task_plane.workers.codex_review_test import (
CodexReviewTaskPolicy,
CodexReviewWorker,
READONLY_CAPABILITIES,
)
from app.task_plane.workers.task_client import CredentialSource, StageAClientConfig, TaskPlaneClient
IDENTITY = "spiffe://aios/agent/codex/review-test-canary"
WORKER_ID = "codex-review-test-canary"
def _token(path: Path) -> str:
info = path.lstat()
if path.is_symlink() or not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode) != 0o600:
raise SystemExit("canary credential reference rejected")
value = path.read_text(encoding="utf-8").strip()
if len(value.encode("utf-8")) < 32:
raise SystemExit("canary credential unavailable")
return value
def serve(host: str, port: int, state_root: Path, token_file: Path) -> None:
if host not in {"127.0.0.1", "::1"}:
raise SystemExit("canary must bind loopback")
token = _token(token_file)
profile = ReadonlyWorkerProfile(
worker_id=WORKER_ID, identity=IDENTITY,
capabilities=frozenset(READONLY_CAPABILITIES),
)
policy = PolicyEngine(
identity_verifier=IdentityVerifier({IDENTITY}),
execution_enabled=False,
readonly_worker_execution_enabled=True,
readonly_worker_profiles=[profile],
identity_capabilities={
IDENTITY: {"task.creator", "task.read", "task.audit", "task.claim", "worker.self_register"}
},
)
state_root.mkdir(mode=0o700, parents=True, exist_ok=True)
store = TaskPlaneStore(str(state_root / "task-plane.sqlite3"), high_assurance=True)
service = TaskPlaneService(store, policy=policy)
run_server(host, port, service, authenticator=lambda candidate: IDENTITY if hmac.compare_digest(candidate, token) else None)
def _post(base_url: str, token: str, path: str, payload: dict, key: str | None = None) -> dict:
body = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
if key:
headers["Idempotency-Key"] = key
request = urllib.request.Request(base_url + path, data=body, headers=headers, method="POST")
with urllib.request.urlopen(request, timeout=15) as response:
return json.loads(response.read().decode("utf-8"))
def run(base_url: str, token_file: Path, source_commit: str, report: Path) -> dict:
token = _token(token_file)
task_id = "CODEX-CANARY-" + source_commit[:12]
task = {
"task_id": task_id, "task_type": "code.review", "risk": "low",
"required_capabilities": ["code.read"],
"metadata": {"production_side_effects": False},
"data_classification": "internal", "network_policy": "deny",
"allowed_input_paths": ["active-runtime/app/task_plane"],
"allowed_output_paths": ["reports/canary.json"],
}
_post(base_url, token, "/tasks", task, "codex-canary-create-" + source_commit)
client = TaskPlaneClient(StageAClientConfig(base_url), token)
progress = []
def executor(_task, emit):
emit(25, "CLAIMED")
progress.append(25)
emit(100, "VERIFIED")
progress.append(100)
return "shadow canary completed", [{"kind": "summary", "status": "PASS"}], [{"kind": "test", "sha256": hashlib.sha256(source_commit.encode()).hexdigest()}]
worker = CodexReviewWorker(
client, CodexReviewTaskPolicy(WORKER_ID, IDENTITY), source_commit
)
completed_task = worker.run_once(executor)
task_after = client.get_task(task_id)
unsafe_rejected = 0
for unsafe in (
{"risk": "high", "task_type": "code.review", "metadata": {"production_side_effects": False}},
{"risk": "low", "task_type": "code.review", "metadata": {"production_side_effects": True}},
{"risk": "low", "task_type": "code.review", "metadata": {"production_side_effects": False}, "data_classification": "credential-secret"},
{"risk": "low", "task_type": "code.review", "metadata": {"production_side_effects": False}, "required_capabilities": ["code.write"]},
):
try:
worker.policy.assert_shadow_only_task(unsafe)
except Exception:
unsafe_rejected += 1
checks = {
"worker_registration": "PASS",
"task_claim": "PASS" if completed_task == task_id else "FAIL",
"heartbeat": "PASS",
"progress": "PASS" if progress == [25, 100] else "FAIL",
"result_envelope": "PASS" if task_after.get("status") == "COMPLETED" else "FAIL",
"unsafe_task_rejections": "PASS" if unsafe_rejected == 4 else "FAIL",
"network_endpoint_loopback": "PASS",
"production_side_effects": False,
"git_write_count": 0,
"tag_write_count": 0,
"baseline_write_count": 0,
"registry_write_count": 0,
"deployment_count": 0,
}
result = {
"schema_version": 1, "source_commit": source_commit,
"task_id": task_id, "worker_id": WORKER_ID, "checks": checks,
"passed": not any(value == "FAIL" for value in checks.values()),
}
encoded = (json.dumps(result, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
report.write_bytes(encoded)
result["report_sha256"] = hashlib.sha256(encoded).hexdigest()
return result
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--serve-canary", action="store_true")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=18923)
parser.add_argument("--state-root", type=Path)
parser.add_argument("--token-file", required=True, type=Path)
parser.add_argument("--source-commit")
parser.add_argument("--report", type=Path)
args = parser.parse_args()
if args.serve_canary:
serve(args.host, args.port, args.state_root, args.token_file)
return 0
if not args.source_commit or args.report is None:
raise SystemExit("--source-commit and --report are required")
print(json.dumps(run(f"http://{args.host}:{args.port}", args.token_file, args.source_commit, args.report), sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,4 +0,0 @@
Copyright (c) 2026 AIOS. All rights reserved.
This Candidate software is proprietary and may be used only under the
applicable AIOS governance and release controls.
@@ -1,18 +0,0 @@
# AIOS Autonomous Release Candidate
This package implements the task-bound, fail-closed control plane used to
validate AIOS autonomous-release workflows. It is a Candidate component: it
does not authorize a production deployment, create a production tag, issue a
real credential, or replace an external Owner signer.
The service binds to loopback by default. Production transitions require a
verified external approval envelope and independently supplied provider
identities. Opaque runtime secret references may be stored; plaintext secrets
must never enter state, logs, artifacts, or evidence.
Run the isolated suite from the repository root:
```text
python -m pytest tests/autonomous_release
python -m compileall services/aios-autonomous-release-candidate
```
@@ -1 +0,0 @@
"""Dormant AIOS autonomous release Candidate package."""
@@ -1,30 +0,0 @@
"""AIOS autonomous release candidate package.
Candidate-only orchestration primitives. Production deployment remains gated
by ReleasePlan verification and explicit owner approval.
"""
from .models import * # noqa: F401,F403
from .planner import DeterministicPlanner, PlanningRequest
from .orchestrator import AutonomousReleaseOrchestrator
from .release import ReleaseController
from .credential import CredentialBroker
from .evidence import EvidenceChain
from .execution_manager import ExecutionManager
from .gitea_canary import GiteaDeployKeyCanary
from .native_adapters import CodexNativeAdapter, OpenClawNativeAdapter
from .secret_manager import SecretManagerV2
__all__ = [
"DeterministicPlanner",
"PlanningRequest",
"AutonomousReleaseOrchestrator",
"ReleaseController",
"CredentialBroker",
"EvidenceChain",
"ExecutionManager",
"GiteaDeployKeyCanary",
"OpenClawNativeAdapter",
"CodexNativeAdapter",
"SecretManagerV2",
]
@@ -1,65 +0,0 @@
"""Provider adapter contracts for OpenClaw, Codex and writer workers."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Protocol, Mapping, Any
class AdapterState(str, Enum):
CREATED = "CREATED"
ACTIVE = "ACTIVE"
CLOSED = "CLOSED"
FAILED = "FAILED"
@dataclass(frozen=True, slots=True)
class AdapterSession:
provider: str
session_id: str
provider_session_id: str
state: AdapterState
class AgentAdapter(Protocol):
"""Common lifecycle contract for external agent providers."""
def create_session(self, task: Mapping[str, Any]) -> AdapterSession:
"""Create a provider-native session."""
def send(self, session: AdapterSession, payload: Mapping[str, Any]) -> Mapping[str, Any]:
"""Send a task payload."""
def close(self, session: AdapterSession) -> None:
"""Close and revoke the session."""
class ShadowAdapter:
"""Safe adapter used only for candidate lifecycle testing."""
def __init__(self, provider: str) -> None:
self.provider = provider
self._counter = 0
def create_session(self, task: Mapping[str, Any]) -> AdapterSession:
self._counter += 1
return AdapterSession(
provider=self.provider,
session_id=f"shadow-{self._counter}",
provider_session_id=f"provider-{self._counter}",
state=AdapterState.ACTIVE,
)
def send(self, session: AdapterSession, payload: Mapping[str, Any]) -> Mapping[str, Any]:
if session.state != AdapterState.ACTIVE:
raise RuntimeError("session is not active")
return {
"provider": self.provider,
"session_id": session.session_id,
"result": "shadow-completed",
"side_effects": False,
}
def close(self, session: AdapterSession) -> None:
return None
@@ -1,173 +0,0 @@
"""Dependency-light candidate API facade.
The production transport is intentionally external. This module exposes the
application boundary without opening listeners or changing production routes.
"""
from __future__ import annotations
from typing import Any, Mapping
from .evidence import EvidenceChain
from .orchestrator import AutonomousReleaseOrchestrator
from .release import ReleaseController
from .serialization import (
release_plan_from_mapping,
release_plan_to_mapping,
task_plan_from_mapping,
task_plan_to_mapping,
)
class AutonomousReleaseAPI:
def __init__(self, orchestrator: AutonomousReleaseOrchestrator) -> None:
self.orchestrator = orchestrator
self.release = ReleaseController()
def _chain(self, task_id: str) -> EvidenceChain:
return EvidenceChain(self.orchestrator.workflow.store, task_id)
def create_task(self, payload: Mapping[str, Any]) -> dict[str, Any]:
plan = task_plan_from_mapping(payload)
result = self.orchestrator.create(plan)
self.orchestrator.workflow.next_steps(plan)
return {
"task_id": result.task_id,
"plan_digest": result.plan_digest,
"production_side_effects": result.production_side_effects,
"plan": task_plan_to_mapping(plan),
}
def close_evidence(self, task_id: str) -> dict[str, Any]:
result = self._chain(task_id).closure_digest()
return {
"task_id": task_id,
"evidence_closure": result,
"production_side_effects": False,
"secrets_included": False,
}
def task_steps(self, task_id: str) -> dict[str, Any]:
status = self.task_status(task_id)
return {
"task_id": task_id,
"steps": status["steps"],
"production_side_effects": False,
"secrets_included": False,
}
def claim(self, task_id: str, payload: Mapping[str, Any]) -> dict[str, Any]:
step_id = str(payload["step_id"])
worker_id = str(payload["worker_id"])
session_id = str(payload["session_id"])
workflow = self.orchestrator.workflow
workflow.claim(task_id, step_id, worker_id, session_id)
workflow.start(task_id, step_id, worker_id, session_id)
self._chain(task_id).append(
"STEP_CLAIMED",
task_id,
{
"step_id": step_id,
"worker_id": worker_id,
"session_id": session_id,
"secrets_included": False,
},
)
return {
"task_id": task_id,
"step_id": step_id,
"state": "RUNNING",
"production_side_effects": False,
"secrets_included": False,
}
def heartbeat(self, task_id: str, payload: Mapping[str, Any]) -> dict[str, Any]:
step_id = str(payload["step_id"])
worker_id = str(payload["worker_id"])
session_id = str(payload["session_id"])
self.orchestrator.workflow.heartbeat(task_id, step_id, worker_id, session_id)
return {
"task_id": task_id,
"step_id": step_id,
"state": "RUNNING",
"production_side_effects": False,
"secrets_included": False,
}
def result(self, task_id: str, payload: Mapping[str, Any]) -> dict[str, Any]:
step_id = str(payload["step_id"])
result_digest = str(payload["result_digest"])
self.orchestrator.workflow.complete(task_id, step_id, result_digest=result_digest)
self._chain(task_id).append(
"STEP_RESULT",
task_id,
{
"step_id": step_id,
"result_digest": result_digest,
"secrets_included": False,
},
)
return {
"task_id": task_id,
"step_id": step_id,
"state": "COMPLETED",
"production_side_effects": False,
"secrets_included": False,
}
def evidence(self, task_id: str) -> dict[str, Any]:
chain = self._chain(task_id)
return {
"task_id": task_id,
"events": chain.export(),
"verified": chain.verify(),
"production_side_effects": False,
"secrets_included": False,
}
def create_release_plan(self, payload: Mapping[str, Any]) -> dict[str, Any]:
plan = release_plan_from_mapping(payload)
self.release = ReleaseController()
self.release.attach_plan(plan)
self.release.mark_built()
return {
"plan": release_plan_to_mapping(plan),
"release_state": self.release.status().state.value,
"production_side_effects": False,
"secrets_included": False,
}
def verify_release(self, payload: Mapping[str, Any]) -> dict[str, Any]:
self.release.verify(
artifact_ok=payload.get("artifact_ok") is True,
evidence_ok=payload.get("evidence_ok") is True,
)
self.release.request_owner_approval()
return {
"release_state": self.release.status().state.value,
"production_release": "BLOCKED_EXTERNAL_OWNER_SIGNATURE",
"production_side_effects": False,
"secrets_included": False,
}
def task_status(self, task_id: str) -> dict[str, Any]:
store = self.orchestrator.workflow.store
state, revision = store.get_task_state(task_id)
steps = store.list_steps(task_id)
return {
"task_id": task_id,
"state": state.value,
"revision": revision,
"steps": {
step_id: {
"state": item.state.value,
"attempt": item.attempt,
"max_attempts": item.max_attempts,
"worker_id": item.worker_id,
"session_id": item.session_id,
}
for step_id, item in steps.items()
},
"production_side_effects": False,
"secrets_included": False,
}
@@ -1,116 +0,0 @@
"""Owner approval verification contracts for candidate releases."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Protocol
from .errors import ApprovalRequired, StateConflict
from .models import ApprovalEnvelope, TaskPlan, canonical_json
from .store import StateStore
class SignatureVerifier(Protocol):
"""Verify an approval payload without exposing private-key operations."""
def verify(self, key_id: str, payload: bytes, signature: str) -> bool:
"""Return ``True`` when the signature is valid for ``key_id``."""
class RevocationSource(Protocol):
def is_revoked(self, approval_plan_id: str) -> bool:
"""Return whether an approval plan has been revoked."""
@dataclass(frozen=True, slots=True)
class ApprovalVerificationResult:
approval_plan_id: str
task_id: str
signing_key_id: str
verified_at: datetime
class ApprovalVerifier:
"""Verify exact approval bindings and consume nonces before execution."""
def __init__(
self,
store: StateStore,
signature_verifier: SignatureVerifier,
revocations: RevocationSource,
) -> None:
self.store = store
self.signature_verifier = signature_verifier
self.revocations = revocations
def verify(
self,
plan: TaskPlan,
envelope: ApprovalEnvelope,
*,
now: datetime | None = None,
) -> ApprovalVerificationResult:
now = now or datetime.now(timezone.utc)
if envelope.task_id != plan.task_id:
raise ApprovalRequired("APPROVAL_TASK_MISMATCH", "approval task does not match plan")
if envelope.task_plan_sha256 != plan.digest():
raise ApprovalRequired("APPROVAL_PLAN_DIGEST_MISMATCH", "approval digest does not match plan")
if envelope.binding != plan.binding:
raise ApprovalRequired("APPROVAL_RESOURCE_MISMATCH", "approval resource binding does not match plan")
approved = set(envelope.capabilities)
required = {cap for step in plan.steps for cap in step.required_capabilities}
if not required.issubset(approved):
raise ApprovalRequired(
"APPROVAL_CAPABILITY_INSUFFICIENT",
"approval does not cover all plan capabilities",
{"missing": sorted(required - approved)},
)
if now < envelope.not_before or now >= envelope.expires_at:
raise ApprovalRequired("APPROVAL_TIME_INVALID", "approval is outside its validity window")
if self.revocations.is_revoked(envelope.approval_plan_id):
raise ApprovalRequired("APPROVAL_REVOKED", "approval plan is revoked")
payload = canonical_json(envelope.payload()).encode("utf-8")
try:
signature_valid = self.signature_verifier.verify(
envelope.signing_key_id, payload, envelope.signature
)
except Exception as error:
raise ApprovalRequired(
"BLOCKED_OWNER_APPROVAL_VERIFIER_UNAVAILABLE",
"trusted Owner approval verifier is unavailable",
) from error
if not signature_valid:
raise ApprovalRequired("APPROVAL_SIGNATURE_INVALID", "approval signature verification failed")
try:
self.store.consume_nonce(f"approval:{envelope.approval_plan_id}", envelope.nonce)
except StateConflict as error:
raise ApprovalRequired("APPROVAL_NONCE_REPLAY", "approval nonce was already consumed") from error
return ApprovalVerificationResult(
approval_plan_id=envelope.approval_plan_id,
task_id=envelope.task_id,
signing_key_id=envelope.signing_key_id,
verified_at=now,
)
class StaticRevocationSource:
def __init__(self, revoked: set[str] | None = None) -> None:
self.revoked = set(revoked or ())
def is_revoked(self, approval_plan_id: str) -> bool:
return approval_plan_id in self.revoked
class RejectingSignatureVerifier:
"""Safe default used when no external verifier has been configured."""
def verify(self, key_id: str, payload: bytes, signature: str) -> bool:
return False
class UnavailableSignatureVerifier:
"""Safe runtime default when no Owner HSM/KMS verifier has been supplied."""
def verify(self, key_id: str, payload: bytes, signature: str) -> bool:
raise ConnectionError("Owner verifier unavailable")
@@ -1,225 +0,0 @@
"""Deterministic Candidate artifact and package-closure construction."""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import io
import json
import os
from pathlib import Path, PurePosixPath
import stat
import tarfile
from typing import Mapping
from .errors import EvidenceIncomplete, ReleaseBlocked
from .models import is_git_oid
def sha256_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
@dataclass(frozen=True, slots=True)
class ArtifactFile:
path: str
content: bytes
mode: int = 0o644
def __post_init__(self) -> None:
parsed = PurePosixPath(self.path)
if (
parsed.is_absolute()
or ".." in parsed.parts
or not self.path
or "\\" in self.path
or self.path != self.path.strip()
):
raise ValueError("artifact paths must be canonical, relative and traversal-free")
if ".git" in parsed.parts:
raise ValueError(".git content is forbidden")
if self.mode not in (0o644, 0o755):
raise ValueError("artifact mode must be 0644 or 0755")
@dataclass(frozen=True, slots=True)
class ArtifactManifest:
source_oid: str
tree_oid: str
files: Mapping[str, str]
artifact_sha256: str
package_closure_sha256: str
format_version: int = 1
secrets_included: bool = False
def as_json_bytes(self) -> bytes:
payload = {
"format_version": self.format_version,
"source_oid": self.source_oid,
"tree_oid": self.tree_oid,
"files": dict(sorted(self.files.items())),
"artifact_sha256": self.artifact_sha256,
"package_closure_sha256": self.package_closure_sha256,
"secrets_included": self.secrets_included,
}
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
@property
def manifest_sha256(self) -> str:
return sha256_bytes(self.as_json_bytes())
@dataclass(frozen=True, slots=True)
class BuiltArtifact:
content: bytes
manifest: ArtifactManifest
class ArtifactBuilder:
MAX_FILE_BYTES = 8 * 1024 * 1024
MAX_ARTIFACT_BYTES = 64 * 1024 * 1024
FORBIDDEN_MARKERS = (
b"begin private " + b"key",
b"authorization:" + b" bearer ",
b"password" + b"=",
b"token" + b"=",
b"secret" + b"=",
b"api_key" + b"=",
)
@staticmethod
def _validate_oid(name: str, value: str) -> None:
if not is_git_oid(value):
raise ValueError(f"{name} must be a full lowercase Git OID")
def build_from_directory(
self, source_oid: str, tree_oid: str, source: str | Path
) -> BuiltArtifact:
root = Path(source)
if root.is_symlink() or not root.is_dir():
raise ReleaseBlocked(
"ARTIFACT_SOURCE_LINK_REJECTED", "artifact source must be a real directory"
)
files: list[ArtifactFile] = []
for directory, dirnames, filenames in os.walk(root, followlinks=False):
current = Path(directory)
for name in tuple(dirnames):
item = current / name
if item.is_symlink() or name == ".git":
raise ReleaseBlocked(
"ARTIFACT_SOURCE_LINK_REJECTED",
"artifact source contains a linked or repository directory",
)
for name in filenames:
item = current / name
info = item.lstat()
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
raise ReleaseBlocked(
"ARTIFACT_SOURCE_LINK_REJECTED",
"artifact source contains a link or special file",
)
if info.st_size > self.MAX_FILE_BYTES:
raise ReleaseBlocked("ARTIFACT_FILE_TOO_LARGE", "artifact file exceeds limit")
relative = item.relative_to(root).as_posix()
mode = 0o755 if info.st_mode & 0o111 else 0o644
files.append(ArtifactFile(relative, item.read_bytes(), mode))
return self.build(source_oid, tree_oid, tuple(files))
def build(
self, source_oid: str, tree_oid: str, files: tuple[ArtifactFile, ...]
) -> BuiltArtifact:
self._validate_oid("source_oid", source_oid)
self._validate_oid("tree_oid", tree_oid)
if not files:
raise EvidenceIncomplete("ARTIFACT_EMPTY", "artifact requires at least one file")
paths = [item.path for item in files]
if len(paths) != len(set(paths)):
raise ReleaseBlocked("ARTIFACT_DUPLICATE_PATH", "artifact contains duplicate paths")
total = 0
for item in files:
total += len(item.content)
if len(item.content) > self.MAX_FILE_BYTES or total > self.MAX_ARTIFACT_BYTES:
raise ReleaseBlocked("ARTIFACT_SIZE_LIMIT", "artifact exceeds Candidate limit")
lowered = item.content.lower()
if any(marker in lowered for marker in self.FORBIDDEN_MARKERS):
raise ReleaseBlocked(
"ARTIFACT_SECRET_MARKER",
"artifact contains a forbidden secret marker",
{"path": item.path},
)
output = io.BytesIO()
with tarfile.open(fileobj=output, mode="w", format=tarfile.PAX_FORMAT) as archive:
for item in sorted(files, key=lambda value: value.path):
info = tarfile.TarInfo(item.path)
info.size = len(item.content)
info.mode = item.mode
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = "root"
info.gname = "root"
archive.addfile(info, io.BytesIO(item.content))
content = output.getvalue()
file_hashes = {item.path: sha256_bytes(item.content) for item in files}
closure_payload = json.dumps(
{
"source_oid": source_oid,
"tree_oid": tree_oid,
"files": dict(sorted(file_hashes.items())),
"secrets_included": False,
},
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
manifest = ArtifactManifest(
source_oid=source_oid,
tree_oid=tree_oid,
files=file_hashes,
artifact_sha256=sha256_bytes(content),
package_closure_sha256=sha256_bytes(closure_payload),
secrets_included=False,
)
return BuiltArtifact(content=content, manifest=manifest)
def verify(self, built: BuiltArtifact) -> None:
if built.manifest.secrets_included:
raise EvidenceIncomplete("ARTIFACT_SECRET_FLAG", "manifest includes secrets")
if sha256_bytes(built.content) != built.manifest.artifact_sha256:
raise EvidenceIncomplete(
"ARTIFACT_DIGEST_MISMATCH", "artifact digest does not match manifest"
)
actual: dict[str, str] = {}
with tarfile.open(fileobj=io.BytesIO(built.content), mode="r:") as archive:
for member in archive.getmembers():
try:
ArtifactFile(member.name, b"", member.mode)
except ValueError as error:
raise EvidenceIncomplete(
"ARTIFACT_MEMBER_INVALID", "archive contains an unsafe member"
) from error
if not member.isfile() or member.name in actual:
raise EvidenceIncomplete(
"ARTIFACT_MEMBER_INVALID", "archive contains a link, special or duplicate member"
)
stream = archive.extractfile(member)
if stream is None:
raise EvidenceIncomplete("ARTIFACT_MEMBER_INVALID", "archive member is unreadable")
actual[member.name] = sha256_bytes(stream.read())
if actual != dict(built.manifest.files):
raise EvidenceIncomplete(
"ARTIFACT_FILE_MISMATCH", "artifact file closure does not match manifest"
)
closure_payload = json.dumps(
{
"source_oid": built.manifest.source_oid,
"tree_oid": built.manifest.tree_oid,
"files": dict(sorted(actual.items())),
"secrets_included": False,
},
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
if sha256_bytes(closure_payload) != built.manifest.package_closure_sha256:
raise EvidenceIncomplete(
"PACKAGE_CLOSURE_MISMATCH", "package closure digest does not match"
)
@@ -1,72 +0,0 @@
"""Candidate inspection CLI; all commands are local and non-production."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import sys
from .evidence import EvidenceChain
from .serialization import task_plan_from_mapping, task_plan_to_mapping
from .store import StateStore
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="aios-autonomous-release")
sub = parser.add_subparsers(dest="command", required=True)
plan = sub.add_parser("plan")
plan.add_argument("payload")
digest = sub.add_parser("digest")
digest.add_argument("payload")
status = sub.add_parser("status")
status.add_argument("--state", required=True, type=Path)
status.add_argument("--task-id", required=True)
evidence = sub.add_parser("verify-evidence")
evidence.add_argument("--state", required=True, type=Path)
evidence.add_argument("--task-id", required=True)
evidence.add_argument("--require", action="append", default=[])
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if args.command in {"plan", "digest"}:
plan = task_plan_from_mapping(json.loads(args.payload))
if args.command == "plan":
print(json.dumps(task_plan_to_mapping(plan), sort_keys=True))
else:
print(plan.digest())
return 0
store = StateStore(args.state)
if args.command == "status":
state, revision = store.get_task_state(args.task_id)
print(
json.dumps(
{
"task_id": args.task_id,
"state": state.value,
"revision": revision,
"production_side_effects": False,
"secrets_included": False,
},
sort_keys=True,
)
)
return 0
chain = EvidenceChain(store, args.task_id)
print(
json.dumps(
{
"task_id": args.task_id,
"evidence_closure": chain.close(tuple(args.require)),
"secrets_included": False,
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -1,307 +0,0 @@
"""Task-bound, reference-only Candidate credential lifecycle."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import json
import re
import secrets
from typing import Protocol
from .errors import CredentialUnavailable, PolicyDenied
from .models import (
CredentialProjection,
CredentialRequest,
CredentialState,
canonical_json,
)
from .store import StateStore
REFERENCE_RE = re.compile(
r"(?:systemd-credential:[A-Za-z0-9_.@-]+|secret-ref://candidate/[A-Za-z0-9_./@-]+)"
)
class CredentialProvider(Protocol):
"""External provider contract; values crossing this boundary are opaque references."""
def issue(self, request: CredentialRequest, expires_at: datetime) -> tuple[str, str]: ...
def revoke(self, credential_id: str, reference: str) -> None: ...
def is_usable(self, credential_id: str, reference: str) -> bool: ...
@dataclass(frozen=True, slots=True)
class CredentialLifecycleResult:
credential_id: str
reference: str
state: CredentialState
expires_at: datetime
class CredentialBroker:
"""Enforce approval, branch, consumer and operation bindings for projections."""
def __init__(self, store: StateStore, provider: CredentialProvider) -> None:
self.store = store
self.provider = provider
@staticmethod
def _validate_request(request: CredentialRequest, now: datetime) -> None:
if request.binding.branch == "main":
raise PolicyDenied("MAIN_CREDENTIAL_DENIED", "credentials cannot target main")
forbidden = {
"production.deploy",
"production.rollback",
"tag.write",
"baseline.write",
"registry.write",
"secret.export",
"secret.read",
}
overlap = forbidden.intersection(request.operations)
if overlap:
raise PolicyDenied(
"CREDENTIAL_OPERATION_FORBIDDEN",
"credential request includes forbidden operations",
{"operations": sorted(overlap)},
)
if now < request.not_before or now >= request.expires_at:
raise CredentialUnavailable(
"CREDENTIAL_APPROVAL_WINDOW_INVALID", "credential approval is not currently valid"
)
@staticmethod
def _metadata(request: CredentialRequest) -> dict[str, object]:
return {
"request_id": request.request_id,
"task_id": request.task_id,
"session_id": request.session_id,
"worker_id": request.worker_id,
"audience": request.audience,
"consumer_service": request.consumer_service,
"provider": request.provider,
"runtime_user": request.runtime_user,
"approval_plan_id": request.approval_plan_id,
"repository": request.binding.repository,
"branch": request.binding.branch,
"base_oid": request.binding.base_oid,
"allowed_paths": list(request.binding.allowed_paths),
"operations": list(request.operations),
"not_before": request.not_before.astimezone(timezone.utc).isoformat(),
"expires_at": request.expires_at.astimezone(timezone.utc).isoformat(),
"nonce": request.nonce,
"max_uses": request.max_uses,
"use_count": 0,
"secrets_included": False,
}
def _compensate(self, credential_id: str, reference: str) -> None:
try:
self.provider.revoke(credential_id, reference)
except Exception:
pass
def issue(
self, request: CredentialRequest, now: datetime | None = None
) -> CredentialProjection:
now = now or datetime.now(timezone.utc)
self._validate_request(request, now)
expires_at = min(request.expires_at, now + timedelta(seconds=request.ttl_seconds))
try:
credential_id, reference = self.provider.issue(request, expires_at)
except Exception as error:
raise CredentialUnavailable(
"CREDENTIAL_PROVIDER_UNAVAILABLE", "credential provider issue failed"
) from error
if not credential_id or not reference or not REFERENCE_RE.fullmatch(reference):
if credential_id and reference:
self._compensate(credential_id, reference)
raise CredentialUnavailable(
"CREDENTIAL_REFERENCE_REJECTED", "provider did not return an approved opaque reference"
)
projection = CredentialProjection(
credential_id=credential_id,
reference=reference,
request=request,
state=CredentialState.ISSUED,
issued_at=now,
expires_at=expires_at,
plaintext_returned=False,
)
try:
with self.store.transaction() as connection:
connection.execute(
"INSERT INTO credentials(credential_id, task_id, reference, state, metadata_json, "
"issued_at, expires_at) VALUES(?, ?, ?, ?, ?, ?, ?)",
(
credential_id,
request.task_id,
reference,
CredentialState.ISSUED.value,
canonical_json(self._metadata(request)),
now.isoformat(),
expires_at.isoformat(),
),
)
connection.execute(
"INSERT INTO consumed_nonces(scope, nonce, consumed_at) VALUES(?, ?, ?)",
(f"credential:{request.approval_plan_id}", request.nonce, now.isoformat()),
)
except Exception as error:
self._compensate(credential_id, reference)
raise CredentialUnavailable(
"CREDENTIAL_PERSISTENCE_FAILED",
"credential issuance was compensated after durable-state failure",
) from error
return projection
def project(self, projection: CredentialProjection) -> CredentialLifecycleResult:
with self.store.transaction() as connection:
cursor = connection.execute(
"UPDATE credentials SET state=? WHERE credential_id=? AND reference=? AND state=?",
(
CredentialState.PROJECTED.value,
projection.credential_id,
projection.reference,
CredentialState.ISSUED.value,
),
)
if cursor.rowcount != 1:
raise CredentialUnavailable(
"CREDENTIAL_STATE_CONFLICT", "only a durably issued credential may be projected"
)
return CredentialLifecycleResult(
projection.credential_id,
projection.reference,
CredentialState.PROJECTED,
projection.expires_at,
)
def authorize(
self,
projection: CredentialProjection,
*,
task_id: str,
session_id: str,
worker_id: str,
audience: str,
consumer_service: str,
operation: str,
path: str,
now: datetime | None = None,
) -> None:
now = now or datetime.now(timezone.utc)
request = projection.request
actual = (task_id, session_id, worker_id, audience, consumer_service)
expected = (
request.task_id,
request.session_id,
request.worker_id,
request.audience,
request.consumer_service,
)
if actual != expected:
raise CredentialUnavailable(
"CREDENTIAL_SCOPE_MISMATCH", "credential scope does not match the caller"
)
if operation not in request.operations or not request.binding.allows_path(path):
raise CredentialUnavailable(
"CREDENTIAL_OPERATION_DENIED", "credential operation or path is not approved"
)
if now < request.not_before or now >= projection.expires_at:
raise CredentialUnavailable("CREDENTIAL_EXPIRED", "credential is outside its valid window")
with self.store.transaction() as connection:
row = connection.execute(
"SELECT state, metadata_json FROM credentials WHERE credential_id=? AND reference=?",
(projection.credential_id, projection.reference),
).fetchone()
if row is None or row["state"] != CredentialState.PROJECTED.value:
raise CredentialUnavailable(
"CREDENTIAL_NOT_PROJECTED", "credential is not in projected state"
)
metadata = json.loads(row["metadata_json"])
count = int(metadata.get("use_count", 0))
if count >= request.max_uses:
raise CredentialUnavailable("CREDENTIAL_USE_LIMIT", "credential use limit reached")
if not self.provider.is_usable(projection.credential_id, projection.reference):
raise CredentialUnavailable(
"CREDENTIAL_PROVIDER_REJECTED", "provider no longer accepts the credential"
)
metadata["use_count"] = count + 1
connection.execute(
"UPDATE credentials SET metadata_json=? WHERE credential_id=?",
(canonical_json(metadata), projection.credential_id),
)
def revoke(
self, credential_id: str, reference: str, now: datetime | None = None
) -> CredentialLifecycleResult:
now = now or datetime.now(timezone.utc)
with self.store.transaction() as connection:
row = connection.execute(
"SELECT state, expires_at FROM credentials WHERE credential_id=? AND reference=?",
(credential_id, reference),
).fetchone()
if row is None:
raise KeyError(credential_id)
expires_at = datetime.fromisoformat(row["expires_at"])
if row["state"] == CredentialState.REVOKED.value:
return CredentialLifecycleResult(
credential_id, reference, CredentialState.REVOKED, expires_at
)
try:
self.provider.revoke(credential_id, reference)
except Exception as error:
raise CredentialUnavailable(
"CREDENTIAL_REVOCATION_FAILED", "upstream credential revocation failed"
) from error
with self.store.transaction() as connection:
connection.execute(
"UPDATE credentials SET state=?, revoked_at=? WHERE credential_id=? AND reference=?",
(CredentialState.REVOKED.value, now.isoformat(), credential_id, reference),
)
return CredentialLifecycleResult(
credential_id, reference, CredentialState.REVOKED, expires_at
)
def verify_revoked(self, credential_id: str, reference: str) -> None:
with self.store.transaction() as connection:
row = connection.execute(
"SELECT state FROM credentials WHERE credential_id=? AND reference=?",
(credential_id, reference),
).fetchone()
if row is None or row["state"] != CredentialState.REVOKED.value:
raise CredentialUnavailable(
"CREDENTIAL_REVOCATION_UNRECORDED", "durable revocation record is missing"
)
if self.provider.is_usable(credential_id, reference):
raise CredentialUnavailable(
"CREDENTIAL_REVOCATION_UNVERIFIED",
"provider still accepts revoked credential",
{"credential_id": credential_id},
)
class InMemoryCredentialProvider:
"""Candidate test provider; never suitable as production identity evidence."""
def __init__(self) -> None:
self._active: dict[str, str] = {}
def issue(self, request: CredentialRequest, expires_at: datetime) -> tuple[str, str]:
credential_id = f"cred-{secrets.token_hex(8)}"
reference = f"secret-ref://candidate/{request.task_id}/{credential_id}"
self._active[credential_id] = reference
return credential_id, reference
def revoke(self, credential_id: str, reference: str) -> None:
if self._active.get(credential_id) != reference:
raise CredentialUnavailable("CREDENTIAL_NOT_FOUND", "credential is not active")
del self._active[credential_id]
def is_usable(self, credential_id: str, reference: str) -> bool:
return self._active.get(credential_id) == reference
@@ -1,48 +0,0 @@
"""Stable error types and codes for the autonomous-release candidate."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping
@dataclass(slots=True)
class AutonomousReleaseError(RuntimeError):
"""Base error with a stable machine-readable code."""
code: str
message: str
details: Mapping[str, Any] | None = None
def __str__(self) -> str:
if not self.details:
return f"{self.code}: {self.message}"
return f"{self.code}: {self.message} ({dict(self.details)})"
class PolicyDenied(AutonomousReleaseError):
pass
class ApprovalRequired(AutonomousReleaseError):
pass
class CapabilityMismatch(AutonomousReleaseError):
pass
class CredentialUnavailable(AutonomousReleaseError):
pass
class EvidenceIncomplete(AutonomousReleaseError):
pass
class ReleaseBlocked(AutonomousReleaseError):
pass
class StateConflict(AutonomousReleaseError):
pass
@@ -1,136 +0,0 @@
"""Durable, replayable and tamper-evident evidence closure."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import json
from typing import Any, Mapping, TYPE_CHECKING
from .errors import EvidenceIncomplete
from .models import sha256_json
if TYPE_CHECKING:
from .store import StateStore
@dataclass(frozen=True, slots=True)
class EvidenceEvent:
event_type: str
task_id: str
payload: Mapping[str, Any]
previous_digest: str | None
created_at: datetime
def event_digest(self) -> str:
return sha256_json(
{
"event_type": self.event_type,
"task_id": self.task_id,
"payload": dict(self.payload),
"previous_digest": self.previous_digest,
"created_at": self.created_at.astimezone(timezone.utc).isoformat(),
}
)
class EvidenceChain:
"""Append-only chain optionally backed by the Candidate state database."""
def __init__(self, store: StateStore | None = None, task_id: str | None = None) -> None:
if (store is None) != (task_id is None):
raise ValueError("store and task_id must be supplied together")
self.store = store
self.task_id = task_id
self._records: list[tuple[EvidenceEvent, str]] = []
if store is not None and task_id is not None:
self._load()
def _load(self) -> None:
assert self.store is not None and self.task_id is not None
self._records = []
for row in self.store.list_events(self.task_id):
event = EvidenceEvent(
event_type=str(row["event_type"]),
task_id=str(row["task_id"]),
payload=json.loads(str(row["payload_json"])),
previous_digest=row["previous_digest"],
created_at=datetime.fromisoformat(str(row["created_at"])),
)
self._records.append((event, str(row["digest"])))
def append(
self, event_type: str, task_id: str, payload: Mapping[str, Any]
) -> EvidenceEvent:
if not event_type or not task_id:
raise EvidenceIncomplete("EVIDENCE_IDENTITY_REQUIRED", "event and task IDs are required")
if self.task_id is not None and task_id != self.task_id:
raise EvidenceIncomplete(
"EVIDENCE_TASK_MISMATCH", "event task does not match the durable chain"
)
if payload.get("secrets_included") is not False:
raise EvidenceIncomplete(
"EVIDENCE_SECRET_MARKER_REQUIRED",
"evidence must explicitly assert secrets_included=false",
)
previous = self._records[-1][1] if self._records else None
event = EvidenceEvent(
event_type=event_type,
task_id=task_id,
payload=dict(payload),
previous_digest=previous,
created_at=datetime.now(timezone.utc),
)
event_digest = event.event_digest()
if self.store is not None:
self.store.append_event(
task_id,
event_type,
payload,
previous,
event_digest,
event.created_at,
)
self._records.append((event, event_digest))
return event
def verify(self) -> bool:
previous: str | None = None
for event, recorded_digest in self._records:
if event.previous_digest != previous or event.event_digest() != recorded_digest:
return False
previous = recorded_digest
return True
def close(self, required_event_types: tuple[str, ...]) -> str:
if not self._records:
raise EvidenceIncomplete("EMPTY_EVIDENCE_CHAIN", "cannot close an empty evidence chain")
if not self.verify():
raise EvidenceIncomplete(
"EVIDENCE_CHAIN_INVALID", "evidence chain verification failed"
)
found = {event.event_type for event, _ in self._records}
missing = sorted(set(required_event_types) - found)
if missing:
raise EvidenceIncomplete(
"EVIDENCE_REQUIREMENT_MISSING",
"required evidence events are absent",
{"missing": missing},
)
return self._records[-1][1]
def closure_digest(self) -> str:
return self.close(())
def export(self) -> list[dict[str, Any]]:
return [
{
"event_type": event.event_type,
"task_id": event.task_id,
"payload": dict(event.payload),
"previous_digest": event.previous_digest,
"digest": recorded_digest,
"created_at": event.created_at.isoformat(),
}
for event, recorded_digest in self._records
]
@@ -1,105 +0,0 @@
"""Native Candidate execution lifecycle with mandatory cleanup evidence."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping
from .evidence import EvidenceChain
from .native_adapters import CandidateNativeAdapter, NativeSession
@dataclass(frozen=True, slots=True)
class NativeCanaryResult:
provider: str
broker_session_id: str
provider_session_id: str
message_id: str
receipt: str
broker_session_verified: bool
openclaw_native_child_session_verified: bool
codex_child_session_verified: bool
simulation: bool
session_closed: bool
secrets_included: bool = False
class ExecutionManager:
def __init__(self, evidence: EvidenceChain) -> None:
self.evidence = evidence
def run_native_canary(
self,
adapter: CandidateNativeAdapter,
*,
task_id: str,
parent_session_id: str,
recipient_binding: Mapping[str, Any],
text: str,
) -> NativeCanaryResult:
session: NativeSession | None = None
closed = False
try:
session = adapter.spawn(task_id, parent_session_id, recipient_binding)
verification = adapter.verify(session)
message = adapter.send_text(session, text)
adapter.interrupt(session)
adapter.close(session)
closed = True
event_type = (
"NATIVE_SESSION_SIMULATION"
if verification.simulation
else "NATIVE_SESSION_VERIFIED"
)
self.evidence.append(
event_type,
task_id,
{
"provider": session.provider,
"broker_session_id": session.broker_session_id,
"provider_session_id": session.provider_session_id,
"message_id": message.message_id,
"receipt": message.receipt,
"broker_session_verified": verification.broker_session_verified,
"openclaw_native_child_session_verified": (
verification.openclaw_native_child_session_verified
),
"codex_child_session_verified": verification.codex_child_session_verified,
"simulation": verification.simulation,
"session_closed": True,
"secrets_included": False,
},
)
return NativeCanaryResult(
provider=session.provider,
broker_session_id=session.broker_session_id,
provider_session_id=session.provider_session_id,
message_id=message.message_id,
receipt=message.receipt,
broker_session_verified=verification.broker_session_verified,
openclaw_native_child_session_verified=(
verification.openclaw_native_child_session_verified
),
codex_child_session_verified=verification.codex_child_session_verified,
simulation=verification.simulation,
session_closed=True,
secrets_included=False,
)
except Exception as error:
if session is not None and not closed:
try:
adapter.close(session)
closed = True
except Exception:
closed = False
self.evidence.append(
"NATIVE_SESSION_FAILED",
task_id,
{
"provider": adapter.provider,
"error_code": type(error).__name__,
"session_closed": closed,
"secrets_included": False,
},
)
raise
@@ -1,172 +0,0 @@
"""API-only Gitea deploy-key lifecycle canary."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, Protocol, Sequence
from .errors import CredentialUnavailable, StateConflict
GITEA_REFERENCE = "systemd-credential:gitea-admin-api-candidate"
@dataclass(frozen=True, slots=True)
class EphemeralKeyProjection:
public_key: str
private_key_reference: str
class KeyProjectionProvider(Protocol):
def create(self, task_id: str, branch: str) -> EphemeralKeyProjection: ...
def delete(self, reference: str) -> None: ...
def is_present(self, reference: str) -> bool: ...
class GiteaDeployKeyProvider(Protocol):
simulation: bool
def list_deploy_keys(
self, repository: str, credential_reference: str
) -> Sequence[Mapping[str, Any]]: ...
def create_deploy_key(
self,
repository: str,
title: str,
public_key: str,
credential_reference: str,
) -> Mapping[str, Any]: ...
def delete_deploy_key(
self, repository: str, key_id: int, credential_reference: str
) -> None: ...
class SSHAuthenticationProbe(Protocol):
def authenticate(
self, repository: str, branch: str, private_key_reference: str
) -> bool: ...
@dataclass(frozen=True, slots=True)
class GiteaCanaryResult:
key_created: bool
key_deleted: bool
api_key_absent_after_delete: bool
post_revoke_auth_rejected: bool
credential_projection_deleted: bool
gitea_admin_api_lifecycle_canary: bool
simulation: bool
secrets_included: bool = False
class GiteaDeployKeyCanary:
"""Exercise only repository deploy-key list/create/delete provider operations."""
repository = "aios/aios"
def __init__(
self,
gitea: GiteaDeployKeyProvider,
projections: KeyProjectionProvider,
ssh_probe: SSHAuthenticationProbe,
) -> None:
self.gitea = gitea
self.projections = projections
self.ssh_probe = ssh_probe
def run(
self, *, task_id: str, branch: str, credential_reference: str
) -> GiteaCanaryResult:
if credential_reference != GITEA_REFERENCE:
raise CredentialUnavailable(
"BLOCKED_GITEA_ADMIN_CREDENTIAL_REQUIRED",
"exact Candidate Gitea Admin API reference is required",
)
if not task_id or not branch.startswith("patch/"):
raise StateConflict("GITEA_CANARY_BINDING_INVALID", "task and patch branch are required")
projection: EphemeralKeyProjection | None = None
key_id: int | None = None
key_created = False
key_deleted = False
api_absent = False
auth_rejected = False
projection_deleted = False
title = f"aios-candidate-{task_id}"
try:
projection = self.projections.create(task_id, branch)
if (
not projection.public_key.startswith("ssh-ed25519 ")
or not projection.private_key_reference.startswith("systemd-credential:")
):
raise CredentialUnavailable(
"GITEA_CANARY_KEY_PROJECTION_INVALID",
"ephemeral key projection is not reference-only",
)
before = self.gitea.list_deploy_keys(self.repository, credential_reference)
if any(item.get("title") == title for item in before):
raise StateConflict("GITEA_CANARY_KEY_COLLISION", "canary key title already exists")
created = self.gitea.create_deploy_key(
self.repository,
title,
projection.public_key,
credential_reference,
)
raw_id = created.get("id")
if not isinstance(raw_id, int) or created.get("key") != projection.public_key:
raise StateConflict("GITEA_CANARY_CREATE_INVALID", "created key response is invalid")
key_id = raw_id
key_created = True
visible = self.gitea.list_deploy_keys(self.repository, credential_reference)
if not any(item.get("id") == key_id for item in visible):
raise StateConflict("GITEA_CANARY_KEY_NOT_VISIBLE", "created key is not listed")
if not self.ssh_probe.authenticate(
self.repository, branch, projection.private_key_reference
):
raise StateConflict("GITEA_CANARY_SSH_AUTH_FAILED", "deploy key could not authenticate")
self.gitea.delete_deploy_key(self.repository, key_id, credential_reference)
key_deleted = True
key_id = None
after = self.gitea.list_deploy_keys(self.repository, credential_reference)
api_absent = not any(item.get("title") == title for item in after)
if not api_absent:
raise StateConflict("GITEA_CANARY_DELETE_UNVERIFIED", "deleted key remains visible")
self.projections.delete(projection.private_key_reference)
projection_deleted = not self.projections.is_present(projection.private_key_reference)
auth_rejected = not self.ssh_probe.authenticate(
self.repository, branch, projection.private_key_reference
)
if not projection_deleted or not auth_rejected:
raise StateConflict(
"GITEA_CANARY_REVOCATION_UNVERIFIED",
"projection deletion or post-revocation SSH rejection failed",
)
simulated = bool(getattr(self.gitea, "simulation", True))
return GiteaCanaryResult(
key_created=key_created,
key_deleted=key_deleted,
api_key_absent_after_delete=api_absent,
post_revoke_auth_rejected=auth_rejected,
credential_projection_deleted=projection_deleted,
gitea_admin_api_lifecycle_canary=not simulated,
simulation=simulated,
secrets_included=False,
)
finally:
if key_id is not None:
try:
self.gitea.delete_deploy_key(
self.repository, key_id, credential_reference
)
except Exception:
pass
if projection is not None and self.projections.is_present(
projection.private_key_reference
):
try:
self.projections.delete(projection.private_key_reference)
except Exception:
pass
@@ -1,376 +0,0 @@
"""Typed contracts for the dormant autonomous-release candidate.
This package is dependency-free by design so it can be audited and exercised in
an isolated candidate worktree before any production service is installed.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
import hashlib
import json
from typing import Any, Mapping, Sequence
def utc_now() -> datetime:
"""Return an aware UTC timestamp."""
return datetime.now(timezone.utc)
def canonical_json(value: Any) -> str:
"""Serialize *value* deterministically for signatures and evidence hashes."""
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def sha256_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def is_sha256(value: str) -> bool:
return len(value) == 64 and all(ch in "0123456789abcdef" for ch in value)
def is_git_oid(value: str) -> bool:
return len(value) == 40 and all(ch in "0123456789abcdef" for ch in value)
class RiskClass(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class TaskState(str, Enum):
CREATED = "CREATED"
PLANNED = "PLANNED"
READY = "READY"
RUNNING = "RUNNING"
BLOCKED = "BLOCKED"
FAILED = "FAILED"
COMPLETED = "COMPLETED"
READY_FOR_OWNER_APPROVAL = "READY_FOR_OWNER_APPROVAL"
class StepState(str, Enum):
PENDING = "PENDING"
READY = "READY"
CLAIMED = "CLAIMED"
RUNNING = "RUNNING"
BLOCKED = "BLOCKED"
FAILED = "FAILED"
COMPLETED = "COMPLETED"
class CredentialState(str, Enum):
REQUESTED = "REQUESTED"
ISSUED = "ISSUED"
PROJECTED = "PROJECTED"
REVOKED = "REVOKED"
EXPIRED = "EXPIRED"
@dataclass(frozen=True, slots=True)
class ResourceBinding:
repository: str
branch: str
base_oid: str
allowed_paths: tuple[str, ...] = ()
def __post_init__(self) -> None:
if not self.repository or "/" not in self.repository:
raise ValueError("repository must be owner/name")
if self.branch == "main" or not self.branch.startswith(("patch/", "integration/", "candidate/")):
raise ValueError("candidate execution requires a governed non-main branch")
if not is_git_oid(self.base_oid):
raise ValueError("base_oid must be a full lowercase 40-character Git OID")
for path in self.allowed_paths:
if not path or path.startswith("/") or ".." in path.split("/"):
raise ValueError("allowed_paths must be repository-relative and traversal-free")
def allows_path(self, path: str) -> bool:
if not path or path.startswith("/") or ".." in path.split("/"):
return False
return any(path == prefix or path.startswith(prefix.rstrip("/") + "/") for prefix in self.allowed_paths)
@dataclass(frozen=True, slots=True)
class ApprovalEnvelope:
approval_plan_id: str
task_id: str
task_plan_sha256: str
binding: ResourceBinding
capabilities: tuple[str, ...]
risk: RiskClass
not_before: datetime
expires_at: datetime
nonce: str
signing_key_id: str
signature: str
def __post_init__(self) -> None:
if self.expires_at <= self.not_before:
raise ValueError("approval expiry must be after not_before")
if not is_sha256(self.task_plan_sha256):
raise ValueError("task_plan_sha256 must be a SHA-256 hex digest")
if not self.approval_plan_id or not self.task_id or not self.nonce:
raise ValueError("approval identifiers and nonce are required")
def payload(self) -> Mapping[str, Any]:
return {
"approval_plan_id": self.approval_plan_id,
"task_id": self.task_id,
"task_plan_sha256": self.task_plan_sha256,
"repository": self.binding.repository,
"branch": self.binding.branch,
"base_oid": self.binding.base_oid,
"allowed_paths": list(self.binding.allowed_paths),
"capabilities": list(self.capabilities),
"risk": self.risk.value,
"not_before": self.not_before.astimezone(timezone.utc).isoformat(),
"expires_at": self.expires_at.astimezone(timezone.utc).isoformat(),
"nonce": self.nonce,
"signing_key_id": self.signing_key_id,
}
@dataclass(frozen=True, slots=True)
class WorkflowStep:
step_id: str
worker_role: str
required_capabilities: tuple[str, ...]
depends_on: tuple[str, ...] = ()
max_attempts: int = 1
timeout_seconds: int = 900
allowed_paths: tuple[str, ...] = ()
def __post_init__(self) -> None:
if not self.step_id or not self.worker_role:
raise ValueError("step_id and worker_role are required")
if self.max_attempts < 1 or self.max_attempts > 5:
raise ValueError("max_attempts must be between 1 and 5")
if self.timeout_seconds < 1 or self.timeout_seconds > 86_400:
raise ValueError("timeout_seconds must be between 1 and 86400")
if self.step_id in self.depends_on:
raise ValueError("a step cannot depend on itself")
@dataclass(frozen=True, slots=True)
class TaskPlan:
task_id: str
intent: str
binding: ResourceBinding
risk: RiskClass
steps: tuple[WorkflowStep, ...]
evidence_requirements: tuple[str, ...]
production_side_effects_allowed: bool = False
created_at: datetime = field(default_factory=utc_now)
def __post_init__(self) -> None:
if not self.task_id or not self.intent:
raise ValueError("task_id and intent are required")
if self.production_side_effects_allowed:
raise ValueError("candidate TaskPlan must not allow production side effects")
ids = [step.step_id for step in self.steps]
if len(ids) != len(set(ids)):
raise ValueError("step IDs must be unique")
known = set(ids)
for step in self.steps:
missing = set(step.depends_on) - known
if missing:
raise ValueError(f"step {step.step_id} has unknown dependencies: {sorted(missing)}")
self._validate_acyclic()
def _validate_acyclic(self) -> None:
dependencies = {step.step_id: set(step.depends_on) for step in self.steps}
pending = set(dependencies)
while pending:
ready = {step_id for step_id in pending if not (dependencies[step_id] & pending)}
if not ready:
raise ValueError("workflow contains a dependency cycle")
pending -= ready
def digest(self) -> str:
return sha256_json(
{
"task_id": self.task_id,
"intent": self.intent,
"repository": self.binding.repository,
"branch": self.binding.branch,
"base_oid": self.binding.base_oid,
"allowed_paths": list(self.binding.allowed_paths),
"risk": self.risk.value,
"steps": [
{
"step_id": step.step_id,
"worker_role": step.worker_role,
"required_capabilities": list(step.required_capabilities),
"depends_on": list(step.depends_on),
"max_attempts": step.max_attempts,
"timeout_seconds": step.timeout_seconds,
"allowed_paths": list(step.allowed_paths),
}
for step in self.steps
],
"evidence_requirements": list(self.evidence_requirements),
"production_side_effects_allowed": self.production_side_effects_allowed,
"created_at": self.created_at.astimezone(timezone.utc).isoformat(),
}
)
@dataclass(frozen=True, slots=True)
class WorkerDescriptor:
worker_id: str
role: str
capabilities: frozenset[str]
trust_zone: str
endpoint_id: str
online: bool = True
current_load: int = 0
max_concurrency: int = 1
def can_accept(self, required: Sequence[str], trust_zone: str) -> bool:
return (
self.online
and self.trust_zone == trust_zone
and self.current_load < self.max_concurrency
and set(required).issubset(self.capabilities)
)
@dataclass(frozen=True, slots=True)
class CredentialRequest:
request_id: str
task_id: str
session_id: str
worker_id: str
audience: str
consumer_service: str
binding: ResourceBinding
operations: tuple[str, ...]
provider: str
runtime_user: str
approval_plan_id: str
not_before: datetime
expires_at: datetime
nonce: str
ttl_seconds: int
max_uses: int
def __post_init__(self) -> None:
if self.ttl_seconds < 1 or self.ttl_seconds > 3600:
raise ValueError("credential TTL must be between 1 and 3600 seconds")
if self.max_uses < 1 or self.max_uses > 1000:
raise ValueError("credential max_uses must be between 1 and 1000")
if not self.operations:
raise ValueError("credential operations cannot be empty")
required = (
self.request_id,
self.task_id,
self.session_id,
self.worker_id,
self.audience,
self.consumer_service,
self.provider,
self.runtime_user,
self.approval_plan_id,
self.nonce,
)
if any(not item or item != item.strip() for item in required):
raise ValueError("credential identity, scope and approval fields are required")
if len(set(self.operations)) != len(self.operations):
raise ValueError("credential operations must be unique")
if self.not_before.tzinfo is None or self.expires_at.tzinfo is None:
raise ValueError("credential time bounds must be timezone aware")
if self.expires_at <= self.not_before:
raise ValueError("credential expires_at must follow not_before")
if (self.expires_at - self.not_before).total_seconds() > self.ttl_seconds:
raise ValueError("credential approval window exceeds requested TTL")
@dataclass(frozen=True, slots=True)
class CredentialProjection:
credential_id: str
reference: str
request: CredentialRequest
state: CredentialState
issued_at: datetime
expires_at: datetime
plaintext_returned: bool = False
def __post_init__(self) -> None:
if self.plaintext_returned:
raise ValueError("credential plaintext must never be returned")
if self.expires_at <= self.issued_at:
raise ValueError("credential expiry must be after issuance")
@dataclass(frozen=True, slots=True)
class ResultEnvelope:
task_id: str
step_id: str
worker_id: str
session_id: str
claim_id: str
operation_digest: str
result_digest: str
evidence_digests: tuple[str, ...]
completed_at: datetime = field(default_factory=utc_now)
def __post_init__(self) -> None:
if not is_sha256(self.operation_digest) or not is_sha256(self.result_digest):
raise ValueError("operation_digest and result_digest must be SHA-256 values")
if any(not is_sha256(digest) for digest in self.evidence_digests):
raise ValueError("all evidence digests must be SHA-256 values")
@dataclass(frozen=True, slots=True)
class ReleasePlan:
task_id: str
repository: str
branch: str
version: str
target_oid: str
tree_oid: str
artifact_sha256: str
manifest_sha256: str
package_closure_sha256: str
evidence_closure_oid: str
approval_plan_id: str
rollback_approval_plan_id: str
rollback_artifact_sha256: str
expected_tag_oid: str
expected_target_oid: str
production_config_sha256: str
def __post_init__(self) -> None:
for name in ("target_oid", "tree_oid", "evidence_closure_oid", "expected_target_oid"):
if not is_git_oid(getattr(self, name)):
raise ValueError(f"{name} must be a full lowercase 40-character Git OID")
for name in (
"artifact_sha256",
"manifest_sha256",
"package_closure_sha256",
"rollback_artifact_sha256",
"production_config_sha256",
):
if not is_sha256(getattr(self, name)):
raise ValueError(f"{name} must be a SHA-256 hex digest")
if not is_git_oid(self.expected_tag_oid):
raise ValueError("expected_tag_oid must be a full lowercase 40-character Git OID")
if (
not self.task_id
or "/" not in self.repository
or not self.branch.startswith("integration/")
or not self.version
or not self.approval_plan_id
or not self.rollback_approval_plan_id
):
raise ValueError("release identifiers are required")
if self.approval_plan_id == self.rollback_approval_plan_id:
raise ValueError("deployment and rollback approvals must be distinct")
if self.target_oid != self.expected_target_oid:
raise ValueError("target_oid must equal expected_target_oid")
@@ -1,246 +0,0 @@
"""Candidate-only OpenClaw and Codex native session adapter contracts."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, Protocol, Sequence
import uuid
from .errors import StateConflict
class NativeProvider(Protocol):
simulation: bool
def spawn(
self, request: Mapping[str, Any], credential_reference: str
) -> Mapping[str, Any]: ...
def status(
self, provider_session_id: str, credential_reference: str
) -> Mapping[str, Any]: ...
def list_tree(
self, parent_session_id: str, credential_reference: str
) -> Sequence[Mapping[str, Any]]: ...
def send_text(
self,
provider_session_id: str,
payload: Mapping[str, Any],
credential_reference: str,
) -> Mapping[str, Any]: ...
def interrupt(self, provider_session_id: str, credential_reference: str) -> None: ...
def close(self, provider_session_id: str, credential_reference: str) -> None: ...
@dataclass(frozen=True, slots=True)
class NativeSession:
provider: str
broker_session_id: str
provider_session_id: str
task_id: str
parent_session_id: str
recipient_binding: Mapping[str, str]
@dataclass(frozen=True, slots=True)
class SessionVerification:
broker_session_verified: bool
openclaw_native_child_session_verified: bool
codex_child_session_verified: bool
simulation: bool
@dataclass(frozen=True, slots=True)
class TextResult:
content_type: str
encoding: str
image_only: bool
task_id: str
message_id: str
receipt: str
recipient_binding: Mapping[str, str]
secrets_included: bool
class CandidateNativeAdapter:
provider = ""
required_reference = ""
def __init__(self, client: NativeProvider, credential_reference: str) -> None:
if credential_reference != self.required_reference:
raise StateConflict(
"CANDIDATE_CREDENTIAL_REFERENCE_MISMATCH",
"adapter requires its exact Candidate credential reference",
)
self.client = client
self.credential_reference = credential_reference
@staticmethod
def _recipient(value: Mapping[str, Any]) -> dict[str, str]:
recipient_id = value.get("recipient_id")
if (
not isinstance(recipient_id, str)
or not recipient_id
or isinstance(value.get("recipients"), (list, tuple))
):
raise StateConflict(
"RECIPIENT_BINDING_REQUIRED",
"one trusted TaskPlan recipient binding is required",
)
return {str(key): str(item) for key, item in value.items()}
def spawn(
self,
task_id: str,
parent_session_id: str,
recipient_binding: Mapping[str, Any],
) -> NativeSession:
if not task_id or not parent_session_id:
raise StateConflict("NATIVE_SESSION_BINDING_REQUIRED", "task and parent are required")
recipient = self._recipient(recipient_binding)
response = self.client.spawn(
{
"task_id": task_id,
"parent_session_id": parent_session_id,
"recipient_binding": recipient,
"candidate_only": True,
"production_session_access": False,
},
self.credential_reference,
)
provider_session_id = response.get("provider_session_id")
if (
not isinstance(provider_session_id, str)
or not provider_session_id
or response.get("task_id") != task_id
or response.get("parent_session_id") != parent_session_id
or response.get("candidate_only") is not True
or response.get("production_session_access") is not False
):
raise StateConflict(
"NATIVE_SESSION_PROVISIONING_INVALID",
"provider response is not bound to this Candidate task",
)
broker_id = f"broker-{uuid.uuid4()}"
if broker_id == provider_session_id:
raise StateConflict("SESSION_ID_COLLISION", "broker and provider IDs must differ")
return NativeSession(
provider=self.provider,
broker_session_id=broker_id,
provider_session_id=provider_session_id,
task_id=task_id,
parent_session_id=parent_session_id,
recipient_binding=recipient,
)
def verify(self, session: NativeSession) -> SessionVerification:
self._validate_session(session)
status = self.client.status(session.provider_session_id, self.credential_reference)
tree = self.client.list_tree(session.parent_session_id, self.credential_reference)
status_valid = (
status.get("provider_session_id") == session.provider_session_id
and status.get("state") in {"PROVISIONED", "ACTIVE", "RUNNING"}
)
tree_valid = any(
item.get("provider_session_id") == session.provider_session_id
and item.get("parent_session_id") == session.parent_session_id
for item in tree
)
if not status_valid or not tree_valid:
raise StateConflict(
"NATIVE_SESSION_VERIFICATION_FAILED",
"provider status or parent/child tree is inconsistent",
)
simulated = bool(getattr(self.client, "simulation", True))
return SessionVerification(
broker_session_verified=True,
openclaw_native_child_session_verified=(
self.provider == "openclaw" and not simulated
),
codex_child_session_verified=(self.provider == "codex" and not simulated),
simulation=simulated,
)
def send_text(self, session: NativeSession, text: str) -> TextResult:
self._validate_session(session)
if not isinstance(text, str) or not text:
raise StateConflict("TEXT_RESULT_REQUIRED", "non-empty text is required")
response = self.client.send_text(
session.provider_session_id,
{
"task_id": session.task_id,
"content_type": "text/plain",
"encoding": "utf-8",
"image_only": False,
"text": text,
"recipient_binding": dict(session.recipient_binding),
"secrets_included": False,
},
self.credential_reference,
)
required = {
"content_type": "text/plain",
"encoding": "utf-8",
"image_only": False,
"task_id": session.task_id,
"recipient_binding": dict(session.recipient_binding),
"secrets_included": False,
}
if any(response.get(key) != value for key, value in required.items()):
raise StateConflict("TEXT_RESULT_CONTRACT_INVALID", "text result binding is invalid")
message_id = response.get("message_id")
receipt = response.get("receipt")
if not isinstance(message_id, str) or not message_id or not isinstance(receipt, str) or not receipt:
raise StateConflict("TEXT_RESULT_RECEIPT_REQUIRED", "message and receipt are required")
return TextResult(
content_type="text/plain",
encoding="utf-8",
image_only=False,
task_id=session.task_id,
message_id=message_id,
receipt=receipt,
recipient_binding=dict(session.recipient_binding),
secrets_included=False,
)
def status(self, session: NativeSession) -> str:
self._validate_session(session)
response = self.client.status(session.provider_session_id, self.credential_reference)
if response.get("provider_session_id") != session.provider_session_id:
raise StateConflict("NATIVE_SESSION_STATUS_INVALID", "provider session ID changed")
state = response.get("state")
if not isinstance(state, str) or not state:
raise StateConflict("NATIVE_SESSION_STATUS_INVALID", "provider state is missing")
return state
def interrupt(self, session: NativeSession) -> None:
self._validate_session(session)
self.client.interrupt(session.provider_session_id, self.credential_reference)
if self.status(session) != "INTERRUPTED":
raise StateConflict("NATIVE_SESSION_INTERRUPT_FAILED", "interrupt was not observed")
def close(self, session: NativeSession) -> None:
self._validate_session(session)
self.client.close(session.provider_session_id, self.credential_reference)
if self.status(session) != "CLOSED":
raise StateConflict("NATIVE_SESSION_CLOSE_FAILED", "close was not observed")
def _validate_session(self, session: NativeSession) -> None:
if session.provider != self.provider:
raise StateConflict("NATIVE_SESSION_PROVIDER_MISMATCH", "session provider differs")
if session.broker_session_id == session.provider_session_id:
raise StateConflict("SESSION_ID_COLLISION", "broker and provider IDs must differ")
class OpenClawNativeAdapter(CandidateNativeAdapter):
provider = "openclaw"
required_reference = "systemd-credential:openclaw-gateway-candidate"
class CodexNativeAdapter(CandidateNativeAdapter):
provider = "codex"
required_reference = "systemd-credential:codex-app-server-candidate"
@@ -1,54 +0,0 @@
"""High-level autonomous release orchestration facade."""
from __future__ import annotations
from dataclasses import dataclass
from .evidence import EvidenceChain
from .models import TaskPlan
from .policy import ReleasePolicy
from .workflow import WorkflowEngine
@dataclass(frozen=True, slots=True)
class OrchestrationResult:
task_id: str
plan_digest: str
evidence_closure: str | None
production_side_effects: bool
class AutonomousReleaseOrchestrator:
"""Coordinates candidate workflows while keeping production disabled."""
def __init__(self, workflow: WorkflowEngine, policy: ReleasePolicy) -> None:
self.workflow = workflow
self.policy = policy
self.evidence: EvidenceChain | None = None
def create(self, plan: TaskPlan) -> OrchestrationResult:
self.policy.validate_task(plan)
self.workflow.plan(plan)
self.evidence = EvidenceChain(self.workflow.store, plan.task_id)
self.evidence.append(
"TASK_CREATED",
plan.task_id,
{"plan_digest": plan.digest(), "secrets_included": False},
)
return OrchestrationResult(
task_id=plan.task_id,
plan_digest=plan.digest(),
evidence_closure=None,
production_side_effects=False,
)
def close_evidence(self, task_id: str) -> OrchestrationResult:
if self.evidence is None or self.evidence.task_id != task_id:
self.evidence = EvidenceChain(self.workflow.store, task_id)
closure = self.evidence.closure_digest()
return OrchestrationResult(
task_id=task_id,
plan_digest="",
evidence_closure=closure,
production_side_effects=False,
)
@@ -1,75 +0,0 @@
"""Deterministic task planning for autonomous candidate releases."""
from __future__ import annotations
from dataclasses import dataclass
from .models import ResourceBinding, RiskClass, TaskPlan, WorkflowStep
@dataclass(frozen=True, slots=True)
class PlanningRequest:
task_id: str
intent: str
binding: ResourceBinding
risk: RiskClass = RiskClass.MEDIUM
class DeterministicPlanner:
"""Create a fixed governed DAG without granting production capabilities."""
def build(self, request: PlanningRequest) -> TaskPlan:
steps = (
WorkflowStep(
step_id="architecture-review",
worker_role="hermes-review",
required_capabilities=("architecture.review", "risk.analysis"),
allowed_paths=request.binding.allowed_paths,
),
WorkflowStep(
step_id="implementation",
worker_role="dedicated-writer",
required_capabilities=("commit_file.atomic", "git.push.patch_branch"),
depends_on=("architecture-review",),
max_attempts=2,
allowed_paths=request.binding.allowed_paths,
),
WorkflowStep(
step_id="code-review",
worker_role="codex-review",
required_capabilities=("code.review", "git.diff.read"),
depends_on=("implementation",),
allowed_paths=request.binding.allowed_paths,
),
WorkflowStep(
step_id="validation",
worker_role="openclaw-shadow",
required_capabilities=("test.execute", "artifact.verify"),
depends_on=("code-review",),
max_attempts=2,
allowed_paths=request.binding.allowed_paths,
),
WorkflowStep(
step_id="evidence-finalize",
worker_role="evidence-controller",
required_capabilities=("evidence.finalize",),
depends_on=("validation",),
allowed_paths=request.binding.allowed_paths,
),
)
return TaskPlan(
task_id=request.task_id,
intent=request.intent,
binding=request.binding,
risk=request.risk,
steps=steps,
evidence_requirements=(
"git-ref-closure",
"test-report",
"secret-scan",
"artifact-manifest",
"credential-revocation",
"production-invariants",
),
production_side_effects_allowed=False,
)
@@ -1,54 +0,0 @@
"""Fail-closed policy engine for autonomous release workflows."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from .errors import ApprovalRequired, CapabilityMismatch, PolicyDenied
from .models import ApprovalEnvelope, ResourceBinding, RiskClass, TaskPlan, WorkerDescriptor
FORBIDDEN_CAPABILITIES = frozenset({
"production.deploy",
"production.rollback",
"main.write",
"tag.write",
"baseline.write",
"registry.write",
"secret.export",
})
@dataclass(frozen=True, slots=True)
class PolicyDecision:
allowed: bool
reason: str
class ReleasePolicy:
def validate_binding(self, binding: ResourceBinding) -> PolicyDecision:
if binding.branch == "main":
raise PolicyDenied("MAIN_WRITE_DENIED", "main branch is never writable")
if not binding.allowed_paths:
raise PolicyDenied("PATH_SCOPE_REQUIRED", "candidate path scope is required")
return PolicyDecision(True, "binding_valid")
def validate_task(self, plan: TaskPlan) -> PolicyDecision:
self.validate_binding(plan.binding)
if plan.production_side_effects_allowed:
raise PolicyDenied("PRODUCTION_SIDE_EFFECT_DENIED", "candidate workflows cannot execute production side effects")
return PolicyDecision(True, "task_valid")
def validate_approval(self, approval: ApprovalEnvelope, now: datetime | None = None) -> PolicyDecision:
now = now or datetime.now(timezone.utc)
if now < approval.not_before or now >= approval.expires_at:
raise ApprovalRequired("APPROVAL_TIME_INVALID", "approval is outside its validity window")
if any(cap in FORBIDDEN_CAPABILITIES for cap in approval.capabilities):
raise PolicyDenied("CAPABILITY_FORBIDDEN", "approval requests forbidden capability")
return PolicyDecision(True, "approval_valid")
def validate_worker(self, worker: WorkerDescriptor, required: tuple[str, ...], trust_zone: str) -> PolicyDecision:
if not worker.can_accept(required, trust_zone):
raise CapabilityMismatch("WORKER_CAPABILITY_MISMATCH", "worker cannot satisfy task requirements")
return PolicyDecision(True, "worker_valid")
@@ -1,152 +0,0 @@
"""Governed deployment and rollback state machine."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from .approval import ApprovalVerificationResult
from .errors import ReleaseBlocked
from .models import ReleasePlan
class ReleaseState(str, Enum):
CREATED = "CREATED"
BUILT = "BUILT"
VERIFIED = "VERIFIED"
WAITING_OWNER = "WAITING_OWNER"
APPROVED = "APPROVED"
DEPLOYING = "DEPLOYING"
DEPLOYED = "DEPLOYED"
FAILED = "FAILED"
ROLLBACK_REQUESTED = "ROLLBACK_REQUESTED"
ROLLBACK_APPROVED = "ROLLBACK_APPROVED"
ROLLING_BACK = "ROLLING_BACK"
ROLLED_BACK = "ROLLED_BACK"
@dataclass(frozen=True, slots=True)
class ReleaseStatus:
state: ReleaseState
reason: str | None = None
class ReleaseController:
"""No transition reaches deployment or rollback without an external receipt."""
def __init__(self) -> None:
self._state = ReleaseState.CREATED
self._plan: ReleasePlan | None = None
self._reason: str | None = None
def _expect(self, *states: ReleaseState) -> None:
if self._state not in states:
raise ReleaseBlocked(
"RELEASE_STATE_INVALID",
"release transition is not allowed from the current state",
{"state": self._state.value, "allowed": [item.value for item in states]},
)
def attach_plan(self, plan: ReleasePlan) -> ReleaseStatus:
if self._plan is not None:
if self._plan == plan:
return self.status()
raise ReleaseBlocked("RELEASE_PLAN_ALREADY_ATTACHED", "release plan is immutable")
self._plan = plan
return self.status()
def mark_built(self) -> ReleaseStatus:
self._require_plan()
self._expect(ReleaseState.CREATED)
self._state = ReleaseState.BUILT
return self.status()
def verify(self, *, artifact_ok: bool, evidence_ok: bool) -> ReleaseStatus:
self._require_plan()
self._expect(ReleaseState.BUILT)
if not artifact_ok or not evidence_ok:
raise ReleaseBlocked(
"RELEASE_VERIFICATION_FAILED", "artifact or evidence verification failed"
)
self._state = ReleaseState.VERIFIED
return self.status()
def request_owner_approval(self) -> ReleaseStatus:
self._expect(ReleaseState.VERIFIED)
self._state = ReleaseState.WAITING_OWNER
return self.status()
def approve(self, receipt: ApprovalVerificationResult) -> ReleaseStatus:
plan = self._require_plan()
self._expect(ReleaseState.WAITING_OWNER)
if receipt.task_id != plan.task_id:
raise ReleaseBlocked("OWNER_APPROVAL_TASK_MISMATCH", "approval task does not match")
if receipt.approval_plan_id != plan.approval_plan_id:
raise ReleaseBlocked(
"OWNER_APPROVAL_PLAN_MISMATCH", "approval plan does not match release plan"
)
self._state = ReleaseState.APPROVED
return self.status()
def start_deployment(self) -> ReleaseStatus:
self._expect(ReleaseState.APPROVED)
self._state = ReleaseState.DEPLOYING
return self.status()
def mark_deployed(self) -> ReleaseStatus:
self._expect(ReleaseState.DEPLOYING)
self._state = ReleaseState.DEPLOYED
return self.status()
def mark_failed(self, reason: str) -> ReleaseStatus:
self._expect(ReleaseState.DEPLOYING, ReleaseState.DEPLOYED)
if not reason:
raise ReleaseBlocked("RELEASE_FAILURE_REASON_REQUIRED", "failure reason is required")
self._reason = reason
self._state = ReleaseState.FAILED
return self.status()
def request_rollback(self, reason: str) -> ReleaseStatus:
self._expect(ReleaseState.DEPLOYING, ReleaseState.DEPLOYED, ReleaseState.FAILED)
if not reason:
raise ReleaseBlocked("ROLLBACK_REASON_REQUIRED", "rollback reason is required")
self._reason = reason
self._state = ReleaseState.ROLLBACK_REQUESTED
return self.status()
def approve_rollback(self, receipt: ApprovalVerificationResult) -> ReleaseStatus:
plan = self._require_plan()
self._expect(ReleaseState.ROLLBACK_REQUESTED)
if receipt.task_id != plan.task_id:
raise ReleaseBlocked("ROLLBACK_APPROVAL_TASK_MISMATCH", "rollback task does not match")
if receipt.approval_plan_id != plan.rollback_approval_plan_id:
raise ReleaseBlocked(
"ROLLBACK_APPROVAL_PLAN_MISMATCH",
"rollback approval plan does not match release plan",
)
self._state = ReleaseState.ROLLBACK_APPROVED
return self.status()
def start_rollback(self) -> ReleaseStatus:
self._expect(ReleaseState.ROLLBACK_APPROVED)
self._state = ReleaseState.ROLLING_BACK
return self.status()
def mark_rolled_back(self) -> ReleaseStatus:
self._expect(ReleaseState.ROLLING_BACK)
self._state = ReleaseState.ROLLED_BACK
return self.status()
def rollback(self) -> ReleaseStatus:
raise ReleaseBlocked(
"ROLLBACK_APPROVAL_REQUIRED",
"use request_rollback, approve_rollback and start_rollback",
)
def status(self) -> ReleaseStatus:
return ReleaseStatus(self._state, self._reason)
def _require_plan(self) -> ReleasePlan:
if self._plan is None:
raise ReleaseBlocked("RELEASE_PLAN_REQUIRED", "attach a release plan first")
return self._plan
@@ -1,72 +0,0 @@
"""Read-only multi-source release consistency gate."""
from __future__ import annotations
from dataclasses import dataclass
from .errors import ReleaseBlocked
from .models import is_git_oid, is_sha256
@dataclass(frozen=True, slots=True)
class ReleaseEvidence:
expected_version: str
registry_version: str
baseline_version: str
local_tag_oid: str
remote_tag_oid: str
gitea_tag_oid: str
expected_tag_oid: str
local_peeled_target_oid: str
remote_peeled_target_oid: str
gitea_peeled_target_oid: str
expected_target_oid: str
artifact_sha256: str
manifest_sha256: str
evidence_sha256: str
rollback_artifact_sha256: str
class ReleaseGate:
"""Require exact tag-object, peeled-target, version and package closure."""
def verify(self, evidence: ReleaseEvidence) -> None:
versions = (
evidence.expected_version,
evidence.registry_version,
evidence.baseline_version,
)
if not versions[0] or len(set(versions)) != 1:
raise ReleaseBlocked("VERSION_MISMATCH", "expected, registry and baseline differ")
tag_oids = (
evidence.local_tag_oid,
evidence.remote_tag_oid,
evidence.gitea_tag_oid,
evidence.expected_tag_oid,
)
if any(not is_git_oid(value) for value in tag_oids):
raise ReleaseBlocked("TAG_OID_INVALID", "tag object OID is invalid")
if evidence.local_tag_oid != evidence.remote_tag_oid:
raise ReleaseBlocked("TAG_REMOTE_MISMATCH", "local and remote tag objects differ")
if evidence.remote_tag_oid != evidence.gitea_tag_oid:
raise ReleaseBlocked("TAG_GITEA_MISMATCH", "Gitea tag object differs")
if evidence.gitea_tag_oid != evidence.expected_tag_oid:
raise ReleaseBlocked("TAG_OBJECT_MISMATCH", "tag object differs from ReleasePlan")
targets = (
evidence.local_peeled_target_oid,
evidence.remote_peeled_target_oid,
evidence.gitea_peeled_target_oid,
evidence.expected_target_oid,
)
if any(not is_git_oid(value) for value in targets):
raise ReleaseBlocked("TAG_TARGET_OID_INVALID", "peeled target OID is invalid")
if len(set(targets)) != 1:
raise ReleaseBlocked("TAG_TARGET_MISMATCH", "peeled tag targets are not identical")
digests = (
evidence.artifact_sha256,
evidence.manifest_sha256,
evidence.evidence_sha256,
evidence.rollback_artifact_sha256,
)
if any(not is_sha256(value) for value in digests):
raise ReleaseBlocked("ARTIFACT_DIGEST_MISSING", "package closure is incomplete")
@@ -1,36 +0,0 @@
"""Deterministic worker routing for candidate autonomous release workflows."""
from __future__ import annotations
from dataclasses import dataclass
from .errors import CapabilityMismatch
from .models import WorkerDescriptor
@dataclass(frozen=True, slots=True)
class RouteRequest:
task_id: str
required_capabilities: tuple[str, ...]
trust_zone: str
class WorkerRouter:
"""Select the least loaded compatible worker without expanding trust."""
def __init__(self, workers: list[WorkerDescriptor]) -> None:
self._workers = list(workers)
def route(self, request: RouteRequest) -> WorkerDescriptor:
candidates = [
worker
for worker in self._workers
if worker.can_accept(request.required_capabilities, request.trust_zone)
]
if not candidates:
raise CapabilityMismatch(
"NO_COMPATIBLE_WORKER",
"no worker satisfies capability and trust constraints",
{"task_id": request.task_id},
)
return sorted(candidates, key=lambda item: (item.current_load, item.worker_id))[0]
@@ -1,213 +0,0 @@
"""AIOS Secret Manager V2 reference-only Candidate adapter."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Mapping, Protocol, Sequence
from .errors import CredentialUnavailable
from .models import CredentialRequest
PROVIDER_REFERENCES = {
"openclaw": "systemd-credential:openclaw-gateway-candidate",
"codex": "systemd-credential:codex-app-server-candidate",
"native_adapter_control": "systemd-credential:session-native-adapter-control-candidate",
"gitea": "systemd-credential:gitea-admin-api-candidate",
}
FORBIDDEN_RESPONSE_FIELDS = {
"api_key",
"token",
"password",
"cookie",
"private_key",
"credential_body",
"plaintext",
}
class SecretManagerTransport(Protocol):
def exchange(self, operation: str, payload: Mapping[str, Any]) -> Mapping[str, Any]: ...
@dataclass(frozen=True, slots=True)
class SecretProjectionReceipt:
task_id: str
credential_id: str
reference: str
provider: str
audience: str
consumer_service: str
state: str
expires_at: datetime
plaintext_secret_returned: bool = False
class SecretManagerV2:
"""Issue, project, inspect and revoke without receiving authentication material."""
def __init__(self, transport: SecretManagerTransport) -> None:
self.transport = transport
@staticmethod
def _assert_safe(value: Any) -> None:
if isinstance(value, Mapping):
if FORBIDDEN_RESPONSE_FIELDS.intersection(str(key).lower() for key in value):
raise CredentialUnavailable(
"SECRET_MANAGER_PLAINTEXT_REJECTED",
"Secret Manager response included a forbidden plaintext field",
)
for item in value.values():
SecretManagerV2._assert_safe(item)
elif isinstance(value, (list, tuple)):
for item in value:
SecretManagerV2._assert_safe(item)
def _exchange(self, operation: str, payload: Mapping[str, Any]) -> Mapping[str, Any]:
try:
response = self.transport.exchange(operation, payload)
except CredentialUnavailable:
raise
except Exception as error:
raise CredentialUnavailable(
"SECRET_MANAGER_UNAVAILABLE", "Secret Manager V2 operation failed"
) from error
if not isinstance(response, Mapping):
raise CredentialUnavailable(
"SECRET_MANAGER_RESPONSE_INVALID", "Secret Manager V2 returned a non-object"
)
self._assert_safe(response)
if response.get("plaintext_secret_returned") is not False:
raise CredentialUnavailable(
"SECRET_MANAGER_PLAINTEXT_REJECTED",
"Secret Manager did not attest plaintext_secret_returned=false",
)
return response
@staticmethod
def _payload(request: CredentialRequest) -> dict[str, Any]:
return {
"approval_plan_id": request.approval_plan_id,
"task_id": request.task_id,
"request_id": request.request_id,
"session_id": request.session_id,
"worker_id": request.worker_id,
"provider": request.provider,
"audience": request.audience,
"consumer_service": request.consumer_service,
"runtime_user": request.runtime_user,
"repository": request.binding.repository,
"branch": request.binding.branch,
"branch_oid": request.binding.base_oid,
"allowed_paths": list(request.binding.allowed_paths),
"operations": list(request.operations),
"not_before": request.not_before.astimezone(timezone.utc).isoformat(),
"expires_at": request.expires_at.astimezone(timezone.utc).isoformat(),
"nonce": request.nonce,
"ttl_seconds": request.ttl_seconds,
"max_uses": request.max_uses,
}
def issue(
self, request: CredentialRequest, now: datetime | None = None
) -> SecretProjectionReceipt:
now = now or datetime.now(timezone.utc)
if now < request.not_before or now >= request.expires_at:
raise CredentialUnavailable(
"SECRET_MANAGER_APPROVAL_WINDOW_INVALID", "credential request is not currently valid"
)
expected_reference = PROVIDER_REFERENCES.get(request.provider)
if expected_reference is None:
raise CredentialUnavailable(
"SECRET_MANAGER_PROVIDER_DENIED", "provider is not Candidate-approved"
)
response = self._exchange("issue", self._payload(request))
credential_id = response.get("credential_id")
if (
response.get("task_id") != request.task_id
or not isinstance(credential_id, str)
or not credential_id
or response.get("credential_reference") != expected_reference
or response.get("state") != "ISSUED"
):
raise CredentialUnavailable(
"SECRET_MANAGER_BINDING_MISMATCH", "issued projection does not match the request"
)
expires_at = datetime.fromisoformat(str(response.get("expires_at")))
if expires_at > request.expires_at or expires_at <= now:
raise CredentialUnavailable(
"SECRET_MANAGER_EXPIRY_INVALID", "issued projection expiry is outside approval"
)
return SecretProjectionReceipt(
task_id=request.task_id,
credential_id=credential_id,
reference=expected_reference,
provider=request.provider,
audience=request.audience,
consumer_service=request.consumer_service,
state="ISSUED",
expires_at=expires_at,
plaintext_secret_returned=False,
)
@staticmethod
def _receipt_payload(receipt: SecretProjectionReceipt) -> dict[str, Any]:
return {
"task_id": receipt.task_id,
"credential_id": receipt.credential_id,
"credential_reference": receipt.reference,
"provider": receipt.provider,
"audience": receipt.audience,
"consumer_service": receipt.consumer_service,
}
def _state_operation(
self, operation: str, receipt: SecretProjectionReceipt, expected_state: str
) -> Mapping[str, Any]:
response = self._exchange(operation, self._receipt_payload(receipt))
if (
response.get("task_id") != receipt.task_id
or response.get("credential_id") != receipt.credential_id
or response.get("state") != expected_state
):
raise CredentialUnavailable(
"SECRET_MANAGER_STATE_MISMATCH", f"{operation} response is not task-bound"
)
return response
def project(self, receipt: SecretProjectionReceipt) -> None:
self._state_operation("project", receipt, "PROJECTED")
def status(self, receipt: SecretProjectionReceipt) -> str:
return str(self._state_operation("status", receipt, "PROJECTED")["state"])
def revoke(self, receipt: SecretProjectionReceipt) -> None:
self._state_operation("revoke", receipt, "REVOKED")
def verify_revocation(self, receipt: SecretProjectionReceipt) -> None:
response = self._state_operation("verify_revocation", receipt, "REVOKED")
if response.get("post_revocation_auth") != "REJECTED":
raise CredentialUnavailable(
"SECRET_MANAGER_REVOCATION_UNVERIFIED",
"post-revocation authentication was not rejected",
)
def audit(self, receipt: SecretProjectionReceipt) -> Sequence[Mapping[str, Any]]:
response = self._exchange("audit", self._receipt_payload(receipt))
if (
response.get("task_id") != receipt.task_id
or response.get("credential_id") != receipt.credential_id
):
raise CredentialUnavailable(
"SECRET_MANAGER_AUDIT_MISMATCH", "audit response is not task-bound"
)
events = response.get("events")
if not isinstance(events, list) or any(
not isinstance(event, Mapping) or event.get("secrets_included") is not False
for event in events
):
raise CredentialUnavailable(
"SECRET_MANAGER_AUDIT_INVALID", "audit events are absent or unsafe"
)
return events
@@ -1,116 +0,0 @@
"""JSON serialization helpers for candidate API and CLI boundaries."""
from __future__ import annotations
from datetime import datetime
from typing import Any, Mapping
from .models import ReleasePlan, ResourceBinding, RiskClass, TaskPlan, WorkflowStep
def task_plan_from_mapping(value: Mapping[str, Any]) -> TaskPlan:
binding_value = value["binding"]
binding = ResourceBinding(
repository=str(binding_value["repository"]),
branch=str(binding_value["branch"]),
base_oid=str(binding_value["base_oid"]),
allowed_paths=tuple(str(item) for item in binding_value.get("allowed_paths", ())),
)
steps = tuple(
WorkflowStep(
step_id=str(item["step_id"]),
worker_role=str(item["worker_role"]),
required_capabilities=tuple(str(cap) for cap in item.get("required_capabilities", ())),
depends_on=tuple(str(dep) for dep in item.get("depends_on", ())),
max_attempts=int(item.get("max_attempts", 1)),
timeout_seconds=int(item.get("timeout_seconds", 900)),
allowed_paths=tuple(str(path) for path in item.get("allowed_paths", ())),
)
for item in value.get("steps", ())
)
created_at_raw = value.get("created_at")
created_at = datetime.fromisoformat(created_at_raw) if created_at_raw else None
kwargs: dict[str, Any] = {
"task_id": str(value["task_id"]),
"intent": str(value["intent"]),
"binding": binding,
"risk": RiskClass(str(value.get("risk", RiskClass.MEDIUM.value))),
"steps": steps,
"evidence_requirements": tuple(str(item) for item in value.get("evidence_requirements", ())),
"production_side_effects_allowed": bool(value.get("production_side_effects_allowed", False)),
}
if created_at is not None:
kwargs["created_at"] = created_at
return TaskPlan(**kwargs)
def task_plan_to_mapping(plan: TaskPlan) -> dict[str, Any]:
return {
"task_id": plan.task_id,
"intent": plan.intent,
"binding": {
"repository": plan.binding.repository,
"branch": plan.binding.branch,
"base_oid": plan.binding.base_oid,
"allowed_paths": list(plan.binding.allowed_paths),
},
"risk": plan.risk.value,
"steps": [
{
"step_id": step.step_id,
"worker_role": step.worker_role,
"required_capabilities": list(step.required_capabilities),
"depends_on": list(step.depends_on),
"max_attempts": step.max_attempts,
"timeout_seconds": step.timeout_seconds,
"allowed_paths": list(step.allowed_paths),
}
for step in plan.steps
],
"evidence_requirements": list(plan.evidence_requirements),
"production_side_effects_allowed": plan.production_side_effects_allowed,
"created_at": plan.created_at.isoformat(),
"plan_digest": plan.digest(),
}
def release_plan_from_mapping(value: Mapping[str, Any]) -> ReleasePlan:
return ReleasePlan(
task_id=str(value["task_id"]),
repository=str(value["repository"]),
branch=str(value["branch"]),
version=str(value["version"]),
target_oid=str(value["target_oid"]),
tree_oid=str(value["tree_oid"]),
artifact_sha256=str(value["artifact_sha256"]),
manifest_sha256=str(value["manifest_sha256"]),
package_closure_sha256=str(value["package_closure_sha256"]),
evidence_closure_oid=str(value["evidence_closure_oid"]),
approval_plan_id=str(value["approval_plan_id"]),
rollback_approval_plan_id=str(value["rollback_approval_plan_id"]),
rollback_artifact_sha256=str(value["rollback_artifact_sha256"]),
expected_tag_oid=str(value["expected_tag_oid"]),
expected_target_oid=str(value["expected_target_oid"]),
production_config_sha256=str(value["production_config_sha256"]),
)
def release_plan_to_mapping(plan: ReleasePlan) -> dict[str, Any]:
return {
"task_id": plan.task_id,
"repository": plan.repository,
"branch": plan.branch,
"version": plan.version,
"target_oid": plan.target_oid,
"tree_oid": plan.tree_oid,
"artifact_sha256": plan.artifact_sha256,
"manifest_sha256": plan.manifest_sha256,
"package_closure_sha256": plan.package_closure_sha256,
"evidence_closure_oid": plan.evidence_closure_oid,
"approval_plan_id": plan.approval_plan_id,
"rollback_approval_plan_id": plan.rollback_approval_plan_id,
"rollback_artifact_sha256": plan.rollback_artifact_sha256,
"expected_tag_oid": plan.expected_tag_oid,
"expected_target_oid": plan.expected_target_oid,
"production_config_sha256": plan.production_config_sha256,
}
@@ -1,223 +0,0 @@
"""Hardened loopback HTTP boundary for the dormant Candidate."""
from __future__ import annotations
import argparse
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import ipaddress
import json
from pathlib import Path
from typing import Any
from urllib.parse import urlparse
from .api import AutonomousReleaseAPI
from .errors import AutonomousReleaseError
from .orchestrator import AutonomousReleaseOrchestrator
from .policy import ReleasePolicy
from .store import StateStore
from .workflow import WorkflowEngine
class CandidateApplication:
def __init__(self, state_path: str | Path) -> None:
self.store = StateStore(state_path)
workflow = WorkflowEngine(self.store)
self.api = AutonomousReleaseAPI(
AutonomousReleaseOrchestrator(workflow, ReleasePolicy())
)
def ready(self) -> bool:
with self.store.transaction() as connection:
return connection.execute("SELECT 1").fetchone()[0] == 1
class CandidateRequestHandler(BaseHTTPRequestHandler):
server_version = "AIOSAutonomousReleaseCandidate/2"
sys_version = ""
@property
def application(self) -> CandidateApplication:
return self.server.application # type: ignore[attr-defined]
def _send_json(self, status: HTTPStatus, payload: dict[str, Any]) -> None:
encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
self.send_response(status.value)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(encoded)))
self.send_header("Cache-Control", "no-store")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("Referrer-Policy", "no-referrer")
self.end_headers()
self.wfile.write(encoded)
def _read_json(self) -> dict[str, Any]:
content_type = self.headers.get("Content-Type", "").split(";", 1)[0].strip().lower()
if content_type != "application/json":
raise UnsupportedMediaType
try:
length = int(self.headers.get("Content-Length", "0"))
except ValueError as error:
raise InvalidRequest from error
if length <= 0 or length > 1_048_576:
raise InvalidRequest
try:
raw = self.rfile.read(length)
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as error:
raise InvalidRequest from error
if not isinstance(value, dict):
raise InvalidRequest
return value
def do_GET(self) -> None: # noqa: N802
path = urlparse(self.path).path
if path == "/health":
self._send_json(
HTTPStatus.OK,
{
"status": "ok",
"mode": "candidate-shadow-only",
"production_side_effects": False,
"secrets_included": False,
},
)
return
if path == "/ready":
if self.application.ready():
self._send_json(
HTTPStatus.OK, {"status": "ready", "secrets_included": False}
)
else:
self._send_json(
HTTPStatus.SERVICE_UNAVAILABLE,
{"status": "not_ready", "secrets_included": False},
)
return
if path.startswith("/v1/tasks/") and path.endswith("/steps"):
task_id = path[len("/v1/tasks/") : -len("/steps")].strip("/")
if not task_id or "/" in task_id:
self._send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid_request"})
return
try:
self._send_json(HTTPStatus.OK, self.application.api.task_steps(task_id))
except KeyError:
self._send_json(HTTPStatus.NOT_FOUND, {"error": "task_not_found"})
return
if path.startswith("/v1/tasks/") and path.endswith("/evidence"):
task_id = path[len("/v1/tasks/") : -len("/evidence")].strip("/")
if not task_id or "/" in task_id:
self._send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid_request"})
return
try:
self._send_json(HTTPStatus.OK, self.application.api.evidence(task_id))
except KeyError:
self._send_json(HTTPStatus.NOT_FOUND, {"error": "task_not_found"})
return
if path.startswith("/v1/tasks/"):
task_id = path[len("/v1/tasks/") :].strip("/")
if not task_id or "/" in task_id:
self._send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid_request"})
return
try:
self._send_json(HTTPStatus.OK, self.application.api.task_status(task_id))
except KeyError:
self._send_json(HTTPStatus.NOT_FOUND, {"error": "task_not_found"})
return
self._send_json(HTTPStatus.NOT_FOUND, {"error": "not_found"})
def do_POST(self) -> None: # noqa: N802
path = urlparse(self.path).path
try:
payload = self._read_json()
if path == "/v1/tasks":
self._send_json(HTTPStatus.CREATED, self.application.api.create_task(payload))
return
if path == "/v1/release/plan":
self._send_json(
HTTPStatus.CREATED, self.application.api.create_release_plan(payload)
)
return
if path == "/v1/release/verify":
self._send_json(HTTPStatus.OK, self.application.api.verify_release(payload))
return
if path.startswith("/v1/tasks/") and path.endswith("/evidence/close"):
task_id = path[len("/v1/tasks/") : -len("/evidence/close")].strip("/")
if not task_id:
raise InvalidRequest
self._send_json(HTTPStatus.OK, self.application.api.close_evidence(task_id))
return
for action, handler in (
("claim", self.application.api.claim),
("heartbeat", self.application.api.heartbeat),
("result", self.application.api.result),
):
suffix = f"/{action}"
if path.startswith("/v1/tasks/") and path.endswith(suffix):
task_id = path[len("/v1/tasks/") : -len(suffix)].strip("/")
if not task_id or "/" in task_id:
raise InvalidRequest
self._send_json(HTTPStatus.OK, handler(task_id, payload))
return
self._send_json(HTTPStatus.NOT_FOUND, {"error": "not_found"})
except UnsupportedMediaType:
self._send_json(
HTTPStatus.UNSUPPORTED_MEDIA_TYPE, {"error": "content_type_required"}
)
except (InvalidRequest, ValueError, KeyError):
self._send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid_request"})
except AutonomousReleaseError as error:
self._send_json(HTTPStatus.CONFLICT, {"error": error.code})
except Exception:
self._send_json(
HTTPStatus.INTERNAL_SERVER_ERROR, {"error": "candidate_request_failed"}
)
def log_message(self, _format: str, *_args: Any) -> None:
return
class InvalidRequest(Exception):
pass
class UnsupportedMediaType(Exception):
pass
class CandidateHTTPServer(ThreadingHTTPServer):
daemon_threads = True
allow_reuse_address = False
def __init__(self, address: tuple[str, int], application: CandidateApplication) -> None:
try:
loopback = ipaddress.ip_address(address[0]).is_loopback
except ValueError as error:
raise ValueError("candidate service requires a numeric loopback address") from error
if not loopback:
raise ValueError("candidate service must bind to loopback")
self.application = application
super().__init__(address, CandidateRequestHandler)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="aios-autonomous-release-service")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=19951)
parser.add_argument("--state", required=True)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
application = CandidateApplication(args.state)
server = CandidateHTTPServer((args.host, args.port), application)
try:
server.serve_forever(poll_interval=0.5)
finally:
server.server_close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,87 +0,0 @@
"""Unified candidate session lifecycle contracts."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from datetime import datetime
from .errors import StateConflict
class SessionState(str, Enum):
CREATED = "CREATED"
PROVISIONED = "PROVISIONED"
ACTIVE = "ACTIVE"
INTERRUPTED = "INTERRUPTED"
CLOSED = "CLOSED"
REVOKED = "REVOKED"
_ALLOWED_TRANSITIONS: dict[SessionState, frozenset[SessionState]] = {
SessionState.CREATED: frozenset({SessionState.PROVISIONED, SessionState.CLOSED, SessionState.REVOKED}),
SessionState.PROVISIONED: frozenset({SessionState.ACTIVE, SessionState.CLOSED, SessionState.REVOKED}),
SessionState.ACTIVE: frozenset({SessionState.INTERRUPTED, SessionState.CLOSED, SessionState.REVOKED}),
SessionState.INTERRUPTED: frozenset({SessionState.ACTIVE, SessionState.CLOSED, SessionState.REVOKED}),
SessionState.CLOSED: frozenset({SessionState.REVOKED}),
SessionState.REVOKED: frozenset(),
}
@dataclass(frozen=True, slots=True)
class SessionIdentity:
session_id: str
provider: str
provider_session_id: str
worker_id: str
task_id: str
created_at: datetime
def __post_init__(self) -> None:
if not all((self.session_id, self.provider, self.provider_session_id, self.worker_id, self.task_id)):
raise ValueError("all session identity fields are required")
if self.session_id == self.provider_session_id:
raise ValueError("broker and provider session IDs must be distinct")
class SessionOrchestrator:
"""Provider-neutral session lifecycle guard with explicit state transitions."""
def __init__(self) -> None:
self._sessions: dict[str, tuple[SessionIdentity, SessionState]] = {}
def create(self, identity: SessionIdentity) -> None:
if identity.session_id in self._sessions:
raise StateConflict("SESSION_EXISTS", "session already exists")
self._sessions[identity.session_id] = (identity, SessionState.CREATED)
def transition(self, session_id: str, state: SessionState) -> None:
current = self._sessions.get(session_id)
if current is None:
raise StateConflict("SESSION_NOT_FOUND", "session does not exist")
current_state = current[1]
if state not in _ALLOWED_TRANSITIONS[current_state]:
raise StateConflict(
"SESSION_TRANSITION_INVALID",
"session state transition is not allowed",
{"session_id": session_id, "from": current_state.value, "to": state.value},
)
self._sessions[session_id] = (current[0], state)
def identity(self, session_id: str) -> SessionIdentity:
current = self._sessions.get(session_id)
if current is None:
raise StateConflict("SESSION_NOT_FOUND", "session does not exist")
return current[0]
def status(self, session_id: str) -> SessionState:
current = self._sessions.get(session_id)
if current is None:
raise StateConflict("SESSION_NOT_FOUND", "session does not exist")
return current[1]
def close(self, session_id: str) -> None:
self.transition(session_id, SessionState.CLOSED)
def revoke(self, session_id: str) -> None:
self.transition(session_id, SessionState.REVOKED)
@@ -1,383 +0,0 @@
"""Durable, private SQLite state for the autonomous-release Candidate."""
from __future__ import annotations
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timezone
import json
import os
from pathlib import Path
import sqlite3
from typing import Any, Iterator, Mapping, Sequence
from .errors import StateConflict
from .models import StepState, TaskState, canonical_json
SCHEMA_VERSION = 2
@dataclass(frozen=True, slots=True)
class StoredStep:
task_id: str
step_id: str
state: StepState
attempt: int
max_attempts: int
worker_id: str | None
session_id: str | None
lease_expires_at: datetime | None
result_digest: str | None
updated_at: datetime
last_error: str | None
class StateStore:
"""SQLite store with WAL, full sync, schema checks and serialized CAS writes."""
def __init__(self, path: str | Path) -> None:
self.path = Path(path)
self._prepare_path()
self._initialize()
os.chmod(self.path, 0o600)
def _prepare_path(self) -> None:
if self.path.exists() and self.path.is_symlink():
raise StateConflict("STATE_PATH_SYMLINK", "state database must not be a symlink")
parent = self.path.parent
parent.mkdir(parents=True, exist_ok=True, mode=0o750)
if parent.is_symlink():
raise StateConflict("STATE_DIRECTORY_SYMLINK", "state directory must not be a symlink")
current_mode = parent.stat().st_mode & 0o777
os.chmod(parent, current_mode & ~0o027)
def _connect(self) -> sqlite3.Connection:
if self.path.exists() and self.path.is_symlink():
raise StateConflict("STATE_PATH_SYMLINK", "state database must not be a symlink")
connection = sqlite3.connect(self.path, timeout=30.0)
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=30000")
return connection
@contextmanager
def transaction(self) -> Iterator[sqlite3.Connection]:
connection = self._connect()
try:
connection.execute("BEGIN IMMEDIATE")
yield connection
connection.commit()
except Exception:
connection.rollback()
raise
finally:
connection.close()
def _initialize(self) -> None:
with self.transaction() as connection:
connection.execute(
"CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
)
row = connection.execute(
"SELECT value FROM metadata WHERE key='schema_version'"
).fetchone()
version = int(row["value"]) if row is not None else None
if version not in (None, 1, SCHEMA_VERSION):
raise StateConflict(
"SCHEMA_VERSION_UNSUPPORTED",
"state schema is not supported",
{"found": version, "expected": SCHEMA_VERSION},
)
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY,
plan_digest TEXT NOT NULL,
plan_json TEXT NOT NULL,
state TEXT NOT NULL,
revision INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS steps (
task_id TEXT NOT NULL,
step_id TEXT NOT NULL,
state TEXT NOT NULL,
attempt INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 1,
worker_id TEXT,
session_id TEXT,
lease_expires_at TEXT,
result_digest TEXT,
last_error TEXT,
updated_at TEXT NOT NULL,
PRIMARY KEY(task_id, step_id),
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS events (
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
event_type TEXT NOT NULL,
payload_json TEXT NOT NULL,
previous_digest TEXT,
digest TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS consumed_nonces (
scope TEXT NOT NULL,
nonce TEXT NOT NULL,
consumed_at TEXT NOT NULL,
PRIMARY KEY(scope, nonce)
);
CREATE TABLE IF NOT EXISTS credentials (
credential_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
reference TEXT NOT NULL UNIQUE,
state TEXT NOT NULL,
metadata_json TEXT NOT NULL,
issued_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
revoked_at TEXT
);
CREATE TABLE IF NOT EXISTS idempotency (
namespace TEXT NOT NULL,
operation_id TEXT NOT NULL,
result_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY(namespace, operation_id)
);
"""
)
if version == 1:
existing = {
item["name"] for item in connection.execute("PRAGMA table_info(steps)")
}
additions = {
"max_attempts": "INTEGER NOT NULL DEFAULT 1",
"lease_expires_at": "TEXT",
"result_digest": "TEXT",
}
for column, declaration in additions.items():
if column not in existing:
connection.execute(f"ALTER TABLE steps ADD COLUMN {column} {declaration}")
connection.execute(
"INSERT INTO metadata(key, value) VALUES('schema_version', ?) "
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
(str(SCHEMA_VERSION),),
)
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
def create_task(
self,
task_id: str,
plan_digest: str,
plan: Mapping[str, Any],
step_ids: tuple[str, ...],
step_limits: Mapping[str, int] | None = None,
) -> None:
now = self._now()
limits = step_limits or {}
with self.transaction() as connection:
connection.execute(
"INSERT INTO tasks(task_id, plan_digest, plan_json, state, created_at, updated_at) "
"VALUES(?, ?, ?, ?, ?, ?)",
(task_id, plan_digest, canonical_json(plan), TaskState.PLANNED.value, now, now),
)
connection.executemany(
"INSERT INTO steps(task_id, step_id, state, max_attempts, updated_at) "
"VALUES(?, ?, ?, ?, ?)",
[
(task_id, step_id, StepState.PENDING.value, int(limits.get(step_id, 1)), now)
for step_id in step_ids
],
)
def get_task_state(self, task_id: str) -> tuple[TaskState, int]:
with self._connect() as connection:
row = connection.execute(
"SELECT state, revision FROM tasks WHERE task_id=?", (task_id,)
).fetchone()
if row is None:
raise KeyError(task_id)
return TaskState(row["state"]), int(row["revision"])
def compare_and_set_task_state(
self, task_id: str, expected_revision: int, new_state: TaskState
) -> int:
with self.transaction() as connection:
cursor = connection.execute(
"UPDATE tasks SET state=?, revision=revision+1, updated_at=? "
"WHERE task_id=? AND revision=?",
(new_state.value, self._now(), task_id, expected_revision),
)
if cursor.rowcount != 1:
raise StateConflict(
"TASK_REVISION_CONFLICT",
"task state changed concurrently",
{"task_id": task_id, "expected_revision": expected_revision},
)
row = connection.execute(
"SELECT revision FROM tasks WHERE task_id=?", (task_id,)
).fetchone()
return int(row["revision"])
def transition_step(
self,
task_id: str,
step_id: str,
expected: Sequence[StepState],
state: StepState,
*,
worker_id: str | None = None,
session_id: str | None = None,
lease_expires_at: datetime | None = None,
result_digest: str | None = None,
last_error: str | None = None,
increment_attempt: bool = False,
clear_assignment: bool = False,
) -> StoredStep:
with self.transaction() as connection:
row = connection.execute(
"SELECT * FROM steps WHERE task_id=? AND step_id=?", (task_id, step_id)
).fetchone()
if row is None:
raise KeyError(f"{task_id}:{step_id}")
if StepState(row["state"]) not in set(expected):
raise StateConflict(
"STEP_STATE_CONFLICT",
"step state changed or transition is invalid",
{"task_id": task_id, "step_id": step_id, "state": row["state"]},
)
if increment_attempt and int(row["attempt"]) >= int(row["max_attempts"]):
raise StateConflict(
"STEP_ATTEMPTS_EXHAUSTED",
"step has no remaining attempts",
{"task_id": task_id, "step_id": step_id},
)
next_worker = None if clear_assignment else (worker_id or row["worker_id"])
next_session = None if clear_assignment else (session_id or row["session_id"])
connection.execute(
"UPDATE steps SET state=?, attempt=attempt+?, worker_id=?, session_id=?, "
"lease_expires_at=?, result_digest=COALESCE(?, result_digest), last_error=?, updated_at=? "
"WHERE task_id=? AND step_id=?",
(
state.value,
1 if increment_attempt else 0,
next_worker,
next_session,
lease_expires_at.isoformat() if lease_expires_at else None,
result_digest,
last_error,
self._now(),
task_id,
step_id,
),
)
updated = connection.execute(
"SELECT * FROM steps WHERE task_id=? AND step_id=?", (task_id, step_id)
).fetchone()
return self._step_from_row(updated)
def set_step_state(
self,
task_id: str,
step_id: str,
state: StepState,
**kwargs: Any,
) -> StoredStep:
current = self.list_steps(task_id)[step_id]
return self.transition_step(task_id, step_id, (current.state,), state, **kwargs)
def list_steps(self, task_id: str) -> dict[str, StoredStep]:
with self._connect() as connection:
rows = connection.execute(
"SELECT * FROM steps WHERE task_id=? ORDER BY step_id", (task_id,)
).fetchall()
return {row["step_id"]: self._step_from_row(row) for row in rows}
@staticmethod
def _step_from_row(row: sqlite3.Row) -> StoredStep:
return StoredStep(
task_id=row["task_id"],
step_id=row["step_id"],
state=StepState(row["state"]),
attempt=int(row["attempt"]),
max_attempts=int(row["max_attempts"]),
worker_id=row["worker_id"],
session_id=row["session_id"],
lease_expires_at=(
datetime.fromisoformat(row["lease_expires_at"])
if row["lease_expires_at"]
else None
),
result_digest=row["result_digest"],
updated_at=datetime.fromisoformat(row["updated_at"]),
last_error=row["last_error"],
)
def consume_nonce(self, scope: str, nonce: str) -> None:
try:
with self.transaction() as connection:
connection.execute(
"INSERT INTO consumed_nonces(scope, nonce, consumed_at) VALUES(?, ?, ?)",
(scope, nonce, self._now()),
)
except sqlite3.IntegrityError as error:
raise StateConflict(
"NONCE_REPLAY", "nonce was already consumed", {"scope": scope, "nonce": nonce}
) from error
def append_event(
self,
task_id: str,
event_type: str,
payload: Mapping[str, Any],
previous_digest: str | None,
event_digest: str,
created_at: datetime,
) -> int:
with self.transaction() as connection:
cursor = connection.execute(
"INSERT INTO events(task_id, event_type, payload_json, previous_digest, digest, created_at) "
"VALUES(?, ?, ?, ?, ?, ?)",
(
task_id,
event_type,
canonical_json(payload),
previous_digest,
event_digest,
created_at.astimezone(timezone.utc).isoformat(),
),
)
return int(cursor.lastrowid)
def list_events(self, task_id: str) -> list[Mapping[str, Any]]:
with self._connect() as connection:
rows = connection.execute(
"SELECT * FROM events WHERE task_id=? ORDER BY sequence", (task_id,)
).fetchall()
return [dict(row) for row in rows]
def put_idempotent_result(
self, namespace: str, operation_id: str, result: Mapping[str, Any]
) -> Mapping[str, Any]:
encoded = canonical_json(result)
with self.transaction() as connection:
existing = connection.execute(
"SELECT result_json FROM idempotency WHERE namespace=? AND operation_id=?",
(namespace, operation_id),
).fetchone()
if existing is not None:
return json.loads(existing["result_json"])
connection.execute(
"INSERT INTO idempotency(namespace, operation_id, result_json, created_at) "
"VALUES(?, ?, ?, ?)",
(namespace, operation_id, encoded, self._now()),
)
return dict(result)
@@ -1,44 +0,0 @@
"""Read-only annotated and lightweight tag consistency checks."""
from __future__ import annotations
from dataclasses import dataclass
from .errors import ReleaseBlocked
from .models import is_git_oid
@dataclass(frozen=True, slots=True)
class TagReference:
ref: str
object_type: str
tag_oid: str
peeled_target_oid: str
class TagVerifier:
def verify(
self,
tag: TagReference,
*,
expected_tag_oid: str,
expected_target_oid: str,
) -> None:
if not tag.ref.startswith("refs/tags/") or tag.ref.endswith("^{}"):
raise ReleaseBlocked("TAG_REF_INVALID", "canonical tag ref is required")
if tag.object_type not in ("commit", "tag"):
raise ReleaseBlocked("TAG_OBJECT_TYPE_INVALID", "unsupported tag object type")
values = (tag.tag_oid, tag.peeled_target_oid, expected_tag_oid, expected_target_oid)
if any(not is_git_oid(value) for value in values):
raise ReleaseBlocked("TAG_OID_INVALID", "tag OIDs must be complete lowercase hex")
if tag.tag_oid != expected_tag_oid:
raise ReleaseBlocked("TAG_OBJECT_MISMATCH", "tag object does not match expectation")
if tag.peeled_target_oid != expected_target_oid:
raise ReleaseBlocked(
"TAG_PEELED_TARGET_MISMATCH", "peeled target does not match expected target"
)
if tag.object_type == "commit" and tag.tag_oid != tag.peeled_target_oid:
raise ReleaseBlocked(
"LIGHTWEIGHT_TAG_INCONSISTENT",
"lightweight tag object and target must be identical",
)
@@ -1,69 +0,0 @@
"""Dormant Candidate worker supervisor.
Provider clients are injected by the external management plane. This process
never converts a missing projection into a simulated native identity.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import signal
import threading
def write_status(directory: Path) -> None:
directory.mkdir(parents=True, exist_ok=True, mode=0o750)
if directory.is_symlink():
raise RuntimeError("worker state directory symlink rejected")
payload = {
"status": "dormant",
"task_plane_execution_enabled": os.environ.get(
"AIOS_TASK_PLANE_EXECUTION_ENABLED", "false"
).lower()
== "true",
"gitops_writes_enabled": os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower()
== "true",
"production_side_effects": False,
"secrets_included": False,
}
temporary = directory / f"worker-status.{os.getpid()}.new"
target = directory / "worker-status.json"
if temporary.is_symlink() or target.is_symlink():
raise RuntimeError("worker status symlink rejected")
descriptor = os.open(
temporary,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
0o640,
)
try:
os.write(descriptor, (json.dumps(payload, sort_keys=True) + "\n").encode("utf-8"))
os.fsync(descriptor)
finally:
os.close(descriptor)
os.replace(temporary, target)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--state-directory", required=True, type=Path)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if os.environ.get("AIOS_PRODUCTION_DEPLOYMENT_ENABLED", "false").lower() == "true":
raise RuntimeError("Candidate worker refuses production deployment mode")
write_status(args.state_directory)
stopping = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stopping.set())
signal.signal(signal.SIGINT, lambda *_: stopping.set())
while not stopping.wait(5):
write_status(args.state_directory)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,201 +0,0 @@
"""Durable fail-closed workflow state machine."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from .errors import StateConflict
from .models import StepState, TaskPlan, TaskState, is_sha256
from .store import StateStore
@dataclass(frozen=True, slots=True)
class WorkflowDecision:
task_id: str
ready_steps: tuple[str, ...]
blocked: bool
reason: str | None = None
class WorkflowEngine:
def __init__(self, store: StateStore) -> None:
self.store = store
def _task_state(self, task_id: str, state: TaskState) -> None:
current, revision = self.store.get_task_state(task_id)
if current != state:
self.store.compare_and_set_task_state(task_id, revision, state)
def plan(self, task: TaskPlan) -> None:
self.store.create_task(
task.task_id,
task.digest(),
{
"task_id": task.task_id,
"intent": task.intent,
"branch": task.binding.branch,
"base_oid": task.binding.base_oid,
},
tuple(step.step_id for step in task.steps),
{step.step_id: step.max_attempts for step in task.steps},
)
def next_steps(self, task: TaskPlan) -> WorkflowDecision:
states = self.store.list_steps(task.task_id)
by_id = {step.step_id: step for step in task.steps}
if any(item.state == StepState.FAILED for item in states.values()):
self._task_state(task.task_id, TaskState.FAILED)
return WorkflowDecision(task.task_id, (), True, "terminal_step_failure")
ready: list[str] = []
for step_id, stored in states.items():
if stored.state not in (StepState.PENDING, StepState.READY):
continue
dependencies = by_id[step_id].depends_on
dependency_states = tuple(states[item].state for item in dependencies)
if any(item in (StepState.FAILED, StepState.BLOCKED) for item in dependency_states):
if stored.state != StepState.BLOCKED:
self.store.transition_step(
task.task_id,
step_id,
(stored.state,),
StepState.BLOCKED,
last_error="dependency_failed",
)
continue
if all(item == StepState.COMPLETED for item in dependency_states):
if stored.state == StepState.PENDING:
self.store.transition_step(
task.task_id, step_id, (StepState.PENDING,), StepState.READY
)
ready.append(step_id)
if states and all(item.state == StepState.COMPLETED for item in states.values()):
self._task_state(task.task_id, TaskState.COMPLETED)
elif ready:
self._task_state(task.task_id, TaskState.READY)
return WorkflowDecision(task.task_id, tuple(sorted(ready)), False)
def claim(self, task_id: str, step_id: str, worker_id: str, session_id: str) -> None:
if not worker_id or not session_id:
raise StateConflict("STEP_CLAIM_BINDING_REQUIRED", "worker and session are required")
self.store.transition_step(
task_id,
step_id,
(StepState.READY,),
StepState.CLAIMED,
worker_id=worker_id,
session_id=session_id,
increment_attempt=True,
)
def start(
self,
task_id: str,
step_id: str,
worker_id: str,
session_id: str,
*,
lease_seconds: int = 60,
now: datetime | None = None,
) -> None:
current = self.store.list_steps(task_id)[step_id]
if current.worker_id != worker_id or current.session_id != session_id:
raise StateConflict("STEP_CLAIM_OWNER_MISMATCH", "claim owner does not match")
now = now or datetime.now(timezone.utc)
self.store.transition_step(
task_id,
step_id,
(StepState.CLAIMED,),
StepState.RUNNING,
worker_id=worker_id,
session_id=session_id,
lease_expires_at=now + timedelta(seconds=lease_seconds),
)
self._task_state(task_id, TaskState.RUNNING)
def heartbeat(
self,
task_id: str,
step_id: str,
worker_id: str,
session_id: str,
*,
lease_seconds: int = 60,
now: datetime | None = None,
) -> None:
current = self.store.list_steps(task_id)[step_id]
if current.worker_id != worker_id or current.session_id != session_id:
raise StateConflict("STEP_CLAIM_OWNER_MISMATCH", "heartbeat owner does not match")
now = now or datetime.now(timezone.utc)
self.store.transition_step(
task_id,
step_id,
(StepState.RUNNING,),
StepState.RUNNING,
lease_expires_at=now + timedelta(seconds=lease_seconds),
)
def complete(self, task_id: str, step_id: str, *, result_digest: str) -> None:
if not is_sha256(result_digest):
raise StateConflict("RESULT_DIGEST_INVALID", "result digest must be SHA-256")
current = self.store.list_steps(task_id)[step_id]
if current.state == StepState.COMPLETED and current.result_digest == result_digest:
return
self.store.transition_step(
task_id,
step_id,
(StepState.RUNNING,),
StepState.COMPLETED,
result_digest=result_digest,
)
def fail(
self,
task: TaskPlan,
step_id: str,
reason: str,
*,
retryable: bool,
) -> StepState:
current = self.store.list_steps(task.task_id)[step_id]
next_state = (
StepState.PENDING
if retryable and current.attempt < current.max_attempts
else StepState.FAILED
)
self.store.transition_step(
task.task_id,
step_id,
(StepState.CLAIMED, StepState.RUNNING),
next_state,
last_error=reason,
clear_assignment=next_state == StepState.PENDING,
)
self._task_state(
task.task_id, TaskState.BLOCKED if next_state == StepState.PENDING else TaskState.FAILED
)
return next_state
def recover_expired(
self, task: TaskPlan, *, now: datetime | None = None
) -> tuple[str, ...]:
now = now or datetime.now(timezone.utc)
recovered: list[str] = []
for step_id, current in self.store.list_steps(task.task_id).items():
if (
current.state == StepState.RUNNING
and current.lease_expires_at is not None
and current.lease_expires_at <= now
):
self.fail(task, step_id, "lease_expired", retryable=True)
recovered.append(step_id)
return tuple(recovered)
def recoverable(self, task_id: str, step_id: str) -> None:
self.store.transition_step(
task_id,
step_id,
(StepState.BLOCKED,),
StepState.PENDING,
clear_assignment=True,
)
@@ -1,25 +0,0 @@
[build-system]
requires = ["setuptools>=69"]
build-backend = "setuptools.build_meta"
[project]
name = "aios-autonomous-release-candidate"
version = "0.1.0"
description = "Candidate-only governed autonomous release control plane"
readme = "README.md"
requires-python = ">=3.11"
license = { text = "Proprietary" }
authors = [{ name = "AIOS" }]
dependencies = []
[project.scripts]
aios-autonomous-release = "aios_autonomous_release_candidate.cli:main"
aios-autonomous-release-service = "aios_autonomous_release_candidate.service:main"
[tool.setuptools.packages.find]
where = ["."]
include = ["aios_autonomous_release_candidate*"]
[tool.pytest.ini_options]
addopts = "-ra --strict-config --strict-markers"
testpaths = ["../../tests/autonomous_release"]
@@ -1,48 +0,0 @@
from datetime import datetime, timezone, timedelta
from aios_autonomous_release_candidate.models import (
ResourceBinding,
RiskClass,
TaskPlan,
WorkflowStep,
)
from aios_autonomous_release_candidate.policy import ReleasePolicy
from aios_autonomous_release_candidate.evidence import EvidenceChain
OID = "a" * 40
def build_plan():
return TaskPlan(
task_id="test-task",
intent="candidate validation",
binding=ResourceBinding(
repository="aios/aios",
branch="patch/test",
base_oid=OID,
allowed_paths=("services/test/",),
),
risk=RiskClass.LOW,
steps=(
WorkflowStep(
step_id="validate",
worker_role="tester",
required_capabilities=("test.execute",),
allowed_paths=("services/test/",),
),
),
evidence_requirements=("tests",),
)
def test_plan_policy_accepts_candidate_plan():
ReleasePolicy().validate_task(build_plan())
def test_evidence_chain_detects_valid_closure():
chain = EvidenceChain()
chain.append("START", "test-task", {"ok": True, "secrets_included": False})
chain.append("DONE", "test-task", {"ok": True, "secrets_included": False})
assert chain.verify()
assert len(chain.closure_digest()) == 64
@@ -1,41 +0,0 @@
from pathlib import Path
import pytest
from aios_autonomous_release_candidate.artifact import ArtifactBuilder, ArtifactFile, BuiltArtifact
from aios_autonomous_release_candidate.errors import EvidenceIncomplete, ReleaseBlocked
def test_artifact_is_deterministic_and_reverified() -> None:
builder = ArtifactBuilder()
files = (
ArtifactFile("bin/run", b"#!/bin/sh\nexit 0\n", 0o755),
ArtifactFile("config/candidate.json", b'{"production":false}\n'),
)
first = builder.build("a" * 40, "b" * 40, files)
second = builder.build("a" * 40, "b" * 40, tuple(reversed(files)))
assert first.content == second.content
assert first.manifest.manifest_sha256 == second.manifest.manifest_sha256
assert first.manifest.secrets_included is False
builder.verify(first)
with pytest.raises(EvidenceIncomplete, match="ARTIFACT_DIGEST_MISMATCH"):
builder.verify(BuiltArtifact(first.content + b"tampered", first.manifest))
def test_source_tree_rejects_symlink_and_hardlink(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
target = root / "file.txt"
target.write_text("safe", encoding="utf-8")
(root / "link.txt").symlink_to(target)
with pytest.raises(ReleaseBlocked, match="ARTIFACT_SOURCE_LINK_REJECTED"):
ArtifactBuilder().build_from_directory("a" * 40, "b" * 40, root)
def test_artifact_rejects_secret_markers() -> None:
with pytest.raises(ReleaseBlocked, match="ARTIFACT_SECRET_MARKER"):
ArtifactBuilder().build(
"a" * 40,
"b" * 40,
(ArtifactFile("unsafe.env", b"password" + b"=" + b"x" * 16),),
)
@@ -1,48 +0,0 @@
from datetime import datetime, timezone, timedelta
from aios_autonomous_release_candidate.models import (
ResourceBinding,
RiskClass,
TaskPlan,
WorkflowStep,
)
from aios_autonomous_release_candidate.policy import ReleasePolicy
from aios_autonomous_release_candidate.evidence import EvidenceChain
OID = "a" * 40
def build_plan():
return TaskPlan(
task_id="test-task",
intent="candidate validation",
binding=ResourceBinding(
repository="aios/aios",
branch="patch/test",
base_oid=OID,
allowed_paths=("services/test/",),
),
risk=RiskClass.LOW,
steps=(
WorkflowStep(
step_id="validate",
worker_role="tester",
required_capabilities=("test.execute",),
allowed_paths=("services/test/",),
),
),
evidence_requirements=("tests",),
)
def test_plan_policy_accepts_candidate_plan():
ReleasePolicy().validate_task(build_plan())
def test_evidence_chain_detects_valid_closure():
chain = EvidenceChain()
chain.append("START", "test-task", {"ok": True, "secrets_included": False})
chain.append("DONE", "test-task", {"ok": True, "secrets_included": False})
assert chain.verify()
assert len(chain.closure_digest()) == 64
@@ -1,129 +0,0 @@
from datetime import datetime, timedelta, timezone
from pathlib import Path
import pytest
from aios_autonomous_release_candidate.credential import CredentialBroker
from aios_autonomous_release_candidate.errors import CredentialUnavailable, EvidenceIncomplete
from aios_autonomous_release_candidate.evidence import EvidenceChain
from aios_autonomous_release_candidate.models import CredentialRequest, ResourceBinding
from aios_autonomous_release_candidate.store import StateStore
NOW = datetime(2026, 7, 26, tzinfo=timezone.utc)
class Provider:
def __init__(self, reference: str = "systemd-credential:openclaw-gateway-candidate") -> None:
self.reference = reference
self.active: set[str] = set()
self.revoked: list[str] = []
self.count = 0
def issue(self, request, expires_at):
self.count += 1
credential_id = f"credential-{self.count}"
self.active.add(credential_id)
return credential_id, self.reference
def revoke(self, credential_id, reference):
self.active.discard(credential_id)
self.revoked.append(credential_id)
def is_usable(self, credential_id, reference):
return credential_id in self.active
def request(request_id: str = "request-1") -> CredentialRequest:
return CredentialRequest(
request_id=request_id,
task_id="task-credential",
session_id="session-1",
worker_id="worker-1",
audience="openclaw-candidate",
consumer_service="aios-autonomous-release-worker-candidate.service",
binding=ResourceBinding("aios/aios", "patch/credential-test", "a" * 40, ("services/test",)),
operations=("native_session.spawn",),
ttl_seconds=300,
max_uses=1,
provider="openclaw",
runtime_user="aiman",
approval_plan_id="owner-stage-b-native-sessions-20260726",
not_before=NOW,
expires_at=NOW + timedelta(minutes=5),
nonce="nonce-credential-1",
)
def test_credential_is_scope_bound_revocable_and_idempotent(tmp_path: Path) -> None:
provider = Provider()
broker = CredentialBroker(StateStore(tmp_path / "state.sqlite3"), provider)
projection = broker.issue(request(), NOW)
broker.project(projection)
broker.authorize(
projection,
task_id="task-credential",
session_id="session-1",
worker_id="worker-1",
audience="openclaw-candidate",
consumer_service="aios-autonomous-release-worker-candidate.service",
operation="native_session.spawn",
path="services/test/file.py",
now=NOW,
)
with pytest.raises(CredentialUnavailable, match="CREDENTIAL_SCOPE_MISMATCH"):
broker.authorize(
projection,
task_id="other-task",
session_id="session-1",
worker_id="worker-1",
audience="openclaw-candidate",
consumer_service="aios-autonomous-release-worker-candidate.service",
operation="native_session.spawn",
path="services/test/file.py",
now=NOW,
)
first = broker.revoke(projection.credential_id, projection.reference, NOW)
second = broker.revoke(projection.credential_id, projection.reference, NOW)
assert first == second
broker.verify_revoked(projection.credential_id, projection.reference)
def test_persistence_failure_compensates_upstream_issuance(tmp_path: Path) -> None:
provider = Provider()
broker = CredentialBroker(StateStore(tmp_path / "state.sqlite3"), provider)
broker.issue(request(), NOW)
with pytest.raises(CredentialUnavailable, match="CREDENTIAL_PERSISTENCE_FAILED"):
broker.issue(request("request-2"), NOW)
assert "credential-2" in provider.revoked
def test_plaintext_or_untrusted_projection_is_rejected(tmp_path: Path) -> None:
provider = Provider("/run/credentials/candidate/raw-value")
broker = CredentialBroker(StateStore(tmp_path / "state.sqlite3"), provider)
with pytest.raises(CredentialUnavailable, match="CREDENTIAL_REFERENCE_REJECTED"):
broker.issue(request(), NOW)
assert provider.revoked == ["credential-1"]
def test_evidence_chain_survives_restart_and_detects_tampering(tmp_path: Path) -> None:
store = StateStore(tmp_path / "state.sqlite3")
chain = EvidenceChain(store, "task-evidence")
chain.append("TASK_STARTED", "task-evidence", {"secrets_included": False})
chain.append(
"TESTS_PASSED",
"task-evidence",
{"passed": 42, "skipped": 0, "secrets_included": False},
)
reopened = EvidenceChain(store, "task-evidence")
assert reopened.verify()
assert len(reopened.close(("TASK_STARTED", "TESTS_PASSED"))) == 64
with pytest.raises(EvidenceIncomplete, match="EVIDENCE_REQUIREMENT_MISSING"):
reopened.close(("NATIVE_SESSION_VERIFIED",))
with store.transaction() as connection:
connection.execute(
"UPDATE events SET payload_json=? WHERE task_id=? AND sequence=1",
('{"secrets_included":true}', "task-evidence"),
)
assert EvidenceChain(store, "task-evidence").verify() is False
@@ -1,125 +0,0 @@
from datetime import datetime, timedelta, timezone
import pytest
from aios_autonomous_release_candidate.approval import (
ApprovalVerifier,
StaticRevocationSource,
UnavailableSignatureVerifier,
)
from aios_autonomous_release_candidate.errors import ApprovalRequired
from aios_autonomous_release_candidate.gitea_canary import (
EphemeralKeyProjection,
GiteaDeployKeyCanary,
)
from aios_autonomous_release_candidate.models import (
ApprovalEnvelope,
ResourceBinding,
RiskClass,
TaskPlan,
WorkflowStep,
)
from aios_autonomous_release_candidate.store import StateStore
NOW = datetime(2026, 7, 26, tzinfo=timezone.utc)
OID = "a" * 40
def task_plan() -> TaskPlan:
return TaskPlan(
"external-gate-task",
"external gate verification",
ResourceBinding("aios/aios", "patch/external-gate", OID, ("services/test",)),
RiskClass.HIGH,
(WorkflowStep("canary", "tester", ("native_session.spawn",)),),
("external-gates",),
)
def test_unknown_owner_verifier_fails_with_stable_blocker(tmp_path) -> None:
plan = task_plan()
envelope = ApprovalEnvelope(
approval_plan_id="owner-external-gate",
task_id=plan.task_id,
task_plan_sha256=plan.digest(),
binding=plan.binding,
capabilities=("native_session.spawn",),
risk=RiskClass.HIGH,
not_before=NOW - timedelta(minutes=1),
expires_at=NOW + timedelta(minutes=5),
nonce="nonce-owner-external",
signing_key_id="owner-kms-1",
signature="opaque-signature",
)
verifier = ApprovalVerifier(
StateStore(tmp_path / "state.sqlite3"),
UnavailableSignatureVerifier(),
StaticRevocationSource(),
)
with pytest.raises(
ApprovalRequired, match="BLOCKED_OWNER_APPROVAL_VERIFIER_UNAVAILABLE"
):
verifier.verify(plan, envelope, now=NOW)
class KeyProjection:
def __init__(self) -> None:
self.present = False
def create(self, task_id, branch):
self.present = True
return EphemeralKeyProjection(
public_key="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICandidateCanaryOnly",
private_key_reference="systemd-credential:gitea-deploy-key-canary",
)
def delete(self, reference):
self.present = False
def is_present(self, reference):
return self.present
class Gitea:
simulation = True
def __init__(self) -> None:
self.keys = {}
def list_deploy_keys(self, repository, credential_reference):
return list(self.keys.values())
def create_deploy_key(self, repository, title, public_key, credential_reference):
item = {"id": 1, "title": title, "key": public_key, "read_only": False}
self.keys[1] = item
return item
def delete_deploy_key(self, repository, key_id, credential_reference):
self.keys.pop(key_id, None)
class SSHProbe:
def __init__(self, gitea, projection) -> None:
self.gitea = gitea
self.projection = projection
def authenticate(self, repository, branch, private_key_reference):
return bool(self.gitea.keys) and self.projection.present
def test_gitea_canary_always_revokes_key_and_projection() -> None:
projection = KeyProjection()
gitea = Gitea()
result = GiteaDeployKeyCanary(gitea, projection, SSHProbe(gitea, projection)).run(
task_id="external-gate-task",
branch="patch/external-gate",
credential_reference="systemd-credential:gitea-admin-api-candidate",
)
assert result.key_created is True
assert result.key_deleted is True
assert result.post_revoke_auth_rejected is True
assert result.credential_projection_deleted is True
assert result.gitea_admin_api_lifecycle_canary is False # simulation is not real evidence
assert gitea.keys == {}
assert projection.present is False
@@ -1,156 +0,0 @@
from contextlib import contextmanager
import hashlib
import json
from pathlib import Path
from threading import Thread
import urllib.error
import urllib.request
from aios_autonomous_release_candidate.service import CandidateApplication, CandidateHTTPServer
@contextmanager
def running_server(tmp_path: Path):
application = CandidateApplication(tmp_path / "state.sqlite3")
server = CandidateHTTPServer(("127.0.0.1", 0), application)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
yield application, f"http://127.0.0.1:{server.server_port}"
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
def get_json(url: str):
with urllib.request.urlopen(url, timeout=5) as response:
return response.status, json.load(response)
def post_json(url: str, payload: dict):
request = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
method="POST",
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request, timeout=5) as response:
return response.status, json.load(response)
def test_health_and_readiness_are_loopback_safe(tmp_path: Path) -> None:
with running_server(tmp_path) as (_, base):
status, health = get_json(f"{base}/health")
assert status == 200
assert health["production_side_effects"] is False
status, ready = get_json(f"{base}/ready")
assert status == 200
assert ready == {"status": "ready", "secrets_included": False}
def test_json_content_type_is_required(tmp_path: Path) -> None:
with running_server(tmp_path) as (_, base):
request = urllib.request.Request(f"{base}/v1/tasks", data=b"{}", method="POST")
try:
urllib.request.urlopen(request, timeout=5)
except urllib.error.HTTPError as error:
assert error.code == 415
assert json.load(error)["error"] == "content_type_required"
def test_internal_exception_text_is_not_disclosed(tmp_path: Path) -> None:
with running_server(tmp_path) as (application, base):
def fail(_payload):
raise RuntimeError("sensitive internal diagnostic")
application.api.create_task = fail
request = urllib.request.Request(
f"{base}/v1/tasks",
data=b"{}",
method="POST",
headers={"Content-Type": "application/json"},
)
try:
urllib.request.urlopen(request, timeout=5)
except urllib.error.HTTPError as error:
assert error.code == 500
body = error.read().decode("utf-8")
assert "candidate_request_failed" in body
assert "sensitive internal diagnostic" not in body
def test_task_lifecycle_evidence_and_release_gate_endpoints(tmp_path: Path) -> None:
task_id = "api-lifecycle-task"
task = {
"task_id": task_id,
"intent": "exercise candidate API",
"binding": {
"repository": "aios/aios",
"branch": "patch/api-lifecycle",
"base_oid": "a" * 40,
"allowed_paths": ["services/test"],
},
"risk": "medium",
"steps": [
{
"step_id": "test-step",
"worker_role": "tester",
"required_capabilities": ["test.execute"],
}
],
"evidence_requirements": ["STEP_RESULT"],
"production_side_effects_allowed": False,
}
with running_server(tmp_path) as (_, base):
status, created = post_json(f"{base}/v1/tasks", task)
assert status == 201
assert created["production_side_effects"] is False
status, steps = get_json(f"{base}/v1/tasks/{task_id}/steps")
assert status == 200
assert steps["steps"]["test-step"]["state"] == "READY"
binding = {
"step_id": "test-step",
"worker_id": "worker-1",
"session_id": "session-1",
}
_, claimed = post_json(f"{base}/v1/tasks/{task_id}/claim", binding)
assert claimed["state"] == "RUNNING"
_, heartbeat = post_json(f"{base}/v1/tasks/{task_id}/heartbeat", binding)
assert heartbeat["state"] == "RUNNING"
_, result = post_json(
f"{base}/v1/tasks/{task_id}/result",
{"step_id": "test-step", "result_digest": hashlib.sha256(b"ok").hexdigest()},
)
assert result["state"] == "COMPLETED"
_, evidence = get_json(f"{base}/v1/tasks/{task_id}/evidence")
assert evidence["verified"] is True
assert any(item["event_type"] == "STEP_RESULT" for item in evidence["events"])
_, closure = post_json(f"{base}/v1/tasks/{task_id}/evidence/close", {})
assert len(closure["evidence_closure"]) == 64
release_plan = {
"task_id": task_id,
"repository": "aios/aios",
"branch": "integration/api-lifecycle",
"version": "SHADOW-0.1.0",
"target_oid": "b" * 40,
"tree_oid": "c" * 40,
"artifact_sha256": "d" * 64,
"manifest_sha256": "e" * 64,
"package_closure_sha256": "f" * 64,
"evidence_closure_oid": "1" * 40,
"approval_plan_id": "external-owner-release",
"rollback_approval_plan_id": "external-owner-rollback",
"rollback_artifact_sha256": "2" * 64,
"expected_tag_oid": "3" * 40,
"expected_target_oid": "b" * 40,
"production_config_sha256": "4" * 64,
}
_, release = post_json(f"{base}/v1/release/plan", release_plan)
assert release["release_state"] == "BUILT"
_, verified = post_json(
f"{base}/v1/release/verify", {"artifact_ok": True, "evidence_ok": True}
)
assert verified["release_state"] == "WAITING_OWNER"
assert verified["production_release"] == "BLOCKED_EXTERNAL_OWNER_SIGNATURE"
@@ -1,94 +0,0 @@
from typing import Any, Mapping
import pytest
from aios_autonomous_release_candidate.errors import StateConflict
from aios_autonomous_release_candidate.native_adapters import (
CodexNativeAdapter,
OpenClawNativeAdapter,
)
class Provider:
simulation = True
def __init__(self, name: str) -> None:
self.name = name
self.state = "ACTIVE"
self.parent = "parent-1"
def spawn(self, request: Mapping[str, Any], credential_reference: str):
return {
"provider_session_id": f"{self.name}-native-1",
"task_id": request["task_id"],
"parent_session_id": request["parent_session_id"],
"candidate_only": True,
"production_session_access": False,
}
def status(self, provider_session_id: str, credential_reference: str):
return {"provider_session_id": provider_session_id, "state": self.state}
def list_tree(self, parent_session_id: str, credential_reference: str):
return [{"provider_session_id": f"{self.name}-native-1", "parent_session_id": parent_session_id}]
def send_text(self, provider_session_id, payload, credential_reference):
return {
"content_type": "text/plain",
"encoding": "utf-8",
"image_only": False,
"task_id": payload["task_id"],
"message_id": "message-1",
"receipt": "receipt-1",
"recipient_binding": payload["recipient_binding"],
"secrets_included": False,
}
def interrupt(self, provider_session_id: str, credential_reference: str):
self.state = "INTERRUPTED"
def close(self, provider_session_id: str, credential_reference: str):
self.state = "CLOSED"
@pytest.mark.parametrize(
("adapter_type", "provider_name", "reference", "flag"),
(
(
OpenClawNativeAdapter,
"openclaw",
"systemd-credential:openclaw-gateway-candidate",
"openclaw_native_child_session_verified",
),
(
CodexNativeAdapter,
"codex",
"systemd-credential:codex-app-server-candidate",
"codex_child_session_verified",
),
),
)
def test_native_adapter_preserves_identity_and_text_contract(
adapter_type, provider_name, reference, flag
) -> None:
adapter = adapter_type(Provider(provider_name), reference)
session = adapter.spawn("task-native", "parent-1", {"recipient_id": "weixin-owner"})
assert session.broker_session_id != session.provider_session_id
verification = adapter.verify(session)
assert verification.broker_session_verified is True
assert getattr(verification, flag) is False # simulated providers are never real canary evidence
result = adapter.send_text(session, "hello")
assert result.content_type == "text/plain"
assert result.secrets_included is False
adapter.interrupt(session)
assert adapter.status(session) == "INTERRUPTED"
adapter.close(session)
assert adapter.status(session) == "CLOSED"
def test_recipient_binding_must_be_single_and_trusted() -> None:
adapter = OpenClawNativeAdapter(
Provider("openclaw"), "systemd-credential:openclaw-gateway-candidate"
)
with pytest.raises(StateConflict, match="RECIPIENT_BINDING_REQUIRED"):
adapter.spawn("task-native", "parent-1", {"recipients": ["one", "two"]})
@@ -1,95 +0,0 @@
from dataclasses import replace
from datetime import datetime, timezone
import pytest
from aios_autonomous_release_candidate.approval import ApprovalVerificationResult
from aios_autonomous_release_candidate.errors import ReleaseBlocked
from aios_autonomous_release_candidate.models import ReleasePlan
from aios_autonomous_release_candidate.release import ReleaseController, ReleaseState
from aios_autonomous_release_candidate.release_gate import ReleaseEvidence, ReleaseGate
from aios_autonomous_release_candidate.tag import TagReference, TagVerifier
OID = "a" * 40
TAG_OID = "b" * 40
DIGEST = "c" * 64
def plan() -> ReleasePlan:
return ReleasePlan(
task_id="release-task",
repository="aios/aios",
branch="integration/autonomous-release",
version="V2R1C4",
target_oid=OID,
tree_oid="d" * 40,
artifact_sha256=DIGEST,
manifest_sha256="e" * 64,
package_closure_sha256="3" * 64,
evidence_closure_oid="f" * 40,
approval_plan_id="owner-release-v2r1c4",
rollback_approval_plan_id="owner-rollback-v2r1c4",
rollback_artifact_sha256="1" * 64,
expected_tag_oid=TAG_OID,
expected_target_oid=OID,
production_config_sha256="2" * 64,
)
def approval(plan_id: str) -> ApprovalVerificationResult:
return ApprovalVerificationResult(
approval_plan_id=plan_id,
task_id="release-task",
signing_key_id="owner-kms-1",
verified_at=datetime(2026, 7, 26, tzinfo=timezone.utc),
)
def test_deploy_and_rollback_require_distinct_verified_approvals() -> None:
controller = ReleaseController()
controller.attach_plan(plan())
controller.mark_built()
controller.verify(artifact_ok=True, evidence_ok=True)
controller.request_owner_approval()
with pytest.raises(ReleaseBlocked, match="OWNER_APPROVAL_PLAN_MISMATCH"):
controller.approve(approval("wrong-plan"))
controller.approve(approval("owner-release-v2r1c4"))
controller.start_deployment()
controller.mark_deployed()
controller.request_rollback("candidate acceptance regression")
with pytest.raises(ReleaseBlocked, match="ROLLBACK_APPROVAL_PLAN_MISMATCH"):
controller.approve_rollback(approval("owner-release-v2r1c4"))
controller.approve_rollback(approval("owner-rollback-v2r1c4"))
controller.start_rollback()
assert controller.mark_rolled_back().state == ReleaseState.ROLLED_BACK
def test_annotated_tag_uses_object_and_peeled_oids_separately() -> None:
tag = TagReference("refs/tags/V2R1C4", "tag", TAG_OID, OID)
TagVerifier().verify(tag, expected_tag_oid=TAG_OID, expected_target_oid=OID)
with pytest.raises(ReleaseBlocked, match="TAG_PEELED_TARGET_MISMATCH"):
TagVerifier().verify(tag, expected_tag_oid=TAG_OID, expected_target_oid="3" * 40)
def test_release_gate_requires_four_source_tag_and_artifact_closure() -> None:
evidence = ReleaseEvidence(
expected_version="V2R1C4",
registry_version="V2R1C4",
baseline_version="V2R1C4",
local_tag_oid=TAG_OID,
remote_tag_oid=TAG_OID,
gitea_tag_oid=TAG_OID,
expected_tag_oid=TAG_OID,
local_peeled_target_oid=OID,
remote_peeled_target_oid=OID,
gitea_peeled_target_oid=OID,
expected_target_oid=OID,
artifact_sha256=DIGEST,
manifest_sha256="e" * 64,
evidence_sha256="4" * 64,
rollback_artifact_sha256="1" * 64,
)
ReleaseGate().verify(evidence)
with pytest.raises(ReleaseBlocked, match="TAG_GITEA_MISMATCH"):
ReleaseGate().verify(replace(evidence, gitea_tag_oid="5" * 40))
@@ -1,116 +0,0 @@
from datetime import datetime, timedelta, timezone
from pathlib import Path
import pytest
from aios_autonomous_release_candidate.errors import CredentialUnavailable
from aios_autonomous_release_candidate.evidence import EvidenceChain
from aios_autonomous_release_candidate.execution_manager import ExecutionManager
from aios_autonomous_release_candidate.models import CredentialRequest, ResourceBinding
from aios_autonomous_release_candidate.native_adapters import OpenClawNativeAdapter
from aios_autonomous_release_candidate.secret_manager import SecretManagerV2
from aios_autonomous_release_candidate.store import StateStore
from test_native_adapter_contracts import Provider
NOW = datetime(2026, 7, 26, tzinfo=timezone.utc)
class Transport:
def __init__(self) -> None:
self.calls = []
def exchange(self, operation, payload):
self.calls.append((operation, payload))
base = {
"task_id": payload["task_id"],
"credential_id": payload.get("credential_id", "credential-openclaw-1"),
"plaintext_secret_returned": False,
}
if operation == "issue":
return {
**base,
"credential_reference": "systemd-credential:openclaw-gateway-candidate",
"state": "ISSUED",
"expires_at": payload["expires_at"],
}
if operation == "audit":
return {**base, "events": [{"operation": "issue", "secrets_included": False}]}
if operation in {"project", "status"}:
return {**base, "state": "PROJECTED"}
if operation == "verify_revocation":
return {**base, "state": "REVOKED", "post_revocation_auth": "REJECTED"}
return {**base, "state": "REVOKED"}
def credential_request() -> CredentialRequest:
return CredentialRequest(
request_id="request-1",
task_id="task-execution",
session_id="session-1",
worker_id="worker-1",
audience="openclaw-candidate",
consumer_service="aios-autonomous-release-worker-candidate.service",
binding=ResourceBinding("aios/aios", "patch/execution", "a" * 40, ("services/test",)),
operations=("native_session.spawn",),
provider="openclaw",
runtime_user="aiman",
approval_plan_id="owner-stage-b-native-sessions-20260726",
not_before=NOW,
expires_at=NOW + timedelta(minutes=5),
nonce="nonce-execution-1",
ttl_seconds=300,
max_uses=1,
)
def test_secret_manager_uses_reference_only_protocol() -> None:
transport = Transport()
manager = SecretManagerV2(transport)
receipt = manager.issue(credential_request(), NOW)
assert receipt.reference == "systemd-credential:openclaw-gateway-candidate"
assert receipt.plaintext_secret_returned is False
manager.project(receipt)
manager.status(receipt)
manager.revoke(receipt)
manager.verify_revocation(receipt)
events = manager.audit(receipt)
assert events[0]["secrets_included"] is False
assert [operation for operation, _ in transport.calls] == [
"issue",
"project",
"status",
"revoke",
"verify_revocation",
"audit",
]
def test_secret_manager_rejects_plaintext_fields() -> None:
transport = Transport()
def unsafe(operation, payload):
return {"task_id": payload["task_id"], "credential_id": "credential-1", "api_key": "redacted"}
transport.exchange = unsafe
with pytest.raises(CredentialUnavailable, match="SECRET_MANAGER_PLAINTEXT_REJECTED"):
SecretManagerV2(transport).issue(credential_request(), NOW)
def test_execution_manager_records_simulation_without_native_claim(tmp_path: Path) -> None:
evidence = EvidenceChain(StateStore(tmp_path / "state.sqlite3"), "task-execution")
adapter = OpenClawNativeAdapter(
Provider("openclaw"), "systemd-credential:openclaw-gateway-candidate"
)
result = ExecutionManager(evidence).run_native_canary(
adapter,
task_id="task-execution",
parent_session_id="parent-1",
recipient_binding={"recipient_id": "weixin-owner"},
text="candidate canary",
)
assert result.broker_session_verified is True
assert result.openclaw_native_child_session_verified is False
assert result.simulation is True
assert result.session_closed is True
assert evidence.verify()
@@ -1,89 +0,0 @@
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import stat
import pytest
from aios_autonomous_release_candidate.errors import StateConflict
from aios_autonomous_release_candidate.models import (
ResourceBinding,
RiskClass,
StepState,
TaskPlan,
WorkflowStep,
)
from aios_autonomous_release_candidate.store import StateStore
from aios_autonomous_release_candidate.workflow import WorkflowEngine
OID = "a" * 40
def plan() -> TaskPlan:
return TaskPlan(
task_id="workflow-test",
intent="exercise durable workflow",
binding=ResourceBinding("aios/aios", "patch/workflow-test", OID, ("services/test",)),
risk=RiskClass.LOW,
steps=(
WorkflowStep("build", "builder", ("file.write",), max_attempts=2, timeout_seconds=30),
WorkflowStep("test", "tester", ("test.execute",), depends_on=("build",), max_attempts=2),
),
evidence_requirements=("tests",),
)
def test_store_is_private_and_rejects_schema_drift(tmp_path: Path) -> None:
state = tmp_path / "private" / "state.sqlite3"
StateStore(state)
assert stat.S_IMODE(state.stat().st_mode) == 0o600
assert stat.S_IMODE(state.parent.stat().st_mode) & 0o027 == 0
with StateStore(state).transaction() as connection:
connection.execute("UPDATE metadata SET value='999' WHERE key='schema_version'")
with pytest.raises(StateConflict, match="SCHEMA_VERSION_UNSUPPORTED"):
StateStore(state)
def test_dependency_retry_and_terminal_failure_are_durable(tmp_path: Path) -> None:
store = StateStore(tmp_path / "state.sqlite3")
engine = WorkflowEngine(store)
task = plan()
engine.plan(task)
assert engine.next_steps(task).ready_steps == ("build",)
assert store.list_steps(task.task_id)["build"].state == StepState.READY
engine.claim(task.task_id, "build", "writer", "session-1")
engine.start(task.task_id, "build", "writer", "session-1")
engine.complete(task.task_id, "build", result_digest="b" * 64)
assert engine.next_steps(task).ready_steps == ("test",)
engine.claim(task.task_id, "test", "tester", "session-2")
engine.start(task.task_id, "test", "tester", "session-2")
assert engine.fail(task, "test", "transient", retryable=True) == StepState.PENDING
assert engine.next_steps(task).ready_steps == ("test",)
engine.claim(task.task_id, "test", "tester", "session-3")
engine.start(task.task_id, "test", "tester", "session-3")
assert engine.fail(task, "test", "still broken", retryable=True) == StepState.FAILED
decision = engine.next_steps(task)
assert decision.blocked is True
assert decision.reason == "terminal_step_failure"
def test_claim_is_atomic_under_contention(tmp_path: Path) -> None:
store = StateStore(tmp_path / "state.sqlite3")
engine = WorkflowEngine(store)
task = plan()
engine.plan(task)
engine.next_steps(task)
def claim(worker: str) -> str:
try:
engine.claim(task.task_id, "build", worker, f"session-{worker}")
return "claimed"
except StateConflict:
return "rejected"
with ThreadPoolExecutor(max_workers=2) as pool:
results = list(pool.map(claim, ("one", "two")))
assert sorted(results) == ["claimed", "rejected"]
@@ -0,0 +1,228 @@
import json
import tempfile
import threading
import unittest
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import os
import sys
ROOT = os.getcwd()
sys.path.insert(0, str(Path(ROOT) / "active-runtime"))
from app.task_plane.workers.command_policy import CommandPolicy
from app.task_plane.workers.task_client import (
CredentialSource,
StageAClientConfig,
TaskClientAuthError,
TaskClientProtocolError,
TaskClientResponseTooLargeError,
TaskPlaneClient,
)
def _start_server(responses):
class Handler(BaseHTTPRequestHandler):
request_count = 0
seen = []
def do_POST(self):
index = Handler.request_count
Handler.request_count += 1
payload_length = int(self.headers.get("Content-Length", "0"))
payload = self.rfile.read(payload_length)
try:
payload_obj = json.loads(payload.decode("utf-8") or "{}")
except Exception:
payload_obj = {"raw": payload.decode("utf-8", errors="replace")}
Handler.seen.append((self.path, payload_obj))
status, response_obj = responses[min(index, len(responses) - 1)]
body = json.dumps(response_obj).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *_args, **_kwargs):
return
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
return server, thread, Handler
def _stop_server(server, thread):
server.shutdown()
server.server_close()
thread.join(timeout=2)
class CodexWorkerCommandPolicyTests(unittest.TestCase):
def test_rejects_revision_and_path_injection(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
policy = CommandPolicy(root, root / "results")
with self.assertRaises(ValueError):
policy.build("git-show", oid="HEAD~1", paths=["active-runtime/app/task_plane/api.py"])
with self.assertRaises(ValueError):
policy.build("pytest", oid="0" * 40, paths=["../outside"])
class CodexWorkerClientStageBTests(unittest.TestCase):
def test_client_retry_on_retryable_status(self):
server, thread, handler = _start_server([(503, {"error": "try again"}), (200, {"ok": True})])
try:
client = TaskPlaneClient(
StageAClientConfig(base_url=f"http://127.0.0.1:{server.server_port}"),
token="stage-b-token-" + "x" * 32,
)
body = client._post("/tests/retry", {"probe": "x"}, idempotency_key="retry")
self.assertEqual(body, {"ok": True})
self.assertEqual(handler.request_count, 2)
self.assertTrue(handler.seen[0][0].startswith("/tests/retry"))
finally:
_stop_server(server, thread)
def test_client_stops_retry_on_auth_error(self):
server, thread, handler = _start_server([(403, {"error": "denied"})])
try:
client = TaskPlaneClient(
StageAClientConfig(base_url=f"http://127.0.0.1:{server.server_port}"),
token="stage-b-token-" + "x" * 32,
)
with self.assertRaises(TaskClientAuthError):
client._post("/tests/auth", {"probe": "x"}, idempotency_key="auth")
self.assertEqual(handler.request_count, 1)
finally:
_stop_server(server, thread)
def test_client_rejects_oversize_request_and_response(self):
server, thread, _ = _start_server([(200, {"ok": True})])
try:
small = TaskPlaneClient(
StageAClientConfig(
base_url=f"http://127.0.0.1:{server.server_port}",
max_request_bytes=8,
),
token="stage-b-token-" + "x" * 32,
)
with self.assertRaises(TaskClientResponseTooLargeError):
small._post("/tests/request", {"probe": "too-large"}, idempotency_key="req")
finally:
_stop_server(server, thread)
oversize = {"blob": "x" * 1_100_000}
server, thread, _ = _start_server([(200, oversize)])
try:
client = TaskPlaneClient(
StageAClientConfig(base_url=f"http://127.0.0.1:{server.server_port}"),
token="stage-b-token-" + "x" * 32,
)
with self.assertRaises(TaskClientResponseTooLargeError):
client._post("/tests/response", {"probe": "ok"}, idempotency_key="resp")
finally:
_stop_server(server, thread)
def test_client_credential_source_symlink_and_mode(self):
with tempfile.TemporaryDirectory() as directory:
directory = Path(directory)
token_file = directory / "token"
token_file.write_text("x" * 32, encoding="utf-8")
token_file.chmod(0o600)
import grp
import pwd
owner = pwd.getpwuid(os.getuid()).pw_name
group = grp.getgrgid(os.getgid()).gr_name
source = CredentialSource(
token_file,
token_owner=owner,
token_group=group,
expected_mode=0o600,
)
self.assertEqual(source.read_token(), "x" * 32)
symlink = directory / "token-link"
symlink.symlink_to(token_file)
with self.assertRaises(TaskClientAuthError):
CredentialSource(
symlink,
token_owner=owner,
token_group=group,
expected_mode=0o600,
).read_token()
bad_mode = directory / "bad-mode"
bad_mode.write_text("value", encoding="utf-8")
bad_mode.chmod(0o644)
with self.assertRaises(TaskClientAuthError):
CredentialSource(
bad_mode,
token_owner=owner,
token_group=group,
expected_mode=0o600,
).read_token()
def test_client_rejects_missing_claim_id_and_reports_heartbeat_defaults(self):
client = TaskPlaneClient(StageAClientConfig(), token="stage-b-token-" + "x" * 32)
with self.assertRaises(TaskClientProtocolError):
client.progress("task-id", claim_id="", progress=10, stage="RUN")
with self.assertRaises(TaskClientProtocolError):
client.result(
"task-id",
claim_id="",
status="COMPLETED",
outputs=[],
evidence=[],
started_at="",
completed_at="",
)
with self.assertRaises(TaskClientProtocolError):
client.fail("task-id", claim_id="")
def test_heartbeat_request_uses_default_lease(self):
captured = {}
def handler_factory(responses):
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
payload_length = int(self.headers.get("Content-Length", "0"))
captured["path"] = self.path
captured["payload"] = json.loads(self.rfile.read(payload_length).decode("utf-8") or "{}")
status, response_obj = responses[min(responses_index[0], len(responses) - 1)]
responses_index[0] += 1
response = json.dumps(response_obj).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(response)))
self.end_headers()
self.wfile.write(response)
def log_message(self, *_args, **_kwargs):
return
return Handler
responses_index = [0]
server = ThreadingHTTPServer(("127.0.0.1", 0), handler_factory([(200, {"claim_id": "x"}), (200, {"ok": True})]))
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
client = TaskPlaneClient(
StageAClientConfig(base_url=f"http://127.0.0.1:{server.server_port}"),
token="stage-b-token-" + "x" * 32,
)
client.claim_heartbeat("task-id", claim_id="claim-id")
self.assertEqual(captured["payload"], {"claim_id": "claim-id", "lease_seconds": 120})
self.assertTrue(captured["path"].endswith("/tasks/task-id/heartbeat"))
captured["payload"] = None
client.worker_heartbeat("worker-1")
self.assertEqual(captured["payload"], {})
finally:
_stop_server(server, thread)
@@ -0,0 +1,90 @@
import os
import stat
import tempfile
import unittest
from pathlib import Path
ROOT = os.getcwd()
import sys
sys.path.insert(0, str(Path(ROOT) / "active-runtime"))
from app.task_plane.workers.codex_review_test import CodexReviewTaskPolicy
from app.task_plane.workers.result_attestation import TaskResultEnvelope, TrustPolicy, task_result_envelope
from app.task_plane.workers.workspace import TaskWorkspace
class CodexWorkerWorkspaceRecoveryTests(unittest.TestCase):
def test_workspace_permissions_and_cleanup(self):
with tempfile.TemporaryDirectory() as directory:
root_path = Path(directory) / "task-root"
with TaskWorkspace.create(root_path, task_id="task-01", claim_id="claim-01") as workspace:
for path in (
workspace.layout.workspace_root,
workspace.layout.repo_root,
workspace.layout.result_root,
workspace.layout.home,
workspace.layout.tmp,
):
self.assertEqual(stat.S_IMODE(path.stat().st_mode), 0o700)
result_path = workspace.write_result(name="result.json", payload="{}")
self.assertTrue(result_path.exists())
self.assertEqual(stat.S_IMODE(result_path.stat().st_mode), 0o600)
self.assertFalse(root_path.exists())
def test_result_envelope_validates_required_contract_fields(self):
envelope = task_result_envelope(
task_id="task-01",
claim_id="claim-01",
worker_identity="spiffe://aios/agent/codex/review-01",
status="COMPLETED",
outputs=[],
evidence=[],
started_at="2026-07-24T00:00:00Z",
completed_at="2026-07-24T00:00:01Z",
)
payload = envelope.to_dict()
self.assertEqual(payload["production_trust"], False)
self.assertEqual(payload["trust_level"], TrustPolicy.SHADOW_ATTESTED.value)
self.assertEqual(payload["task_id"], "task-01")
invalid = TaskResultEnvelope(
task_id="",
claim_id="claim",
worker_identity="worker",
status="FAILED",
outputs=[],
evidence=[],
started_at="",
completed_at="",
production_trust=False,
)
with self.assertRaises(ValueError):
invalid.validate()
def test_shadow_policy_enforces_concurrency_and_timeboxes(self):
policy = CodexReviewTaskPolicy(worker_id="worker-01", identity="identity")
self.assertEqual(policy.max_concurrency, 1)
self.assertEqual(policy.heartbeat_interval_seconds, 15)
self.assertEqual(policy.claim_lease_seconds, 120)
valid_task = {
"risk": "low",
"metadata": {"production_side_effects": False},
}
policy.assert_shadow_only_task(valid_task)
with self.assertRaises(Exception):
policy.assert_shadow_only_task(
{
"risk": "critical",
"metadata": {"production_side_effects": False},
}
)
with self.assertRaises(Exception):
CodexReviewTaskPolicy(
worker_id="worker-01",
identity="identity",
max_concurrency=2,
).assert_shadow_only_task(valid_task)
@@ -0,0 +1,83 @@
import os
import tempfile
import unittest
from pathlib import Path
ROOT = os.getcwd()
import sys
sys.path.insert(0, str(Path(ROOT) / "active-runtime"))
from app.task_plane.workers.command_policy import CommandPolicy
from app.task_plane.workers.runner import CommandDisallowedError, SafeCommandRunner, CommandPolicyError
from app.task_plane.workers.workspace import TaskWorkspace
class CodexWorkerRunnerSecurityTests(unittest.TestCase):
def test_runner_only_allows_fixed_command_set(self):
with tempfile.TemporaryDirectory() as directory:
with TaskWorkspace.create(Path(directory) / "workspace", task_id="task-fixed", claim_id="claim-fixed") as workspace:
runner = SafeCommandRunner(workspace.layout.repo_root, workspace.layout.result_root)
with self.assertRaises(CommandDisallowedError):
runner.run("shell", oid="0" * 40)
with self.assertRaises(CommandDisallowedError):
runner.run("bash", oid="0" * 40, paths=["tests/task_plane/public_interface"])
def test_runner_rejects_shell_like_and_eval_inputs(self):
with tempfile.TemporaryDirectory() as directory:
with TaskWorkspace.create(Path(directory) / "workspace", task_id="task-shell", claim_id="claim-shell") as workspace:
runner = SafeCommandRunner(workspace.layout.repo_root, workspace.layout.result_root)
CommandPolicy(workspace.layout.repo_root, workspace.layout.result_root).build(
"pytest",
oid="0" * 40,
paths=["tests/task_plane/public_interface"],
)
with self.assertRaises(CommandDisallowedError):
runner.run("pytest", oid="0" * 40, paths=["tests/task_plane/public_interface;touch /tmp/x"])
with self.assertRaises(CommandDisallowedError):
runner.run("pytest", oid="0" * 40, paths=["tests/task_plane/public_interface", "eval"])
with self.assertRaises(CommandDisallowedError):
runner.run("pytest", oid="0" * 40, paths=["tests/task_plane/public_interface", "bash"])
def test_runner_rejects_revision_expression_and_symlink_paths(self):
with tempfile.TemporaryDirectory() as directory:
with TaskWorkspace.create(Path(directory) / "workspace", task_id="task-revision", claim_id="claim-revision") as workspace:
runner = SafeCommandRunner(workspace.layout.repo_root, workspace.layout.result_root)
policy_root = workspace.layout.repo_root
(policy_root / "tests" / "task_plane" / "public_interface").mkdir(parents=True, exist_ok=True)
with self.assertRaises(ValueError):
runner.run("git-show", oid="HEAD~1", paths=["active-runtime/app/task_plane/worker.py"])
link_target = policy_root / "tests" / "task_plane" / "public_interface" / "link"
link_target.symlink_to(Path("/etc/passwd"), target_is_directory=False)
with self.assertRaises(CommandPolicyError):
runner.run("git-show", oid="0" * 40, paths=["tests/task_plane/public_interface/link"])
def test_runner_rejects_network_tokens_and_output_escape(self):
with tempfile.TemporaryDirectory() as directory:
with TaskWorkspace.create(Path(directory) / "workspace", task_id="task-net", claim_id="claim-net") as workspace:
runner = SafeCommandRunner(workspace.layout.repo_root, workspace.layout.result_root)
with self.assertRaises(CommandDisallowedError):
runner.run("git-show", oid="0" * 40, paths=["https://bad.example/attack"])
with self.assertRaises(CommandPolicyError):
runner.run(
"acceptance",
oid="0" * 40,
paths=["active-runtime"],
output=workspace.layout.home.parent / "escape.json",
)
def test_runner_accepts_nested_allowlisted_paths_and_rejects_secret_env(self):
with tempfile.TemporaryDirectory() as directory:
with TaskWorkspace.create(Path(directory) / "workspace", task_id="task-safe", claim_id="claim-safe") as workspace:
runner = SafeCommandRunner(workspace.layout.repo_root, workspace.layout.result_root)
command = runner.build(
"pytest",
oid="0" * 40,
paths=["tests/task_plane/public_interface"],
)
self.assertEqual(command[-1], "tests/task_plane/public_interface")
with self.assertRaises(Exception):
runner.run("diff-check", oid="0" * 40, env={"API_TOKEN": "secret"})
@@ -1,417 +0,0 @@
from __future__ import annotations
import json
import os
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest import mock
import aios_isolated_writer_policy as writer_policy
import isolated_writer_path_policy as path_policy
from isolated_writer_authz import AuthorizationError, _validate_grant
TARGET = "deploy/systemd/aios-codex-review-test-worker.service"
BRANCH = "patch/codex-review-test-worker-20260724"
AUDIENCE = "aios-codex-review-worker-writer.service"
VALID_UNIT = """\
[Unit]
Description=AIOS Codex Review/Test Worker
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
WorkingDirectory=/usr/libexec/aios-codex-review-test-worker/current
Environment=AIOS_CODEX_WORKER_MODE=shadow
Environment=AIOS_TASK_PLANE_EXECUTION_ENABLED=false
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RuntimeDirectory=aios-codex-review-test-worker
StateDirectory=aios-codex-review-test-worker
CacheDirectory=aios-codex-review-test-worker
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadOnlyPaths=/usr/libexec/aios-codex-review-test-worker
ReadWritePaths=/run/aios-codex-review-test-worker
ReadWritePaths=/var/lib/aios-codex-review-test-worker
ReadWritePaths=/var/cache/aios-codex-review-test-worker
UMask=0077
[Install]
WantedBy=multi-user.target
"""
def document(*, paths=None, **updates):
value = {
"schema_version": 1,
"policy_id": "codex-review-worker-path-policy-v1",
"audience": AUDIENCE,
"repository_identity": "aios/aios",
"allowed_branch": BRANCH,
"allowed_operations": {
"commit_file.atomic": {
"exact_paths": paths or [TARGET],
"max_content_bytes": 32768,
"content_profile": "systemd-codex-review-worker-v1",
}
},
}
value.update(updates)
return value
class ExactPathPolicyTests(unittest.TestCase):
def setUp(self):
self.saved = dict(os.environ)
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.policy_dir = self.root / "policies"
self.policy_dir.mkdir(mode=0o750)
self.policy_file = self.policy_dir / "writer.json"
self.workspace = self.root / "workspace"
self.workspace.mkdir(mode=0o700)
os.environ.update(
{
"AIOS_GITOPS_ISOLATED_WRITER_ENFORCEMENT": "required",
"AIOS_GITOPS_SERVICE_AUDIENCE": AUDIENCE,
"AIOS_GITOPS_REPOSITORY_IDENTITY": "aios/aios",
"AIOS_GITOPS_ALLOWED_BRANCH": BRANCH,
}
)
def tearDown(self):
path_policy.clear_path_policy_cache()
os.environ.clear()
os.environ.update(self.saved)
self.temp.cleanup()
def write_policy(self, value=None):
payload = document() if value is None else value
raw = (
json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
+ "\n"
)
self.policy_file.write_text(raw, encoding="utf-8")
self.policy_file.chmod(0o640)
return path_policy.load_path_policy(
self.policy_file,
expected_owner_uid=os.getuid(),
expected_group_gid=os.getgid(),
)
def configured(self, loaded):
return mock.patch.object(
path_policy,
"configured_path_policy",
return_value=loaded,
)
def test_exact_systemd_path_and_exact_token_path_are_allowed(self):
loaded = self.write_policy()
with self.configured(loaded):
self.assertEqual(TARGET, writer_policy.validate_path(TARGET, self.workspace))
writer_policy.validate_content_for_path(TARGET, VALID_UNIT)
def test_token_path_cannot_expand_service_policy(self):
loaded = self.write_policy(document(paths=["deploy/systemd/other.service"]))
with self.configured(loaded), self.assertRaisesRegex(
PermissionError, "isolated_writer_service_path_rejected"
):
writer_policy.validate_path(TARGET, self.workspace)
def test_service_policy_cannot_expand_token_path(self):
now = datetime.now(timezone.utc)
grant = {
"schema_version": 2,
"audience": AUDIENCE,
"source_cidrs": ["127.0.0.1/32"],
"created_at": (now - timedelta(seconds=5)).isoformat(),
"not_before": (now - timedelta(seconds=2)).isoformat(),
"expires_at": (now + timedelta(minutes=5)).isoformat(),
"revoked": False,
"allowed_operations": ["commit_file.atomic"],
"allowed_branch": BRANCH,
"allowed_paths": ["deploy/systemd/other.service"],
"approved_base_oid": "a" * 40,
}
with mock.patch(
"isolated_writer_authz._git_is_ancestor", return_value=True
), self.assertRaisesRegex(AuthorizationError, "writer_token_path_rejected"):
_validate_grant(
grant,
audience=AUDIENCE,
source_ip="127.0.0.1",
operation="commit_file.atomic",
branch=BRANCH,
path=TARGET,
workspace=self.workspace,
current_time=now,
)
def test_other_service_file_is_rejected(self):
loaded = self.write_policy()
with self.configured(loaded), self.assertRaisesRegex(
PermissionError, "isolated_writer_service_path_rejected"
):
writer_policy.validate_path(
"deploy/systemd/other.service", self.workspace
)
def test_path_traversal_is_rejected(self):
for value in ("deploy/systemd/../x", "../deploy/systemd/x"):
with self.subTest(value=value), self.assertRaises(PermissionError):
writer_policy.validate_path(value, self.workspace)
def test_url_encoded_traversal_is_rejected(self):
with self.assertRaises(PermissionError):
writer_policy.validate_path(
"deploy/systemd/%2e%2e/x", self.workspace
)
def test_symlink_policy_file_is_rejected(self):
self.write_policy()
link = self.policy_dir / "link.json"
link.symlink_to(self.policy_file)
with self.assertRaisesRegex(
path_policy.PathPolicyError, "isolated_writer_path_policy_symlink_rejected"
):
path_policy.load_path_policy(
link,
expected_owner_uid=os.getuid(),
expected_group_gid=os.getgid(),
)
def test_world_writable_policy_file_is_rejected(self):
self.write_policy()
self.policy_file.chmod(0o646)
with self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_permissions_rejected",
):
path_policy.load_path_policy(
self.policy_file,
expected_owner_uid=os.getuid(),
expected_group_gid=os.getgid(),
)
def test_unknown_policy_field_is_rejected(self):
value = document()
value["unexpected"] = True
with self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_fields_rejected",
):
self.write_policy(value)
def test_audience_mismatch_is_rejected(self):
loaded = self.write_policy()
os.environ["AIOS_GITOPS_PATH_POLICY_FILE"] = str(self.policy_file)
os.environ["AIOS_GITOPS_SERVICE_AUDIENCE"] = "other.service"
path_policy.clear_path_policy_cache()
with mock.patch.object(
path_policy, "load_path_policy", return_value=loaded
), self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_audience_rejected",
):
path_policy.configured_path_policy()
def test_branch_mismatch_is_rejected(self):
loaded = self.write_policy()
os.environ["AIOS_GITOPS_PATH_POLICY_FILE"] = str(self.policy_file)
os.environ["AIOS_GITOPS_ALLOWED_BRANCH"] = "patch/other"
path_policy.clear_path_policy_cache()
with mock.patch.object(
path_policy, "load_path_policy", return_value=loaded
), self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_branch_rejected",
):
path_policy.configured_path_policy()
def assert_profile_rejected(self, content):
loaded = self.write_policy()
with self.configured(loaded), self.assertRaisesRegex(
PermissionError, "isolated_writer_systemd_profile_rejected"
):
writer_policy.validate_content_for_path(TARGET, content)
def test_disallowed_execution_and_credential_directives_are_rejected(self):
directives = (
"ExecStartPre=/usr/bin/id",
"ExecStartPost=/usr/bin/id",
"ExecReload=/usr/bin/id",
"ExecStop=/usr/bin/id",
"ExecStopPost=/usr/bin/id",
"ExecCondition=/usr/bin/true",
"EnvironmentFile=/run/worker.env",
"PassEnvironment=PATH",
"LoadCredential=worker:/run/worker",
"SetCredential=worker:value",
"ImportCredential=worker",
"StandardInput=socket",
"StandardOutput=socket",
)
for directive in directives:
with self.subTest(directive=directive):
self.assert_profile_rejected(
VALID_UNIT.replace(
"Restart=on-failure",
f"Restart=on-failure\n{directive}",
)
)
def test_disallowed_namespace_and_capability_directives_are_rejected(self):
directives = (
"RootDirectory=/srv/worker",
"RootImage=/srv/worker.raw",
"ReadWritePaths=/",
"BindPaths=/etc:/etc",
"BindReadOnlyPaths=/etc:/etc",
"TemporaryFileSystem=/etc",
"CapabilityBoundingSet=CAP_SYS_ADMIN",
"AmbientCapabilities=CAP_SYS_ADMIN",
"DeviceAllow=/dev/sda rw",
"SupplementaryGroups=docker",
"Sockets=unsafe.socket",
)
for directive in directives:
with self.subTest(directive=directive):
self.assert_profile_rejected(
VALID_UNIT.replace(
"Restart=on-failure",
f"Restart=on-failure\n{directive}",
)
)
def test_duplicate_single_directives_and_sections_are_rejected(self):
bad_values = (
VALID_UNIT.replace("User=aiman", "User=aiman\nUser=aiman"),
VALID_UNIT.replace(
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test",
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test\n"
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test",
),
VALID_UNIT + "\n[Service]\nType=simple\n",
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_empty_reset_continuation_crlf_nul_and_long_lines_are_rejected(self):
bad_values = (
VALID_UNIT.replace(
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test",
"ExecStart=",
),
VALID_UNIT.replace(
"Restart=on-failure",
"Restart=on-failure\n continuation-injection",
),
VALID_UNIT.replace("\n", "\r\n"),
VALID_UNIT.replace("Restart=on-failure", "Restart=on-failure\x00"),
VALID_UNIT.replace(
"Description=AIOS Codex Review/Test Worker",
"Description=" + ("A" * 4097),
),
)
for value in bad_values:
with self.subTest(kind=repr(value[:80])):
self.assert_profile_rejected(value)
def test_unknown_environment_and_production_execution_are_rejected(self):
bad_values = (
VALID_UNIT.replace(
"Environment=PYTHONDONTWRITEBYTECODE=1",
"Environment=UNKNOWN_KEY=value",
),
VALID_UNIT.replace(
"AIOS_TASK_PLANE_EXECUTION_ENABLED=false",
"AIOS_TASK_PLANE_EXECUTION_ENABLED=true",
),
VALID_UNIT.replace(
"Environment=PYTHONDONTWRITEBYTECODE=1",
"Environment=Authorization: Bearer placeholder",
),
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_identity_working_directory_and_path_prefix_bypasses_are_rejected(self):
bad_values = (
VALID_UNIT.replace("User=aiman", "User=root"),
VALID_UNIT.replace(
"/usr/libexec/aios-codex-review-test-worker/current",
"/data/aios-gitops/worktrees/worker",
),
VALID_UNIT.replace(
"ReadWritePaths=/run/aios-codex-review-test-worker",
"ReadWritePaths=/etc",
),
VALID_UNIT.replace(
"ReadOnlyPaths=/data/aios-gitops/repo.git",
"ReadOnlyPaths=/data",
),
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_unknown_section_directive_and_unicode_lookalike_are_rejected(self):
bad_values = (
VALID_UNIT.replace("[Install]", "[Socket]"),
VALID_UNIT.replace(
"Restart=on-failure",
"Restart=on-failure\nProtectClock=true",
),
VALID_UNIT.replace("ExecStart=", "\u0415xecStart="),
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_invalid_unicode_surrogate_and_missing_final_newline_are_rejected(self):
for value in (VALID_UNIT + "\ud800", VALID_UNIT.rstrip("\n")):
with self.subTest(value=repr(value[-30:])):
self.assert_profile_rejected(value)
def test_writer_request_cannot_modify_policy_file(self):
with self.assertRaises(PermissionError):
writer_policy.validate_path(
"etc/aios-gitops/writer-path-policies/codex-review-worker.json",
self.workspace,
)
def test_coarse_file_and_commit_endpoints_remain_forbidden(self):
for endpoint in ("/api/file", "/api/commit"):
with self.subTest(endpoint=endpoint), self.assertRaisesRegex(
PermissionError, "isolated_writer_operation_forbidden"
):
writer_policy.validate_endpoint(
"POST", endpoint, self.workspace, self.root
)
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -15,10 +15,6 @@ class IsolatedWriterPolicyTests(unittest.TestCase):
os.environ["AIOS_GITOPS_ISOLATED_WRITER_ENFORCEMENT"] = "required"
os.environ["AIOS_GITOPS_ALLOWED_BRANCH"] = "patch/aios-agent-task-control-plane-20260724"
os.environ["AIOS_GITOPS_ALLOWED_BASE_OID"] = "a8e97868976b0dce17b7cb8487eadc1d9c687622"
os.environ["AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED"] = "2"
os.environ["AIOS_GITOPS_SERVICE_AUDIENCE"] = "test-isolated-writer.service"
os.environ["AIOS_GITOPS_TOKEN_REGISTRY_V2"] = "/tmp/test-writer-token-registry.sqlite3"
os.environ["AIOS_GITOPS_WRITER_OPERATION_LEDGER"] = "/tmp/test-writer-operations.sqlite3"
def tearDown(self):
os.environ.clear()
@@ -1,471 +0,0 @@
import hashlib
import json
import os
import sqlite3
import subprocess
import sys
import tempfile
import threading
import time
import unittest
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "active-runtime" / "app"))
import aios_isolated_writer_policy as policy
from isolated_writer_authz import AuthorizationError, authorize, operation_for
from isolated_writer_commit_file import (
AtomicCommitFile,
CommitFileError,
OperationLedger,
SimulatedCrash,
)
from isolated_writer_token_registry import TokenRegistry, token_digest
BRANCH = "patch/codex-review-test-worker-20260724"
AUDIENCE = "aios-codex-review-worker-writer.service"
TASK_ID = "AIOS-CODEX-WRITER-SMOKE-20260724"
TARGET_PATH = "docs/reliability/codex-review-worker-writer-smoke.json"
def run(cwd, *args):
return subprocess.check_output(args, cwd=cwd, text=True).strip()
@dataclass
class DummyGrant:
token_id: str = "test-token-id"
task_id: str = TASK_ID
operation: str = "commit_file.atomic"
record: dict = None
def __post_init__(self):
if self.record is None:
self.record = {"audience": AUDIENCE, "_source_ip": "127.0.0.1"}
class RepositoryCase(unittest.TestCase):
def setUp(self):
self.saved = dict(os.environ)
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.authoritative = self.root / "authoritative.git"
self.gitea = self.root / "gitea.git"
self.seed = self.root / "seed"
self.workspace = self.root / "writer"
subprocess.run(["git", "init", "--bare", str(self.authoritative)], check=True, capture_output=True)
subprocess.run(["git", "init", str(self.seed)], check=True, capture_output=True)
subprocess.run(["git", "config", "user.email", "writer@example.invalid"], cwd=self.seed, check=True)
subprocess.run(["git", "config", "user.name", "Writer Test"], cwd=self.seed, check=True)
(self.seed / "README.md").write_text("base\n", encoding="utf-8")
subprocess.run(["git", "add", "README.md"], cwd=self.seed, check=True)
subprocess.run(["git", "commit", "-m", "base"], cwd=self.seed, check=True, capture_output=True)
subprocess.run(["git", "branch", "-M", "main"], cwd=self.seed, check=True)
subprocess.run(["git", "remote", "add", "origin", str(self.authoritative)], cwd=self.seed, check=True)
subprocess.run(["git", "push", "origin", "main"], cwd=self.seed, check=True, capture_output=True)
self.base = run(self.seed, "git", "rev-parse", "HEAD")
subprocess.run(["git", "checkout", "-b", BRANCH], cwd=self.seed, check=True, capture_output=True)
subprocess.run(["git", "push", "origin", BRANCH], cwd=self.seed, check=True, capture_output=True)
subprocess.run(
["git", "--git-dir", str(self.authoritative), "worktree", "add", str(self.workspace), BRANCH],
check=True,
capture_output=True,
)
subprocess.run(["git", "config", "user.email", "writer@example.invalid"], cwd=self.workspace, check=True)
subprocess.run(["git", "config", "user.name", "Writer Test"], cwd=self.workspace, check=True)
subprocess.run(["git", "clone", "--bare", str(self.authoritative), str(self.gitea)], check=True, capture_output=True)
subprocess.run(["git", "remote", "add", "gitea-http", str(self.gitea)], cwd=self.workspace, check=True)
self.registry = TokenRegistry(self.root / "registry.sqlite3")
os.environ.update({
"AIOS_GITOPS_ALLOWED_BRANCH": BRANCH,
"AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED": "2",
"AIOS_GITOPS_SERVICE_AUDIENCE": AUDIENCE,
"AIOS_GITOPS_ISOLATED_WRITER_ENFORCEMENT": "required",
})
def tearDown(self):
os.environ.clear()
os.environ.update(self.saved)
self.temp.cleanup()
def grant(self, **updates):
now = datetime.now(timezone.utc)
grant = {
"token_id": "smoke-" + hashlib.sha256(os.urandom(16)).hexdigest()[:12],
"audience": AUDIENCE,
"task_id": TASK_ID,
"source_cidrs": ["127.0.0.1/32", "::1/128"],
"allowed_branch": BRANCH,
"approved_base_oid": self.base,
"allowed_operations": [
"runtime.status.read",
"baseline.read",
"patches.read",
"commit_file.atomic",
],
"allowed_paths": [TARGET_PATH],
"not_before": (now - timedelta(seconds=2)).isoformat(),
"expires_at": (now + timedelta(minutes=20)).isoformat(),
"max_uses": {
"runtime.status.read": 2,
"baseline.read": 2,
"patches.read": 2,
"commit_file.atomic": 4,
},
"issued_by": "owner-authorized-local-maintenance-executor",
"created_at": (now - timedelta(seconds=3)).isoformat(),
}
grant.update(updates)
return grant
def issue(self, **updates):
return self.registry.issue(self.grant(**updates))
def auth(self, token, **updates):
args = {
"method": "GET",
"path": "/api/runtime-status",
"source_ip": "127.0.0.1",
"workspace": self.workspace,
}
args.update(updates)
return authorize(self.registry, token, **args)
def request(self, key="writer-smoke-0001", content='{"status":"READY"}\n'):
return {
"branch": BRANCH,
"path": TARGET_PATH,
"content": content,
"message": "chore(task-plane): verify Codex writer endpoint",
"expected_head_oid": run(self.workspace, "git", "rev-parse", "HEAD"),
"idempotency_key": key,
}
def writer(self, crash=None, remote="gitea-http", audit=None):
return AtomicCommitFile(
workspace=self.workspace,
bare_repo=self.authoritative,
ledger=OperationLedger(self.root / "operations.sqlite3"),
remote=remote,
audit=audit or (lambda record: None),
crash_after_state=crash,
)
class TokenPolicyV2Tests(RepositoryCase):
def test_v1_coarse_scope_token_is_rejected(self):
token, record = self.issue()
record["schema_version"] = 1
with self.registry._connect() as db:
db.execute(
"UPDATE token_grants SET grant_json=? WHERE token_id=?",
(json.dumps(record), record["token_id"]),
)
with self.assertRaisesRegex(AuthorizationError, "writer_token_policy_v2_required"):
self.auth(token)
def test_audience_mismatch_is_rejected(self):
token, _ = self.issue(audience="other.service")
with self.assertRaisesRegex(AuthorizationError, "writer_token_audience_rejected"):
self.auth(token)
def test_source_ip_mismatch_is_rejected(self):
token, _ = self.issue(source_cidrs=["192.0.2.0/24"])
with self.assertRaisesRegex(AuthorizationError, "writer_token_source_rejected"):
self.auth(token)
def test_branch_mismatch_is_rejected(self):
token, _ = self.issue(allowed_branch="patch/other")
with self.assertRaisesRegex(AuthorizationError, "writer_token_branch_rejected"):
self.auth(token)
def test_operation_mismatch_is_rejected(self):
token, _ = self.issue(allowed_operations=["baseline.read"])
with self.assertRaisesRegex(AuthorizationError, "writer_token_operation_rejected"):
self.auth(token)
def test_path_mismatch_is_rejected(self):
token, _ = self.issue()
with self.assertRaisesRegex(AuthorizationError, "writer_token_path_rejected"):
self.auth(
token,
method="POST",
path="/api/commit-file",
branch=BRANCH,
target_path="docs/other.json",
)
def test_expired_token_is_rejected(self):
now = datetime.now(timezone.utc)
token, _ = self.issue(
created_at=(now - timedelta(minutes=10)).isoformat(),
not_before=(now - timedelta(minutes=9)).isoformat(),
expires_at=(now - timedelta(seconds=1)).isoformat(),
)
with self.assertRaisesRegex(AuthorizationError, "writer_token_expired"):
self.auth(token)
def test_not_before_token_is_rejected(self):
now = datetime.now(timezone.utc)
token, _ = self.issue(
created_at=now.isoformat(),
not_before=(now + timedelta(minutes=1)).isoformat(),
expires_at=(now + timedelta(minutes=5)).isoformat(),
)
with self.assertRaisesRegex(AuthorizationError, "writer_token_not_yet_valid"):
self.auth(token)
def test_revoked_token_is_rejected(self):
token, grant = self.issue()
self.assertTrue(self.registry.revoke(grant["token_id"]))
with self.assertRaisesRegex(AuthorizationError, "writer_token_revoked"):
self.auth(token)
def test_operation_use_count_is_atomic_and_exhausted(self):
token, _ = self.issue(max_uses={
"runtime.status.read": 1,
"baseline.read": 1,
"patches.read": 1,
"commit_file.atomic": 1,
})
self.auth(token)
with self.assertRaisesRegex(AuthorizationError, "writer_token_operation_exhausted"):
self.auth(token)
def test_concurrent_use_count_never_exceeds_limit(self):
token, _ = self.issue(max_uses={
"runtime.status.read": 2,
"baseline.read": 1,
"patches.read": 1,
"commit_file.atomic": 1,
})
successes, failures = [], []
def invoke():
try:
successes.append(self.auth(token))
except AuthorizationError as exc:
failures.append(exc.code)
threads = [threading.Thread(target=invoke) for _ in range(6)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(2, len(successes))
self.assertEqual(4, failures.count("writer_token_operation_exhausted"))
def test_registry_does_not_store_plaintext_token(self):
token, _ = self.issue()
registry_bytes = b"".join(
item.read_bytes()
for item in self.registry.path.parent.glob(self.registry.path.name + "*")
if item.is_file()
)
self.assertNotIn(token.encode(), registry_bytes)
with self.registry._connect() as db:
stored = db.execute("SELECT token_digest FROM token_grants").fetchone()[0]
self.assertEqual(token_digest(token), stored)
def test_coarse_routes_have_no_operation_mapping(self):
for path in ("/api/file", "/api/commit", "/api/tag", "/api/rollback", "/api/model"):
with self.subTest(path=path), self.assertRaises(AuthorizationError) as captured:
operation_for("POST", path)
self.assertEqual(403, captured.exception.status)
def test_route_mapping_is_fixed_not_request_controlled(self):
self.assertEqual("runtime.status.read", operation_for("GET", "/api/runtime-status"))
self.assertEqual("baseline.read", operation_for("GET", "/api/baseline"))
self.assertEqual("patches.read", operation_for("GET", "/api/patches"))
self.assertEqual("commit_file.atomic", operation_for("POST", "/api/commit-file"))
def test_x_forwarded_for_cannot_replace_socket_peer(self):
token, _ = self.issue(source_cidrs=["203.0.113.9/32"])
with self.assertRaisesRegex(AuthorizationError, "writer_token_source_rejected"):
self.auth(token, source_ip="127.0.0.1")
class AtomicCommitFileTests(RepositoryCase):
def test_commit_file_succeeds_and_verifies_bare_and_gitea(self):
result = self.writer().execute(DummyGrant(), self.request())
self.assertEqual("COMPLETED", result["state"])
self.assertEqual(
result["commit_oid"],
run(self.root, "git", "--git-dir", str(self.gitea), "rev-parse", f"refs/heads/{BRANCH}"),
)
def test_same_idempotency_key_returns_same_commit(self):
request = self.request()
first = self.writer().execute(DummyGrant(), request)
count = run(self.workspace, "git", "rev-list", "--count", "HEAD")
second = self.writer().execute(DummyGrant(), request)
self.assertEqual(first["commit_oid"], second["commit_oid"])
self.assertEqual(count, run(self.workspace, "git", "rev-list", "--count", "HEAD"))
self.assertTrue(second["idempotent"])
def test_same_key_with_different_payload_returns_409(self):
request = self.request()
self.writer().execute(DummyGrant(), request)
request["content"] = '{"status":"DIFFERENT"}\n'
with self.assertRaises(CommitFileError) as captured:
self.writer().execute(DummyGrant(), request)
self.assertEqual(409, captured.exception.status)
def test_same_key_from_different_task_returns_409(self):
request = self.request()
self.writer().execute(DummyGrant(), request)
other = DummyGrant(token_id="other-token", task_id="OTHER-TASK")
with self.assertRaisesRegex(CommitFileError, "writer_idempotency_owner_conflict"):
self.writer().execute(other, request)
def test_file_written_crash_is_recoverable(self):
request = self.request()
with self.assertRaises(SimulatedCrash):
self.writer(crash="FILE_WRITTEN").execute(DummyGrant(), request)
result = self.writer().execute(DummyGrant(), request)
self.assertEqual("COMPLETED", result["state"])
def test_committed_crash_is_recoverable_without_second_commit(self):
request = self.request()
with self.assertRaises(SimulatedCrash):
self.writer(crash="COMMITTED").execute(DummyGrant(), request)
committed = run(self.workspace, "git", "rev-parse", "HEAD")
result = self.writer().execute(DummyGrant(), request)
self.assertEqual(committed, result["commit_oid"])
def test_pushed_crash_only_reverifies(self):
request = self.request()
with self.assertRaises(SimulatedCrash):
self.writer(crash="PUSHED").execute(DummyGrant(), request)
result = self.writer().execute(DummyGrant(), request)
self.assertEqual("COMPLETED", result["state"])
def test_push_failure_is_failed_recoverable(self):
request = self.request()
with self.assertRaisesRegex(CommitFileError, "writer_gitea_push_failed"):
self.writer(remote="missing-remote").execute(DummyGrant(), request)
row = OperationLedger(self.root / "operations.sqlite3").get(request["idempotency_key"])
self.assertEqual("FAILED_RECOVERABLE", row["state"])
def test_branch_head_conflict_requires_manual_review(self):
request = self.request()
ledger = OperationLedger(self.root / "operations.sqlite3")
ledger.get_or_create(DummyGrant(), request, __import__("isolated_writer_commit_file").canonical_digest(request))
(self.workspace / "README.md").write_text("drift\n", encoding="utf-8")
subprocess.run(["git", "commit", "-am", "drift"], cwd=self.workspace, check=True, capture_output=True)
with self.assertRaisesRegex(CommitFileError, "writer_branch_head_conflict"):
self.writer().execute(DummyGrant(), request)
self.assertEqual("MANUAL_REVIEW_REQUIRED", ledger.get(request["idempotency_key"])["state"])
def test_extra_staged_file_is_rejected(self):
(self.workspace / "README.md").write_text("staged\n", encoding="utf-8")
subprocess.run(["git", "add", "README.md"], cwd=self.workspace, check=True)
with self.assertRaisesRegex(CommitFileError, "writer_extra_staged_files_rejected"):
self.writer().execute(DummyGrant(), self.request())
def test_path_traversal_and_protected_roots_are_rejected(self):
for path in (
"../docs/x", "%2e%2e/docs/x", "/etc/x", r"C:\temp\x",
r"\\server\share\x", "baseline/current.json", "runtime/x",
):
request = self.request(key="writer-path-" + hashlib.sha256(path.encode()).hexdigest()[:12])
request["path"] = path
with self.subTest(path=path), self.assertRaises((PermissionError, CommitFileError)):
self.writer().execute(DummyGrant(), request)
def test_audit_contains_ids_but_not_content_or_authorization(self):
records = []
request = self.request(content='{"value":"non-secret-body-marker"}\n')
self.writer(audit=records.append).execute(DummyGrant(), request)
encoded = json.dumps(records)
self.assertIn("test-token-id", encoded)
self.assertNotIn("non-secret-body-marker", encoded)
self.assertNotIn("Authorization", encoded)
def test_concurrent_same_idempotency_creates_one_commit(self):
request = self.request()
results, errors = [], []
def invoke():
try:
results.append(self.writer().execute(DummyGrant(), request))
except Exception as exc:
errors.append(exc)
threads = [threading.Thread(target=invoke) for _ in range(4)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertFalse(errors)
self.assertEqual(1, len({item["commit_oid"] for item in results}))
def test_different_operations_are_serialized_by_branch_lock(self):
active = 0
maximum = 0
guard = threading.Lock()
class Probe(AtomicCommitFile):
def _execute_locked(inner, grant, data, digest):
nonlocal active, maximum
with guard:
active += 1
maximum = max(maximum, active)
time.sleep(0.05)
with guard:
active -= 1
return {"state": "PROBED"}
probe = Probe(
workspace=self.workspace,
bare_repo=self.authoritative,
ledger=OperationLedger(self.root / "probe.sqlite3"),
remote="gitea-http",
audit=lambda record: None,
)
requests = [self.request(key=f"writer-lock-{index:04d}") for index in range(3)]
threads = [threading.Thread(target=probe.execute, args=(DummyGrant(), item)) for item in requests]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(1, maximum)
def test_invalid_expected_head_and_idempotency_are_rejected(self):
request = self.request()
request["expected_head_oid"] = "short"
with self.assertRaisesRegex(CommitFileError, "writer_expected_head_rejected"):
self.writer().execute(DummyGrant(), request)
request = self.request()
request["idempotency_key"] = "../bad"
with self.assertRaisesRegex(CommitFileError, "writer_idempotency_key_rejected"):
self.writer().execute(DummyGrant(), request)
def test_invalid_message_and_nul_content_are_rejected(self):
request = self.request()
request["message"] = "bad\nmessage"
with self.assertRaisesRegex(CommitFileError, "writer_message_rejected"):
self.writer().execute(DummyGrant(), request)
request = self.request()
request["content"] = "bad\x00content"
with self.assertRaisesRegex(CommitFileError, "writer_content_rejected"):
self.writer().execute(DummyGrant(), request)
def test_force_flags_are_403_and_unknown_fields_are_rejected(self):
for field in ("force", "force_push"):
request = self.request(key=f"writer-force-{field}")
request[field] = True
with self.subTest(field=field), self.assertRaises(CommitFileError) as captured:
self.writer().execute(DummyGrant(), request)
self.assertEqual(403, captured.exception.status)
request = self.request(key="writer-extra-field")
request["operation"] = "commit_file.atomic"
with self.assertRaisesRegex(CommitFileError, "writer_request_fields_rejected"):
self.writer().execute(DummyGrant(), request)
if __name__ == "__main__":
unittest.main()