Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f98e15d37 | ||
|
|
9303a5d684 | ||
|
|
8814dff202 | ||
|
|
ff6743e851 | ||
|
|
ebd7439874 | ||
|
|
c41e278c59 | ||
|
|
1071979079 | ||
|
|
9a158fcab9 | ||
|
|
8b7f9d9028 | ||
|
|
3aa1872084 | ||
|
|
f6bef90633 | ||
|
|
c2eed5b379 | ||
|
|
6e8212608e | ||
|
|
e6a53888fe | ||
|
|
2bef44f6bc | ||
|
|
a96f36bf72 | ||
|
|
4e6ef70029 | ||
|
|
80feef309f | ||
|
|
a5489f1b67 | ||
|
|
312d3b4830 | ||
|
|
ac2dc5c30e | ||
|
|
5ef261b265 | ||
|
|
3a22c3c7d8 | ||
|
|
1c9f28a613 | ||
|
|
9514062cf3 | ||
|
|
a701a32cb4 | ||
|
|
45f3226662 | ||
|
|
975d239d40 | ||
|
|
3bc44accb5 | ||
|
|
3304d87872 | ||
|
|
875d41c310 | ||
|
|
870ac61dc2 | ||
|
|
009f276699 | ||
|
|
600c717e17 | ||
|
|
02be6d28ed | ||
|
|
b1e240dda3 | ||
|
|
2dfbe837f4 | ||
|
|
80df1e0d87 | ||
|
|
7ecf7cbfce | ||
|
|
bbada7c863 |
@@ -5,13 +5,148 @@ 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:
|
||||
@@ -173,6 +308,8 @@ 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,10 +9,9 @@ import re
|
||||
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
from urllib.parse import unquote
|
||||
|
||||
WRITE_ENDPOINTS = frozenset({
|
||||
"/api/branch", "/api/create-branch", "/api/file", "/api/commit-file",
|
||||
"/api/commit", "/api/patch", "/api/create-patch",
|
||||
})
|
||||
import isolated_writer_path_policy as path_policy
|
||||
|
||||
WRITE_ENDPOINTS = frozenset({"/api/commit-file"})
|
||||
ALLOWED_ROOTS = (
|
||||
"active-runtime",
|
||||
"docs",
|
||||
@@ -69,6 +68,15 @@ 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")
|
||||
@@ -182,6 +190,7 @@ def _audit_sanitize(value):
|
||||
"private_key",
|
||||
)
|
||||
)
|
||||
and str(key).lower() != "token_id"
|
||||
else _audit_sanitize(inner)
|
||||
)
|
||||
for key, inner in value.items()
|
||||
@@ -239,9 +248,14 @@ 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:
|
||||
raise PermissionError("isolated_writer_protected_path_rejected")
|
||||
if value != "README.md" and first not in ALLOWED_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_path_outside_allowed_roots")
|
||||
if workspace is not None:
|
||||
lexical_root = Path(os.path.abspath(str(workspace)))
|
||||
@@ -261,6 +275,22 @@ 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:
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
"""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)
|
||||
@@ -0,0 +1,403 @@
|
||||
"""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,
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
"""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")
|
||||
@@ -0,0 +1,241 @@
|
||||
"""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())
|
||||
@@ -1 +0,0 @@
|
||||
"""Isolated, Shadow-attested Codex Task Plane Worker components."""
|
||||
@@ -1,212 +0,0 @@
|
||||
"""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())
|
||||
@@ -1,62 +0,0 @@
|
||||
"""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
|
||||
@@ -1,56 +0,0 @@
|
||||
"""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()
|
||||
@@ -1,157 +0,0 @@
|
||||
"""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()
|
||||
@@ -1,138 +0,0 @@
|
||||
"""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
|
||||
@@ -1,300 +0,0 @@
|
||||
"""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",
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
"""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
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"approval_request_branch": "governance/request/integration-refresh-control-plane-deployment-20260728T093428Z",
|
||||
"approval_type": "CONTROL_PLANE_DEPLOYMENT_APPROVAL_REQUEST",
|
||||
"authorization_lineage": {
|
||||
"execution_renewal_merge_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
|
||||
"grant_id": "grant-11354281aafdd074df5899a3925dd2a0370cffa9edb06f5c7d7227ce8de00d91",
|
||||
"source_mapping_approval_merge_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6"
|
||||
},
|
||||
"deployment_status": "NOT_EXECUTED",
|
||||
"evidence": {
|
||||
"control_plane_artifact": {
|
||||
"path": "/var/lib/aios-integration-refresh-source-writer-v2/artifacts/control-plane-patch-112d995545e3e79126a062b2643a9f0c17ae7bd4.tar",
|
||||
"sha256": "bf101ade1aaf956f40c0bba74ed4b347f5f38d0de6a0c1b7694ae107c4b43c14",
|
||||
"size_bytes": 13363200
|
||||
},
|
||||
"rendered_systemd_config_sha256": "182a3cba48e752798cf24323a8c0249c53d7386371388a8db0435a29c43dc969",
|
||||
"rollback_artifact": {
|
||||
"path": "/var/lib/aios-gitops/runtime/tasks/AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726/implementation/rollback-to-85426f4f570e4d40b212318d47e52e4aed5cb06a.tar",
|
||||
"sha256": "f12a2a95e475da5d02eaff99e93615cb0186019ca30edae013ab054a2c6418ce"
|
||||
},
|
||||
"source_map": {
|
||||
"path": "docs/autonomous-release/INTEGRATION_REFRESH_SOURCE_MAP_20260728.json",
|
||||
"sha256": "01ddd56cfa2a525559e2d174cf7df117a9465ffce44ef25bc7a15b8e74d06bf5"
|
||||
},
|
||||
"test_evidence": {
|
||||
"collection_errors": 0,
|
||||
"path": "/var/lib/aios-gitops/runtime/tasks/AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726/implementation/test-evidence.json",
|
||||
"sha256": "d8933553f56462a3f1b3040dec989ff0adf717a17728d55ba0fe9e127d93c206",
|
||||
"tests_failed": 0,
|
||||
"tests_passed": 41
|
||||
}
|
||||
},
|
||||
"execution_conditions": {
|
||||
"candidate_only": true,
|
||||
"integration_unchanged": true,
|
||||
"main_unchanged": true,
|
||||
"no_credentials_or_secrets_in_request": true,
|
||||
"production_baseline_unchanged": true,
|
||||
"registry_unchanged": true
|
||||
},
|
||||
"expected_governance_base_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
|
||||
"expires_at": "2026-07-28T21:34:28Z",
|
||||
"implementation": {
|
||||
"commit_oid": "112d995545e3e79126a062b2643a9f0c17ae7bd4",
|
||||
"tree_oid": "4ed88399fb8e7e4c235413ebbd783c9b6ad3c624"
|
||||
},
|
||||
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"issued_at": "2026-07-28T09:34:28Z",
|
||||
"prohibited_operations": [
|
||||
"production-baseline.write",
|
||||
"production-tag.write",
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"registry.write",
|
||||
"force-push",
|
||||
"direct-main-push",
|
||||
"direct-tag-write"
|
||||
],
|
||||
"repository": "aios/aios",
|
||||
"request_status": "PENDING_HUMAN_APPROVAL",
|
||||
"requested_operations": [
|
||||
"control-plane.candidate.deploy",
|
||||
"control-plane.candidate.verify",
|
||||
"control-plane.candidate.rollback"
|
||||
],
|
||||
"required_controls": [
|
||||
"Two distinct human approvals, one Codex and one GPT, both APPROVED against the same current approval-request PR head.",
|
||||
"Re-verify artifact, test evidence, source map, rendered systemd configuration, and rollback artifact paths and SHA-256 digests immediately before execution.",
|
||||
"AIOS_GITOPS_WRITES_ENABLED=false throughout candidate deployment, verification, and rollback.",
|
||||
"No force push, direct main write, or direct tag write."
|
||||
],
|
||||
"required_reviewers": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"schema": "aios.control-plane-deployment-approval-request/v1",
|
||||
"target_governance": {
|
||||
"approved_base_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
|
||||
"binding_rule": "Human approvals must bind to the same approval-request PR head; its first parent must equal the active governance base."
|
||||
},
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"validity_hours": 12
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"allowed_bootstrap_operations":["CREATE_OR_REUSE_DEDICATED_GOVERNANCE_SERVICE_ACCOUNT","GRANT_REPOSITORY_ADMIN_ROLE_TO_DEDICATED_GOVERNANCE_EXECUTOR","ISSUE_OR_ROTATE_GOVERNANCE_EXECUTOR_CREDENTIAL","STORE_GOVERNANCE_CREDENTIAL_IN_GOVERNED_SECRET_BACKEND","PROJECT_GOVERNANCE_CREDENTIAL_AS_SYSTEMD_CREDENTIAL","INSTALL_GOVERNANCE_EXECUTOR_SERVICE","VERIFY_GOVERNANCE_EXECUTOR_API_CAPABILITY"],"bootstrap_credential_policy":{"existing_privileged_identity":"gito","git_credentials":false,"one_shot_service":true,"persistent_privileged_credential":false,"plaintext_exposed":false,"source_preference":["OpenBao secret reference","systemd encrypted credential","existing credential broker source"],"use_only_after_exact_owner_approval":true},"bootstrap_must_not_execute_stage1":true,"bootstrap_state_machine":["PREPARED","OWNER_APPROVAL_VERIFIED","BOOTSTRAP_CREDENTIAL_PROJECTED","SERVICE_ACCOUNT_CREATED_OR_VERIFIED","REPOSITORY_ROLE_APPLIED","EXECUTOR_CREDENTIAL_CREATED","SECRET_PERSISTED_ENCRYPTED","EXECUTOR_INSTALLED","EXECUTOR_VERIFIED","PRIVILEGED_BOOTSTRAP_CREDENTIAL_DESTROYED","COMPLETED"],"credential_lifecycle":{"change_identity":false,"expand_repository_scope":false,"grant_global_admin":false,"recover_lost_runtime_projection":true,"reissue_after_expiry":true,"replace_compromised_credential":true,"rotate_same_identity_same_scope":true},"current_production":{"must_remain_unchanged":true,"version":"V2R1C7"},"denied_operations":["grant_global_admin","Owner_impersonation","reuse_Owner_PAT_as_governance_credential","direct_main_push","force_push","Git_push_from_governance_executor","PR_merge_from_governance_executor","tag_create","tag_update","tag_delete","arbitrary_user_management","arbitrary_team_management","arbitrary_repository_management","branch_protection_mutation_during_bootstrap","production_change","Registry_change","Baseline_change"],"depends_on_governance_request":{"dismissed":false,"head":"015185d7e5191d18c5c1596160807ac228782a0a","manifest_sha256":"1b11ee42d53a9f6c1646e5f5ddd8ce5b7ecf301483b9f3d47a068290d541ba4b","pr":54,"review_id":104,"review_state":"APPROVED","reviewer":"owner","reviewer_id":2},"executor_api_surface":["GET_BRANCH_PROTECTION","APPLY_APPROVED_PROTECTION_DELTA","READBACK_BRANCH_PROTECTION"],"generic_request_forwarding":false,"governance_executor":{"global_admin":false,"login":"aios-governance-executor","owner":false,"repository":"aios/aios","repository_role":"administrator","role_scope":"repository-specific branch-protection management only"},"owner_interaction":{"campaign_rebootstrap":false,"pr53_review":false,"pr54_reapprove":false,"required":"exactly_once","target":"this_dedicated_governance_executor_bootstrap_request"},"protection_mutation_binding_required":["governance_request_PR","exact_request_head","Owner_review_id","Owner_review_exact_head","manifest_sha256","expected_previous_snapshot_sha256","approved_delta"],"purpose":"provision_dedicated_non_owner_governance_executor","repository":"aios/aios","request_id":"AIOS-DEDICATED-GOVERNANCE-EXECUTOR-BOOTSTRAP-V1","schema":"aios.governance-executor-bootstrap.v1","stage1_after_bootstrap":{"approved_delta":{"add_user_id":153,"merge_whitelist":[2]},"before_protection_sha256":"8b3370add21cd23c2b4f41d8d84d3eaaa09ddf0451549b72c7941bbd92d5d60a","governance_request_pr":54,"owner_review_id":104,"unchanged_fields":["required_approvals","can_push","can_force_push","required_contexts","review_protections"]}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"authority_main_oid_at_publish":"69b45e81dcd2c3707c367003b08c55fd361faae8","base":{"branch":"governance/approvals","oid":"9303a5d684e972bd15fe19f61f721075f4dda8b6","tree_oid":"cf6ad76c810413b3601891dba6bcb755e630baef"},"depends_on_pr54":{"dismissed":false,"head":"015185d7e5191d18c5c1596160807ac228782a0a","review_id":104,"reviewer_id":2,"state":"APPROVED"},"direct_main_push":false,"force_push":false,"machine_identity":{"credential_ref":{"name":"gitea-machine-token","scheme":"systemd-credential"},"login":"aios-autonomy-agent","owner":false,"user_id":153},"manifest_path":"approvals/governance-policy/AIOS-DEDICATED-GOVERNANCE-EXECUTOR-BOOTSTRAP-V1.json","manifest_sha256":"770b84a243e92a46ae45198c1619ed6bb1a9284bedde1484d496957628d6c482","owner_pat_used":false,"pr53_reviewed":false,"production_version":"V2R1C7","publisher_denials":["branch_protection_change","credential_creation","collaborator_change","main_write","merge","tag","production"],"publisher_scope":["create_governance_request_branch","create_governance_approval_PR"],"request_id":"AIOS-DEDICATED-GOVERNANCE-EXECUTOR-BOOTSTRAP-V1","schema":"aios.governance-request-publisher-preapproval-evidence.v1","secrets_committed":false}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"schema": "aios.governance-approval/v1",
|
||||
"approval_type": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP_POLICY_ACTIVATION",
|
||||
"approval_plan_id": "AIOS-TSG-BS-PHASE-A-RENEWAL-20260731T081147Z",
|
||||
"task_id": "AIOS-V2R1C4-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-RENEWAL",
|
||||
"repository": "aios/aios",
|
||||
"issued_at": "2026-07-31T08:11:47Z",
|
||||
"expires_at": "2026-07-31T20:11:47Z",
|
||||
"expected_governance_base_oid": "ebd74398743e36523b593fcc395b73945d4924c5",
|
||||
"expected_governance_base_tree_oid": "ad1255a1127204fad1d1208d8f31dc94fa64e6e1",
|
||||
"approval_request_branch": "governance/request/two-stage-owner-campaign-v2-phase-a-renewal-20260731T081147Z",
|
||||
"request_head_binding": "GITEA_EXACT_HEAD_REVIEW_POST_COMMIT",
|
||||
"human_authorizer": "owner",
|
||||
"authorization_mode": "GITEA_EXACT_HEAD_REVIEW",
|
||||
"required_human_approvals": 1,
|
||||
"allowed_human_approvers": [
|
||||
"owner"
|
||||
],
|
||||
"require_latest_head_approval": true,
|
||||
"dismiss_stale_approvals": true,
|
||||
"auto_merge_after_machine_gates": true,
|
||||
"phase_a_scope": {
|
||||
"schema_id": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP",
|
||||
"schema_version": 1,
|
||||
"activate_schema_and_validator_only": true,
|
||||
"phase_b_release_authorized": false,
|
||||
"publisher_authorized": false,
|
||||
"writer_authorized": false,
|
||||
"credential_projection_authorized": false,
|
||||
"tag_authorized": false,
|
||||
"registry_authorized": false,
|
||||
"baseline_authorized": false,
|
||||
"production_deployment_authorized": false
|
||||
},
|
||||
"content_bindings": {
|
||||
"schema_document": {
|
||||
"path": "services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json",
|
||||
"blob_oid": "9b117667c0d98f995ac059b0e1b5ffed06ca6444",
|
||||
"sha256": "6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd"
|
||||
},
|
||||
"validator_policy": {
|
||||
"path": "services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py",
|
||||
"blob_oid": "c18d15f31340f19fc5da87944901bdfb867ab6dc",
|
||||
"sha256": "de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac"
|
||||
},
|
||||
"evidence_manifest": {
|
||||
"path": "evidence/governance-policy/AIOS-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-20260730T023551Z.json",
|
||||
"blob_oid": "c3d215b3ea525a25de514c32c5aad91d47e140b4",
|
||||
"sha256": "1b0a993940b41a4b16724b557f6abd8b6cebb56dc64cc8f813bd605b6a3399c2"
|
||||
},
|
||||
"checksums_manifest": {
|
||||
"path": "evidence/governance-policy/AIOS-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-20260730T023551Z.sha256",
|
||||
"blob_oid": "45ed084234dae6a00e69a41cd4f716f46a97d9fb",
|
||||
"sha256": "51c4b029c0792dc98d41f754e484ba3861cf7604443d9c5f289b3b0af768a41a"
|
||||
}
|
||||
},
|
||||
"offline_candidate_bindings": {
|
||||
"schema_seed_commit_oid": "8da47545a507cb798d3d6ab30ff8552a2d0a5ecf",
|
||||
"schema_seed_tree_oid": "991e68577a469657c1c4996814b6634ce5ed752f",
|
||||
"implementation_commit_oid": "a637a9dbb6e0021cb7dcf0d6b0ca00dfe10dce38",
|
||||
"implementation_tree_oid": "bf6e0c16fcad7515465bec4aefdd2e77b1e9bef0",
|
||||
"evidence_closure_commit_oid": "1e4885197f508f7921b148d71e545b3de0029b0a",
|
||||
"evidence_closure_tree_oid": "e55cf9ef19d1c6804f161de54fd3395a06149807",
|
||||
"source_diff_sha256": "c06d002db06da6cbf16f9c8321f1acbfd431f009b5127a13fd2cb5d63b278867",
|
||||
"source_pathset_sha256": "5061402d516c0e9413208ce64e2b52f77d224e6eec1f7b07c5b3a10b58df5eaf",
|
||||
"implementation_bundle_sha256": "15711b225b9ef41cca0588d21b2b95d7a95ac155efc27fcb8772c435fcdab147",
|
||||
"evidence_bundle_sha256": "ec97b8460151fc0e7e7ea9ae044ca13a1543c4b79c41a437ef087d3b1c271857"
|
||||
},
|
||||
"runtime_dependency_bindings": {
|
||||
"handoff_zip_sha256": "668239a549b4a2b2094fe9b5104b84703fe8ed75011fd8ed71ed3958a6dc0706",
|
||||
"installed_wheel_sha256": "abbf616366eb3467d4e651bb5306e22ae5e931b613df5362a0892935c4a1c0c7",
|
||||
"anchor_service": "aios-campaign-anchor.service",
|
||||
"anchor_uid": 988,
|
||||
"runner_uid": 986,
|
||||
"anchor_database": "/var/lib/aios-campaign-anchor/anchor.sqlite3",
|
||||
"anchor_socket": "/run/aios-campaign-anchor/anchor.sock",
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false
|
||||
},
|
||||
"required_machine_status_contexts": [
|
||||
"governance/schema-contract",
|
||||
"governance/validator-security",
|
||||
"governance/fault-injection",
|
||||
"governance/runtime-isolation",
|
||||
"governance/authority-mirror"
|
||||
],
|
||||
"allowed_operations_after_exact_head_owner_approval": [
|
||||
"observe_governance_pr",
|
||||
"wait_governance_gates",
|
||||
"observe_governance_merge",
|
||||
"observe_validator_activation",
|
||||
"complete_policy_activation"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite",
|
||||
"governance.direct_push",
|
||||
"approval.impersonate",
|
||||
"publisher.create",
|
||||
"writer.create",
|
||||
"credential.project",
|
||||
"phase_b.execute"
|
||||
],
|
||||
"merge_constraints": {
|
||||
"allowed_modes": [
|
||||
"FAST_FORWARD_TO_APPROVED_HEAD",
|
||||
"EXACT_TWO_PARENT_MERGE_WITH_APPROVED_TREE"
|
||||
],
|
||||
"force": false,
|
||||
"plus_refspec": false,
|
||||
"tags": false,
|
||||
"prune": false
|
||||
},
|
||||
"governance_declarations": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"force_push_used": false,
|
||||
"tag_rewritten": false,
|
||||
"direct_main_push": false,
|
||||
"secrets_committed": false,
|
||||
"owner_approval_respected": true
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"approval_type": "TASK_STATUS_MERGE_BROKER_BOOTSTRAP_RFC3339_FIX",
|
||||
"schema_version": 2,
|
||||
"repository": "aios/aios",
|
||||
"expected_governance_base_oid": "8814dff20222625b60454c613b61ccf15fa20e51",
|
||||
"request_branch": "patch/codex/AIOS-V2R1C4-BOOTSTRAP-RFC3339-OFFSET-FIX",
|
||||
"target_ref": "refs/heads/governance/approvals",
|
||||
"issued_at": "2026-08-01T02:50:00Z",
|
||||
"expires_at": "2026-08-01T14:50:00Z",
|
||||
"required_human_approvals": 1,
|
||||
"allowed_human_approvers": ["owner"],
|
||||
"require_latest_head_approval": true,
|
||||
"approval_scopes": [
|
||||
"BOOTSTRAP_FIX_APPROVAL",
|
||||
"GITEA_INTERNAL_TOKEN_ROTATION_APPROVAL"
|
||||
],
|
||||
"supersedes": {
|
||||
"pr_number": 37,
|
||||
"head_oid": "57697d81cf3d1fef23798ec390418efe8b64f4e9",
|
||||
"tree_oid": "b58af1925318a0eefa6cc709e8d52ba57bb20d7f",
|
||||
"reason": "APPROVED_BOOTSTRAP_RUNTIME_CONTRACT_MISMATCH",
|
||||
"old_review_reusable": false,
|
||||
"old_pr_immutable": true
|
||||
},
|
||||
"exact_head_binding": {
|
||||
"method": "GITEA_EXACT_HEAD_OWNER_REVIEW_AND_PR_METADATA",
|
||||
"head_oid_resolved_after_commit": true,
|
||||
"tree_oid_resolved_after_commit": true,
|
||||
"manifest_blob_oid_resolved_after_commit": true,
|
||||
"manifest_sha256_resolved_after_commit": true,
|
||||
"head_change_invalidates_review": true
|
||||
},
|
||||
"timestamp_contract": {
|
||||
"id": "RFC3339_OFFSET_AWARE_V2",
|
||||
"canonical_timezone": "UTC",
|
||||
"raw_timestamp_preserved": true,
|
||||
"naive_datetime_allowed": false,
|
||||
"accepted_timezone_forms": ["Z", "+HH:MM", "-HH:MM"],
|
||||
"fractional_seconds_allowed": true,
|
||||
"lowercase_z_allowed": false,
|
||||
"expiry_boundary": "REVIEW_MUST_BE_STRICTLY_BEFORE_EXPIRY"
|
||||
},
|
||||
"internal_token_rotation": {
|
||||
"secret_class": "GITEA_INTERNAL_TOKEN",
|
||||
"old_secret_fingerprint_algorithm": "SHA256",
|
||||
"old_secret_fingerprint": "42e2af774700405ae32d55c98ca010fdb5da890b08f9e12e77372133c68e0562",
|
||||
"old_secret_reference": "gitea-config://security/INTERNAL_TOKEN",
|
||||
"affected_consumers": [
|
||||
"gitea-config-residue:/data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini.bak-20260706-auth-gitops",
|
||||
"gitea-config:/data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini",
|
||||
"systemd-service:aios-gitea.service"
|
||||
],
|
||||
"replacement_secret_reference": "systemd-credential-encrypted://gitea-internal-token-v2",
|
||||
"rotation_method": "ATOMIC_REPLACE_THEN_REVOKE",
|
||||
"rollback_plan_path": "evidence/governance/bootstrap-rfc3339-fix-20260801/ROLLBACK_PLAN.json",
|
||||
"rollback_plan_sha256": "3283f4c3d6bb7f34ac893789f7e9f5df6c68b7807203e6f471ad92e8e007ff3f",
|
||||
"audit_window_start": "2026-08-01T00:00:00Z",
|
||||
"allowed_operations": [
|
||||
"generate_replacement_internal_token_to_encrypted_runtime_credential",
|
||||
"validate_replacement_internal_token_without_logging_plaintext",
|
||||
"atomically_replace_exact_gitea_internal_token_config_value",
|
||||
"restart_exact_aios_gitea_service",
|
||||
"verify_exact_consumers_healthy",
|
||||
"verify_old_internal_token_authentication_rejected",
|
||||
"remove_or_redact_exact_old_secret_config_residue",
|
||||
"audit_exposure_window",
|
||||
"write_immutable_security_event_receipt"
|
||||
],
|
||||
"access_token_table_exception_applicable": false,
|
||||
"plaintext_secret_in_manifest_allowed": false,
|
||||
"plaintext_secret_in_argv_allowed": false,
|
||||
"plaintext_secret_in_logs_allowed": false,
|
||||
"plaintext_secret_in_git_allowed": false
|
||||
},
|
||||
"phase_b": {
|
||||
"approval_mode": "TWO_STAGE_REQUIRED",
|
||||
"reason": "CURRENT_SCHEMAS_DO_NOT_ATOMICALLY_BIND_BOOTSTRAP_ACTIVATION_AND_FULL_TASK_SCOPED_RELEASE_PAYLOAD",
|
||||
"old_pr_number": 36,
|
||||
"old_head_oid": "a8644fc172bd8ebbba17a16e6bf586c93fd1e793",
|
||||
"old_approval_reusable": false,
|
||||
"successor_required_after_bootstrap_activation": true,
|
||||
"frozen_release_objects_unchanged": true
|
||||
},
|
||||
"frozen_release_objects": {
|
||||
"source_commit_oid": "1857b88690cd1c0119df984534c260b6f217a21a",
|
||||
"source_tree_oid": "17c153de2338f570f70cb7407e7dda3c596b524b",
|
||||
"evidence_closure_oid": "057f5923f72cdb8d002ae40bc0cd584961825801",
|
||||
"evidence_tree_oid": "30be66d153fe9d3758f5fdba89ca4169beab38f8",
|
||||
"git_bundle_sha256": "762e280e240988282bc2c2e65fafecedd596400ab6829016947e84559508a015",
|
||||
"tag_oid": "7f82d77cf7702a6d695b4f0b74da7a9a9ecd7fa3",
|
||||
"peeled_target_oid": "057f5923f72cdb8d002ae40bc0cd584961825801",
|
||||
"release_artifact_sha256": "39523caa48f39b33e5e5df785bb568e0381d8b20301746c280442f73f294f2b2",
|
||||
"rollback_artifact_sha256": "dd0dbbe21db14722996636347ea1d22917436c688d5f0b7055b5bfb1776c4d95",
|
||||
"release_root_sha256": "16e4e881287cef4a02525a6a5b337d3e12995e510832fe04fea2ac196885f3ef",
|
||||
"evidence_manifest_sha256": "3b0b72af400997385e817e9bec677942288f422b69c2d2b52a8a00407fc395e0"
|
||||
},
|
||||
"allowed_status_contexts": [
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator"
|
||||
],
|
||||
"allowed_identities": ["codex", "gpt"],
|
||||
"allowed_operations": [
|
||||
"install_exact_approved_bootstrap_files",
|
||||
"sign_task_grant_after_owner_exact_head_review",
|
||||
"issue_task_status_credential",
|
||||
"issue_task_merge_credential",
|
||||
"publish_exact_five_statuses",
|
||||
"fast_forward_only_merge",
|
||||
"readback_status_and_ref",
|
||||
"revoke_task_credentials",
|
||||
"remove_credential_projection",
|
||||
"sweep_completed_or_expired_task_credentials"
|
||||
],
|
||||
"operation_limits": {
|
||||
"maximum_status_writes_per_grant": 5,
|
||||
"maximum_merges_per_grant": 1,
|
||||
"maximum_grant_lifetime_seconds": 43200,
|
||||
"automatic_revocation_required": true
|
||||
},
|
||||
"denied_operations": [
|
||||
"branch_push_outside_request_branch",
|
||||
"main_push",
|
||||
"tag_write",
|
||||
"registry_write",
|
||||
"baseline_write",
|
||||
"production_write",
|
||||
"branch_protection_change",
|
||||
"force_push",
|
||||
"squash",
|
||||
"rebase",
|
||||
"merge_commit",
|
||||
"approval_impersonation",
|
||||
"commit_status_database_write",
|
||||
"database_write_outside_exact_task_access_token_revocation",
|
||||
"reuse_pr36_or_pr37_review",
|
||||
"plaintext_secret_persistence"
|
||||
],
|
||||
"runtime_controls": {
|
||||
"global_gitops_writes_enabled": false,
|
||||
"systemd_encrypted_credentials_required": true,
|
||||
"systemd_projection_mode": "0440_ROOT_ROOT_CREDENTIALS_DIRECTORY_ONLY",
|
||||
"systemd_gitea_token_embedded_name": "gitea-token",
|
||||
"sqlite_parent_directory_metadata_write_required": true,
|
||||
"sqlite_sibling_paths_read_only": true,
|
||||
"sqlite_quota_journal_mode": "WAL",
|
||||
"sqlite_quota_synchronous": "FULL",
|
||||
"status_evidence_digest_bound_in_signed_grant": true,
|
||||
"merge_method": "fast-forward-only",
|
||||
"commit_status_database_write_allowed": false
|
||||
},
|
||||
"approved_files": {
|
||||
"deploy/systemd/aios-task-credential-broker@.service": "95f8da02600b3c908013ffa723d79f6fca0c7a4332a077a7b673e97ce35bcd69",
|
||||
"deploy/systemd/aios-task-credential-expiry-sweeper.service": "72a336523983b2486300c4e52f2f01dce392aaf6ffd953041c209d204df85717",
|
||||
"deploy/systemd/aios-task-credential-expiry-sweeper.timer": "49ff9c80cbd0b44fd584dc7d29f4db0d042e33f325b1ed7af81a5be42f1cd011",
|
||||
"deploy/systemd/aios-task-merge-broker@.service": "4e6b8fcf541f225e58ea080107fc30deb0c4ac9eac70217ff9e57081245c106a",
|
||||
"deploy/systemd/aios-task-status-broker@.service": "378a2e8f658cb56cac5dcc151772ec6a00d86f6c1ce3106509b9aa87549dc9da",
|
||||
"evidence/governance/bootstrap-rfc3339-fix-20260801/ROLLBACK_PLAN.json": "3283f4c3d6bb7f34ac893789f7e9f5df6c68b7807203e6f471ad92e8e007ff3f",
|
||||
"evidence/governance/bootstrap-rfc3339-fix-20260801/TEST_SUMMARY.json": "c7d28139fddb9666e60fd62a9a27803dd00a1ebbccbbb75a5121cd8ed3d20fc2",
|
||||
"services/aios-single-owner-governance/aios_single_owner_governance/task_credential_broker.py": "3b2fbbf2698a2d8385a2bf9eccdebbea629642908cbc7a1e8251476475e0d647",
|
||||
"services/aios-single-owner-governance/aios_single_owner_governance/task_credential_expiry_sweeper.py": "8d383768eb0322c84a35282c126bee26a3e6419b125780424ffc7793a504efdf",
|
||||
"services/aios-single-owner-governance/aios_single_owner_governance/task_status_merge_broker.py": "697d8deccdf2fec902dab38a0436075d78a95c1c5b410e115ee74cf79f477e63",
|
||||
"services/aios-single-owner-governance/task-status-merge-grant-v1.schema.json": "34472624261203b5c1a215c141887ea3fed5d68fb3c503c7db2ad9a717862597",
|
||||
"tests/single_owner_governance/test_task_status_merge_broker.py": "af1c5844ee0f7d6907f8cb68d5f503f3bdbd59bb05150e5364eaf7bf2b37be1a"
|
||||
},
|
||||
"aggregate_bindings": {
|
||||
"broker_module_set_sha256": "c73ccaf8f60cf2446a5d4c0b4baf5456cb77961fda682285cb0a4f509ed49caa",
|
||||
"systemd_definition_set_sha256": "3591be96f1717e7a0a57957aafbb233eec2b1617a8fb938889ccd312d812d066",
|
||||
"grant_schema_sha256": "34472624261203b5c1a215c141887ea3fed5d68fb3c503c7db2ad9a717862597",
|
||||
"test_summary_sha256": "c7d28139fddb9666e60fd62a9a27803dd00a1ebbccbbb75a5121cd8ed3d20fc2",
|
||||
"python_executable_sha256": "e1efa562c2cc2e35521a5c9c9b9939921001ff8ca9708a13ef15ace68cc2ccd7"
|
||||
},
|
||||
"machine_gate_requirements": {
|
||||
"compileall": "PASS",
|
||||
"unit_tests": "PASS",
|
||||
"rfc3339_offset_tests": "PASS",
|
||||
"json_schema": "PASS",
|
||||
"systemd_validation": "PASS",
|
||||
"changed_path_secret_scan": "PASS",
|
||||
"fault_injection": "PASS",
|
||||
"scope_boundary_tests": "PASS"
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"acknowledges_pr30_owner_approval_was_missing": true,
|
||||
"activation_prerequisites_artifact_sha256": "729077800c6510bf8930c97a78781218d1bce9ec40157aa99fc2bd205466e895",
|
||||
"activation_prerequisites_commit_oid": "fa038d17b29001e26f1ac88019a407a4b3ebaa7e",
|
||||
"activation_prerequisites_rollback_artifact_sha256": "00e40eace10a0aae1731eb98851fec71a0c13c48e1e6e5a113e30a4356c2086f",
|
||||
"activation_prerequisites_tree_oid": "d5ff67f848807ef2c7a695c05cdd0bb5f2644fc2",
|
||||
"approval_request_branch": "governance/request/single-owner-atomic-activation-owner-ratification-20260729T014918Z-e503d96665ee",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_ATOMIC_ACTIVATION_OWNER_RATIFICATION",
|
||||
"branch_protection_adapter_sha256": "2706dc6095ff639838f72c91a66c1f80711893c8c0498555b388feaacf2db569",
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite",
|
||||
"governance.direct_push",
|
||||
"approval.impersonate"
|
||||
],
|
||||
"direct_protected_branch_push_allowed": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"dormant_deployment_evidence_sha256": "c4c326784a3d66d62e32c46be3506c801585c06a34e62c9a1f210492a6c1bc06",
|
||||
"evidence_closure_oid": "5f45139ca5f7f7f3da244141c5115d12f5af58ae",
|
||||
"evidence_closure_sha256": "53e5f2fdc01a6225048dfdf6eac973e99743f0295f17f3a42f01dce7e7050ab8",
|
||||
"expected_governance_base_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
|
||||
"expires_at": "2026-07-29T13:49:18Z",
|
||||
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
|
||||
"approval_validator.activate",
|
||||
"governance_state_machine.activate",
|
||||
"gitea_branch_protection.atomic_update",
|
||||
"rollback_configuration.install",
|
||||
"single_owner_policy.activate",
|
||||
"break_glass.expire",
|
||||
"post_activation_tests.execute"
|
||||
],
|
||||
"force_push_allowed": false,
|
||||
"human_authorizer": "owner",
|
||||
"issued_at": "2026-07-29T01:49:18Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"pr30_codex_approval_review_id": 45,
|
||||
"pr30_codex_reviewed_at": "2026-07-29T01:24:38Z",
|
||||
"pr30_codex_reviewed_head_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
|
||||
"pr30_gpt_approval_review_id": 46,
|
||||
"pr30_gpt_reviewed_at": "2026-07-29T01:25:02Z",
|
||||
"pr30_gpt_reviewed_head_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
|
||||
"pr30_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-ATOMIC-ACTIVATION-20260728T204808Z-4cab1d5829d6.json",
|
||||
"pr30_merge_tree_oid": "219974f085efe473fcc6ad2170f67312d8e57c54",
|
||||
"pr30_merged_at": "2026-07-29T01:25:08Z",
|
||||
"pr30_owner_review_id": 42,
|
||||
"pr30_owner_review_state_observed": "REQUESTED_NOT_APPROVED",
|
||||
"pr30_owner_reviewed_head_oid": null,
|
||||
"pr30_premerge_base_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
|
||||
"production_deployment_allowed": false,
|
||||
"ratifies_pr30_manifest_blob_oid": "38c95dde2d3831f8e3c80b342369f1c3e1cbf34c",
|
||||
"ratifies_pr30_manifest_sha256": "bc19ada0e8eeb47d421a6360cf1d66b6d1129eb6056a447d9ff70ebd5ada1718",
|
||||
"ratifies_pr30_merge_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
|
||||
"ratifies_pr30_request_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
|
||||
"ratifies_pr_number": 30,
|
||||
"require_latest_head_approval": true,
|
||||
"required_reviewers": [
|
||||
"owner",
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"rollback_configuration_sha256": "eb44fc5f655f8c30c4b687185055ba3a2725189e19538e3a9a46a2c0cf141b8c",
|
||||
"schema": "aios.governance-approval/v1",
|
||||
"state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"task_id": "AIOS-PR30-SINGLE-OWNER-ATOMIC-ACTIVATION-OWNER-RATIFICATION-20260729",
|
||||
"test_evidence_sha256": "42168da7437028440bece3432d8aa38a8722b4db18244423d9daaa380675c45a",
|
||||
"validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"wip_blocks_merge": true
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"approval_request_branch": "governance/request/single-owner-governance-activation-prerequisites-20260728T183025127107Z",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_ACTIVATION_PREREQUISITES",
|
||||
"binds_final_ratification_manifest_blob_oid": "2865fbec766901eb61fc8aed0542970e951e0d1d",
|
||||
"binds_final_ratification_manifest_sha256": "216e54503d8e6a3f4c908084e19ce1e6242d3ba15f7b8011a9a9d9f7975e63e7",
|
||||
"binds_final_ratification_merge_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
|
||||
"binds_mirror_receipt_sha256": "6873133f7233bfed0d3b78b01847ad4251b8b67f3b6db7011efd07664f8d088f",
|
||||
"expected_governance_base_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
|
||||
"expires_at": "2026-07-29T06:30:25Z",
|
||||
"explicitly_allowed_after_owner_approval_and_legacy_merge": [
|
||||
"task_bound_activation_writer_grant.create",
|
||||
"gitea_owner_review_adapter.create_and_dormant_deploy",
|
||||
"machine_status_producer.create_and_dormant_deploy",
|
||||
"machine_status_producer.credential_rotation_and_revocation",
|
||||
"maintenance_lock.create",
|
||||
"branch_protection_compensation_saga.create",
|
||||
"targeted_online_rollback_executor.create",
|
||||
"disposable_gitea_fixture.create_test_destroy",
|
||||
"single_owner_activation_prerequisite_tests.execute",
|
||||
"activation_prerequisite_evidence.create"
|
||||
],
|
||||
"implementation_branch": "patch/single-owner-governance-activation-prerequisites-20260729",
|
||||
"implementation_paths": [
|
||||
"services/aios-single-owner-governance/**",
|
||||
"tests/single_owner_governance/**",
|
||||
"deploy/systemd/aios-governance-*.service",
|
||||
"deploy/systemd/aios-governance-*.timer"
|
||||
],
|
||||
"issued_at": "2026-07-28T18:30:25Z",
|
||||
"machine_status_contexts": [
|
||||
"aios/machine-gates",
|
||||
"aios/secret-scan",
|
||||
"aios/evidence-closure"
|
||||
],
|
||||
"observed_blockers": {
|
||||
"commit_status_row_count": 0,
|
||||
"disposable_governance_fixture_present": false,
|
||||
"gitea_owner_review_validator_present": false,
|
||||
"gitea_version": "1.22.6",
|
||||
"native_branch_protection_etag_cas_present": false,
|
||||
"required_status_context_producers_present": false,
|
||||
"targeted_online_rollback_executor_present": false
|
||||
},
|
||||
"owner_approval_contract": {
|
||||
"agent_must_not_submit_review": true,
|
||||
"dismissed": false,
|
||||
"head_change_invalidates_approval": true,
|
||||
"only_human_approver": "owner",
|
||||
"owner_user_id": 2,
|
||||
"review_state": "APPROVED",
|
||||
"reviewed_before_expires_at": true,
|
||||
"reviewed_head_oid_must_equal_current_head_oid": true,
|
||||
"stale": false
|
||||
},
|
||||
"permanent_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"direct_main_push": false,
|
||||
"force_push": false,
|
||||
"history_rewrite": false,
|
||||
"secret_commit": false,
|
||||
"tag_rewrite": false
|
||||
},
|
||||
"pre_approval_non_authorizations": {
|
||||
"activation_allowed": false,
|
||||
"baseline_write_allowed": false,
|
||||
"branch_protection_change_allowed": false,
|
||||
"fixture_deployment_allowed": false,
|
||||
"integration_write_allowed": false,
|
||||
"main_write_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"production_write_allowed": false,
|
||||
"registry_write_allowed": false,
|
||||
"tag_write_allowed": false,
|
||||
"writer_grant_allowed": false
|
||||
},
|
||||
"reason": "safe-atomic-activation-prerequisites-absent",
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"schema": "aios.single-owner-governance-activation-prerequisites/v1",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-PREREQUISITES-20260729",
|
||||
"wip": true,
|
||||
"wip_transition_contract": {
|
||||
"codex_and_gpt_not_requested_before_owner_approval": true,
|
||||
"remove_wip_without_head_change_only_after_owner_approval": true,
|
||||
"then_request_legacy_mechanical_reviews": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"wip_must_remain_until_owner_exact_head_approval": true
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"activation_prerequisites_artifact_sha256": "729077800c6510bf8930c97a78781218d1bce9ec40157aa99fc2bd205466e895",
|
||||
"activation_prerequisites_commit_oid": "fa038d17b29001e26f1ac88019a407a4b3ebaa7e",
|
||||
"activation_prerequisites_rollback_artifact_sha256": "00e40eace10a0aae1731eb98851fec71a0c13c48e1e6e5a113e30a4356c2086f",
|
||||
"activation_prerequisites_tree_oid": "d5ff67f848807ef2c7a695c05cdd0bb5f2644fc2",
|
||||
"approval_request_branch": "governance/request/single-owner-governance-atomic-activation-20260728T204808Z-4cab1d5829d6",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_ATOMIC_ACTIVATION",
|
||||
"branch_protection_adapter_sha256": "2706dc6095ff639838f72c91a66c1f80711893c8c0498555b388feaacf2db569",
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite",
|
||||
"governance.direct_push",
|
||||
"approval.impersonate"
|
||||
],
|
||||
"deployment_mode_before_execution": "DORMANT_SHADOW_ONLY",
|
||||
"direct_protected_branch_push_allowed": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"dormant_deployment_evidence_sha256": "c4c326784a3d66d62e32c46be3506c801585c06a34e62c9a1f210492a6c1bc06",
|
||||
"evidence_closure_oid": "5f45139ca5f7f7f3da244141c5115d12f5af58ae",
|
||||
"evidence_closure_sha256": "53e5f2fdc01a6225048dfdf6eac973e99743f0295f17f3a42f01dce7e7050ab8",
|
||||
"expected_governance_base_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
|
||||
"expires_at": "2026-07-29T08:48:08Z",
|
||||
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
|
||||
"approval_validator.activate",
|
||||
"governance_state_machine.activate",
|
||||
"gitea_branch_protection.atomic_update",
|
||||
"rollback_configuration.install",
|
||||
"single_owner_policy.activate",
|
||||
"break_glass.expire",
|
||||
"post_activation_tests.execute"
|
||||
],
|
||||
"force_push_allowed": false,
|
||||
"issued_at": "2026-07-28T20:48:08Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"machine_gates_required": true,
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"pr26_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"pr27_merge_oid": "4e6ef70029b1d94626e432892f7d3beb8e3e5627",
|
||||
"pr28_merge_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
|
||||
"pr29_merge_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
|
||||
"production_deployment_allowed": false,
|
||||
"require_latest_head_approval": true,
|
||||
"required_human_authorizer": "owner",
|
||||
"required_reviewers": [
|
||||
"owner",
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"rollback_configuration_sha256": "eb44fc5f655f8c30c4b687185055ba3a2725189e19538e3a9a46a2c0cf141b8c",
|
||||
"rollback_required_on_any_activation_failure": true,
|
||||
"schema": "aios.governance-approval/v1",
|
||||
"single_owner_policy_active_before_execution": false,
|
||||
"state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ATOMIC-ACTIVATION-20260729",
|
||||
"test_evidence_sha256": "42168da7437028440bece3432d8aa38a8722b4db18244423d9daaa380675c45a",
|
||||
"validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"wip_blocks_merge": true
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
{
|
||||
"acknowledges_pr26_owner_approval_was_missing": true,
|
||||
"approval_request_branch": "governance/request/single-owner-control-plane-deployment-20260728T170740Z-ea7a102dac417c23",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_CONTROL_PLANE_DEPLOYMENT",
|
||||
"control_plane_targets": {
|
||||
"allowed_human_approvers": [
|
||||
"owner"
|
||||
],
|
||||
"branch_deletion": false,
|
||||
"direct_protected_branch_push": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"executor_must_not_approve": true,
|
||||
"force_push": false,
|
||||
"governance_model": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"historical_records_immutable": true,
|
||||
"machine_gates_required": true,
|
||||
"policy_effective_at_set_only_after_activation_tests_pass": true,
|
||||
"protected_branches": [
|
||||
"governance/approvals",
|
||||
"main",
|
||||
"release/**",
|
||||
"integration/**"
|
||||
],
|
||||
"requester_must_not_approve": true,
|
||||
"require_approval_for_latest_commit": true,
|
||||
"required_human_approvals": 1,
|
||||
"required_owner_approvals": 1,
|
||||
"required_status_contexts": [
|
||||
"aios/machine-gates",
|
||||
"aios/secret-scan",
|
||||
"aios/evidence-closure"
|
||||
],
|
||||
"tag_rewrite": false,
|
||||
"wip_blocks_merge": true
|
||||
},
|
||||
"does_not_claim_pr26_met_old_two_natural_person_policy": true,
|
||||
"expected_authoritative_governance_base_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"expected_gitea_governance_base_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"expires_at": "2026-07-29T05:07:40Z",
|
||||
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
|
||||
"approval_mirror.fast_forward_governance_only",
|
||||
"approval_validator.update",
|
||||
"state_machine.update",
|
||||
"gitea_branch_protection.update_via_authenticated_api_compare_and_swap",
|
||||
"governance_policy.activate",
|
||||
"rollback_configuration.install",
|
||||
"governance_acceptance_tests.execute",
|
||||
"single_owner_governance_activation_receipt.create"
|
||||
],
|
||||
"explicitly_denied_operations": [
|
||||
"force_push",
|
||||
"plus_refspec",
|
||||
"tag_fetch",
|
||||
"prune",
|
||||
"direct_git_database_write",
|
||||
"direct_protected_ref_push",
|
||||
"main_write",
|
||||
"integration_write",
|
||||
"tag_write",
|
||||
"registry_write",
|
||||
"baseline_write",
|
||||
"production_write",
|
||||
"history_rewrite",
|
||||
"branch_delete",
|
||||
"secret_read",
|
||||
"approval_impersonation"
|
||||
],
|
||||
"global_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"direct_main_push": false,
|
||||
"force_push_used": false,
|
||||
"owner_approval_respected": true,
|
||||
"secrets_committed": false,
|
||||
"tag_rewritten": false
|
||||
},
|
||||
"implementation_surface": {
|
||||
"approval_validator": [
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py"
|
||||
],
|
||||
"runtime": [
|
||||
"active-runtime/app/silent_release_controller.py",
|
||||
"active-runtime/app/silent_release_hardening.py"
|
||||
],
|
||||
"state_machine": [
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py",
|
||||
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py"
|
||||
],
|
||||
"systemd": [
|
||||
"deploy/systemd/aios-autonomous-release-candidate.service",
|
||||
"deploy/systemd/aios-silent-release-controller.service"
|
||||
]
|
||||
},
|
||||
"issued_at": "2026-07-28T17:07:40Z",
|
||||
"legacy_gitea_protection_gate": {
|
||||
"account_reviews_do_not_prove_distinct_natural_persons": true,
|
||||
"both_must_approve_this_exact_head": true,
|
||||
"current_approval_whitelist": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"current_required_approvals": 2
|
||||
},
|
||||
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET_RATIFICATION",
|
||||
"mirror_contract": {
|
||||
"expected_chain_contains_policy_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"expected_pre_sync_authoritative_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"fast_forward_only": true,
|
||||
"force": false,
|
||||
"fsck_connectivity_only_required": true,
|
||||
"only_ref": "refs/heads/governance/approvals",
|
||||
"plus_refspec": false,
|
||||
"prune": false,
|
||||
"quarantine_required": true,
|
||||
"source": "gitea_repository_file_url_only",
|
||||
"tags": false
|
||||
},
|
||||
"owner_break_glass_ratification": {
|
||||
"attests_reason": "single-maintainer-system",
|
||||
"attests_scope": "governance-policy-migration-only",
|
||||
"created_before_expires_at": true,
|
||||
"dismissed": false,
|
||||
"required": true,
|
||||
"review_state": "APPROVED",
|
||||
"reviewed_head_oid_must_equal_current_pr_head_oid": true,
|
||||
"reviewer_user_id": 2,
|
||||
"reviewer_username": "owner",
|
||||
"stale": false
|
||||
},
|
||||
"pre_approval_non_authorizations": {
|
||||
"approval_validator_update_allowed": false,
|
||||
"development_allowed": false,
|
||||
"gitea_branch_protection_update_allowed": false,
|
||||
"governance_policy_activation_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"mirror_allowed": false,
|
||||
"production_change_allowed": false,
|
||||
"rollback_install_allowed": false,
|
||||
"standing_writer_policy_allowed": false,
|
||||
"state_machine_update_allowed": false
|
||||
},
|
||||
"pre_cutover_protection_snapshot": {
|
||||
"protected_branch": [
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 3,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "main",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 2,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
}
|
||||
],
|
||||
"protected_tag": []
|
||||
},
|
||||
"pre_cutover_protection_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"ratifies_policy_manifest_blob_oid": "65f44007b10fc2a912d69e3f9081c0cfff77dfad",
|
||||
"ratifies_policy_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-MIGRATION-20260728T162246Z.json",
|
||||
"ratifies_policy_manifest_sha256": "c097475afbe3074e6482f125b635981f8c0284f3d763c926e2a63e1a3ee1c6c3",
|
||||
"ratifies_policy_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"ratifies_policy_pr_number": 26,
|
||||
"ratifies_policy_request_oid": "312d3b483049a748675e887a516ec739c81ff474",
|
||||
"ratifies_policy_tree_oid": "5c6974c058bba6210dcc1bdd0968fb7e6c84a844",
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_AND_LEGACY_PROTECTION_APPROVAL_PENDING",
|
||||
"required_machine_gates": [
|
||||
"owner_exact_head_review_valid",
|
||||
"legacy_protection_reviews_valid",
|
||||
"ordinary_merge_commit",
|
||||
"parent_order_verified",
|
||||
"manifest_add_only",
|
||||
"quarantine_verification",
|
||||
"approval_mirror_fast_forward",
|
||||
"validator_tests",
|
||||
"state_machine_tests",
|
||||
"protection_compare_and_swap",
|
||||
"positive_governance_tests",
|
||||
"negative_governance_tests",
|
||||
"secret_scan",
|
||||
"rollback_integrity",
|
||||
"production_write_false"
|
||||
],
|
||||
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
|
||||
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
|
||||
"schema": "aios.single-owner-governance-control-plane-deployment/v1",
|
||||
"scope": "pr26-ratification-mirror-and-single-owner-control-plane-deployment-only",
|
||||
"task_id": "AIOS-SINGLE-OWNER-CONTROL-PLANE-DEPLOYMENT-20260729"
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"acknowledges_pr26_and_pr27_owner_approval_was_missing": true,
|
||||
"approval_request_branch": "governance/request/single-owner-governance-final-owner-ratification-20260728T173634412838Z",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_FINAL_OWNER_RATIFICATION",
|
||||
"backup_checksums_sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"backup_root": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/pre-migration-backup",
|
||||
"break_glass_expires_immediately_after_activation": true,
|
||||
"does_not_claim_old_two_natural_person_compliance": true,
|
||||
"expires_at": "2026-07-29T05:36:34Z",
|
||||
"inherits_only_pr27_control_plane_allowlist": [
|
||||
"approval_mirror.fast_forward_governance_only",
|
||||
"approval_validator.update",
|
||||
"state_machine.update",
|
||||
"gitea_branch_protection.update_via_authenticated_api_compare_and_swap",
|
||||
"governance_policy.activate",
|
||||
"rollback_configuration.install",
|
||||
"governance_acceptance_tests.execute",
|
||||
"single_owner_governance_activation_receipt.create"
|
||||
],
|
||||
"issued_at": "2026-07-28T17:36:34Z",
|
||||
"legacy_mechanical_protection_gate": {
|
||||
"account_reviews_do_not_count_as_owner_ratification": true,
|
||||
"account_reviews_do_not_prove_distinct_natural_persons": true,
|
||||
"approval_and_merge_whitelist_user_ids": [
|
||||
42,
|
||||
43
|
||||
],
|
||||
"both_reviews_must_bind_current_request_head": true,
|
||||
"required_approvals": 2,
|
||||
"reviewers": [
|
||||
"codex",
|
||||
"gpt"
|
||||
]
|
||||
},
|
||||
"merge_contract": {
|
||||
"first_parent_must_equal_premerge_governance_oid": true,
|
||||
"force": false,
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"parent_count": 2,
|
||||
"rebase": false,
|
||||
"second_parent_must_equal_final_ratification_request_oid": true,
|
||||
"single_file_add_only": true,
|
||||
"squash": false
|
||||
},
|
||||
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET",
|
||||
"mirror_contract": {
|
||||
"expected_authoritative_pre_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"fast_forward_only": true,
|
||||
"force": false,
|
||||
"fsck_connectivity_only_required": true,
|
||||
"only_ref": "refs/heads/governance/approvals",
|
||||
"plus_refspec": false,
|
||||
"prune": false,
|
||||
"quarantine_required": true,
|
||||
"required_chain": [
|
||||
"ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"4e6ef70029b1d94626e432892f7d3beb8e3e5627",
|
||||
"FINAL_RATIFICATION_MERGE_OID"
|
||||
],
|
||||
"source": "gitea_repository_file_url_only",
|
||||
"tags": false
|
||||
},
|
||||
"owner_ratification_contract": {
|
||||
"agent_must_not_submit_review": true,
|
||||
"dismissed": false,
|
||||
"only_valid_human_approver": "owner",
|
||||
"owner_user_id": 2,
|
||||
"review_state": "APPROVED",
|
||||
"review_type_must_be_approved_not_requested": true,
|
||||
"reviewed_before_expires_at": true,
|
||||
"reviewed_before_merge": true,
|
||||
"reviewed_head_oid_must_equal_current_request_head_oid": true,
|
||||
"stale": false
|
||||
},
|
||||
"permanent_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"direct_main_push": false,
|
||||
"force_push_used": false,
|
||||
"owner_approval_respected": true,
|
||||
"secrets_committed": false,
|
||||
"tag_rewritten": false
|
||||
},
|
||||
"post_merge_activation_preconditions": [
|
||||
"owner_exact_head_approval_valid",
|
||||
"legacy_mechanical_reviews_valid",
|
||||
"ordinary_merge_commit",
|
||||
"one_file_add_only",
|
||||
"quarantine_chain_verified",
|
||||
"approval_mirror_fast_forward_closed",
|
||||
"rollback_integrity_pass",
|
||||
"validator_and_state_machine_tests_pass",
|
||||
"protection_compare_and_swap_pass",
|
||||
"positive_and_negative_governance_tests_pass",
|
||||
"production_write_false"
|
||||
],
|
||||
"pre_activation_protection_snapshot": {
|
||||
"protected_branch": [
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 3,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "main",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 2,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
}
|
||||
],
|
||||
"protected_tag": []
|
||||
},
|
||||
"pre_activation_protection_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"pre_approval_non_authorizations": {
|
||||
"baseline_write_allowed": false,
|
||||
"branch_protection_update_allowed": false,
|
||||
"control_plane_activation_allowed": false,
|
||||
"development_allowed": false,
|
||||
"integration_write_allowed": false,
|
||||
"main_write_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"mirror_allowed": false,
|
||||
"production_write_allowed": false,
|
||||
"registry_write_allowed": false,
|
||||
"rollback_install_allowed": false,
|
||||
"standing_writer_allowed": false,
|
||||
"state_machine_update_allowed": false,
|
||||
"tag_write_allowed": false,
|
||||
"validator_update_allowed": false
|
||||
},
|
||||
"ratification_scope": "GOVERNANCE_MIGRATION_AND_CONTROL_PLANE_ACTIVATION_ONLY",
|
||||
"ratifies_pr26_manifest_blob_oid": "65f44007b10fc2a912d69e3f9081c0cfff77dfad",
|
||||
"ratifies_pr26_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-MIGRATION-20260728T162246Z.json",
|
||||
"ratifies_pr26_manifest_sha256": "c097475afbe3074e6482f125b635981f8c0284f3d763c926e2a63e1a3ee1c6c3",
|
||||
"ratifies_pr26_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
|
||||
"ratifies_pr26_request_oid": "312d3b483049a748675e887a516ec739c81ff474",
|
||||
"ratifies_pr26_tree_oid": "5c6974c058bba6210dcc1bdd0968fb7e6c84a844",
|
||||
"ratifies_pr27_manifest_blob_oid": "77f3c2ab85cba2c30c8c0aa36e591232d0f4c396",
|
||||
"ratifies_pr27_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-CONTROL-PLANE-DEPLOYMENT-20260728T170740Z-ea7a102dac417c23.json",
|
||||
"ratifies_pr27_manifest_sha256": "2ed09180c0aeadbef2e5d6310efe7c2d0de15e0e87b6135e7a8494cb4f0967c7",
|
||||
"ratifies_pr27_merge_oid": "4e6ef70029b1d94626e432892f7d3beb8e3e5627",
|
||||
"ratifies_pr27_request_oid": "80feef309fd8e1db7795f68d5bf0f15ce805b365",
|
||||
"ratifies_pr27_tree_oid": "85af0d1e88e8484a9c3a0562a81cbc1de09250ae",
|
||||
"ratifies_pr_numbers": [
|
||||
26,
|
||||
27
|
||||
],
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"retroactive_authorization_for_other_operations": false,
|
||||
"rollback_archive_member_hashes": {
|
||||
"pre-migration-backup/app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
|
||||
"pre-migration-backup/authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
|
||||
"pre-migration-backup/authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
|
||||
"pre-migration-backup/backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
|
||||
"pre-migration-backup/checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"pre-migration-backup/gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
|
||||
"pre-migration-backup/gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
|
||||
"pre-migration-backup/gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
|
||||
"pre-migration-backup/governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"pre-migration-backup/protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
|
||||
"pre-migration-backup/protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"pre-migration-backup/repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
|
||||
},
|
||||
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
|
||||
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
|
||||
"schema": "aios.single-owner-governance-final-owner-ratification/v1",
|
||||
"single_owner_policy_effective_at": "FINAL_RATIFICATION_MERGE_OID",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-FINAL-RATIFICATION-20260729"
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
{
|
||||
"allowed_human_approvers": [
|
||||
"owner"
|
||||
],
|
||||
"approval_expires_at": "2026-07-29T16:22:46Z",
|
||||
"approval_request_branch": "governance/request/single-owner-governance-migration-20260728T162246Z",
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_POLICY",
|
||||
"approval_types_migrated_after_policy_effective_at": [
|
||||
"SOURCE_MAPPING_AND_IMPORT_EXECUTION_RENEWAL",
|
||||
"CONTROL_PLANE_DEPLOYMENT_APPROVAL",
|
||||
"DEVELOPMENT_MERGE_APPROVAL",
|
||||
"RELEASE_CANDIDATE_APPROVAL",
|
||||
"PRODUCTION_RELEASE_APPROVAL",
|
||||
"TAG_APPROVAL",
|
||||
"REGISTRY_BASELINE_PROMOTION_APPROVAL",
|
||||
"PRODUCTION_DEPLOYMENT_APPROVAL",
|
||||
"ROLLBACK_APPROVAL"
|
||||
],
|
||||
"atomic_migration": {
|
||||
"backup_checksums_sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"backup_root": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/pre-migration-backup",
|
||||
"direct_database_update": false,
|
||||
"direct_ref_update": false,
|
||||
"dual_capable_validator_shadow_required": true,
|
||||
"identity_snapshot_sha256": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"live_protection_canonical_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"maintenance_lock_required": true,
|
||||
"old_rules_remain_active_until_owner_approval_and_machine_gates_pass": true,
|
||||
"protected_branch_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
|
||||
"protection_update_method": "authenticated_admin_API_compare_and_swap",
|
||||
"reviewable_backup_file_hashes": {
|
||||
"app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
|
||||
"authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
|
||||
"authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
|
||||
"backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
|
||||
"checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
|
||||
"gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
|
||||
"gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
|
||||
"governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
|
||||
"protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
|
||||
"repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
|
||||
"repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
|
||||
"repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
|
||||
"repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
|
||||
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
|
||||
"repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
|
||||
"repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
|
||||
"repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
|
||||
"repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
|
||||
},
|
||||
"reviewable_pre_migration_governance_identity_snapshot": [
|
||||
{
|
||||
"created_unix": 1783321252,
|
||||
"id": 1,
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"last_login_unix": 0,
|
||||
"lower_name": "aios",
|
||||
"prohibit_login": 1,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1783335380,
|
||||
"id": 2,
|
||||
"is_active": 1,
|
||||
"is_admin": 1,
|
||||
"last_login_unix": 1785086126,
|
||||
"lower_name": "owner",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1783411566,
|
||||
"id": 3,
|
||||
"is_active": 1,
|
||||
"is_admin": 1,
|
||||
"last_login_unix": 0,
|
||||
"lower_name": "gito",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1785078826,
|
||||
"id": 42,
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"last_login_unix": 1785206253,
|
||||
"lower_name": "codex",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
},
|
||||
{
|
||||
"created_unix": 1785078882,
|
||||
"id": 43,
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"last_login_unix": 1785206380,
|
||||
"lower_name": "gpt",
|
||||
"prohibit_login": 0,
|
||||
"type": 0
|
||||
}
|
||||
],
|
||||
"reviewable_pre_migration_protection_snapshot": {
|
||||
"protected_branch": [
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 3,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
{
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[42,43]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "main",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 0,
|
||||
"enable_whitelist": 0,
|
||||
"id": 2,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 2,
|
||||
"status_check_contexts": "[]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785093422,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
}
|
||||
],
|
||||
"protected_tag": []
|
||||
},
|
||||
"rollback_archive_members": {
|
||||
"pre-migration-backup/app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
|
||||
"pre-migration-backup/authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
|
||||
"pre-migration-backup/authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
|
||||
"pre-migration-backup/backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
|
||||
"pre-migration-backup/checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
|
||||
"pre-migration-backup/gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
|
||||
"pre-migration-backup/gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
|
||||
"pre-migration-backup/gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
|
||||
"pre-migration-backup/governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
|
||||
"pre-migration-backup/protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
|
||||
"pre-migration-backup/protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"pre-migration-backup/repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
|
||||
"pre-migration-backup/repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
|
||||
"pre-migration-backup/repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
|
||||
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
|
||||
"pre-migration-backup/repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
|
||||
},
|
||||
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
|
||||
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
|
||||
"rollback_restores_protection_before_validator": true
|
||||
},
|
||||
"auto_merge_after_owner_approval": false,
|
||||
"auto_merge_machine_conditions": [
|
||||
"owner_approval_valid",
|
||||
"head_oid_unchanged",
|
||||
"required_checks_pass",
|
||||
"secret_scan_pass",
|
||||
"evidence_closure_pass",
|
||||
"approval_not_expired",
|
||||
"protected_branch_rules_pass"
|
||||
],
|
||||
"automatic_renewal_policy": {
|
||||
"approval_ref_fast_forward_descendant_required": true,
|
||||
"changed_condition_fallback": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"secret_scan": "PASS_REQUIRED",
|
||||
"single_renewal_max_hours": 12,
|
||||
"status": "PROPOSED_AFTER_MIGRATION",
|
||||
"total_window_max_days": 7,
|
||||
"unchanged_fields_required": [
|
||||
"original_policy_sha256",
|
||||
"allowed_paths",
|
||||
"allowed_operations",
|
||||
"denied_operations",
|
||||
"source_sha256",
|
||||
"source_blob_oid"
|
||||
]
|
||||
},
|
||||
"break_glass_executor_may_merge_only_after_owner_approval_machine_gates_and_atomic_cutover_preconditions": true,
|
||||
"break_glass_expires_at": "2026-07-29T16:22:46Z",
|
||||
"desired_branch_protection": {
|
||||
"approval_whitelist_user_ids": [
|
||||
2
|
||||
],
|
||||
"approval_whitelist_usernames": [
|
||||
"owner"
|
||||
],
|
||||
"block_on_official_review_requests": true,
|
||||
"block_on_outdated_branch": true,
|
||||
"block_on_rejected_reviews": true,
|
||||
"branch_deletion": false,
|
||||
"branch_patterns": [
|
||||
"governance/approvals",
|
||||
"main",
|
||||
"release/**",
|
||||
"integration/**"
|
||||
],
|
||||
"direct_push": false,
|
||||
"dismiss_stale_approvals": true,
|
||||
"force_push": false,
|
||||
"require_status_checks": true,
|
||||
"required_approvals": 1,
|
||||
"required_status_contexts": [
|
||||
"aios/machine-gates",
|
||||
"aios/secret-scan",
|
||||
"aios/evidence-closure"
|
||||
],
|
||||
"wip_blocks_merge": true
|
||||
},
|
||||
"executor_must_not_approve": true,
|
||||
"expected_governance_base_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
|
||||
"expires_at": "2026-07-29T16:22:46Z",
|
||||
"global_constraints": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"approval_impersonation": false,
|
||||
"direct_main_push": false,
|
||||
"fake_review": false,
|
||||
"force_push": false,
|
||||
"production_change_without_owner_approval": false,
|
||||
"secret_commit": false,
|
||||
"tag_rewrite": false
|
||||
},
|
||||
"governance_model": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"historical_policy": {
|
||||
"historical_manifests_modified": false,
|
||||
"old_policy_decoder_retained_read_only": true,
|
||||
"requests_created_before_policy_effective_at_use_old_policy": true
|
||||
},
|
||||
"issued_at": "2026-07-28T16:22:46Z",
|
||||
"machine_gates": {
|
||||
"development_branch": [
|
||||
"allowed_branch",
|
||||
"allowed_paths",
|
||||
"compileall",
|
||||
"targeted_tests",
|
||||
"secret_scan",
|
||||
"git_diff_check"
|
||||
],
|
||||
"production": [
|
||||
"owner_approval",
|
||||
"tag_consistency",
|
||||
"registry_consistency",
|
||||
"deployment_plan",
|
||||
"rollback_plan",
|
||||
"artifact_integrity",
|
||||
"production_preflight"
|
||||
],
|
||||
"protected_branch_merge": [
|
||||
"expected_base_oid",
|
||||
"current_head_oid",
|
||||
"ordinary_merge_commit",
|
||||
"parent_order_verified",
|
||||
"artifact_binding",
|
||||
"audit_chain_valid"
|
||||
],
|
||||
"release": [
|
||||
"version_manifest",
|
||||
"explicit_release_target_oid",
|
||||
"artifact_sha256",
|
||||
"rollback_artifact_sha256",
|
||||
"sbom",
|
||||
"test_evidence",
|
||||
"evidence_closure"
|
||||
]
|
||||
},
|
||||
"merge_mode": "CREATE_MERGE_COMMIT",
|
||||
"migration_acceptance_tests": [
|
||||
"agent_request_pass",
|
||||
"owner_current_head_approval_pass",
|
||||
"machine_gates_then_merge_commit_pass",
|
||||
"agent_self_approval_denied",
|
||||
"non_owner_approval_denied",
|
||||
"stale_head_approval_denied",
|
||||
"wip_merge_denied",
|
||||
"machine_test_failure_merge_denied",
|
||||
"secret_scan_failure_merge_denied",
|
||||
"main_direct_write_denied",
|
||||
"tag_direct_write_denied",
|
||||
"force_push_denied",
|
||||
"missing_tag_target_oid_denied",
|
||||
"missing_evidence_closure_oid_denied",
|
||||
"production_without_owner_denied",
|
||||
"dormant_only_production_activation_denied"
|
||||
],
|
||||
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET",
|
||||
"new_policy": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"old_policy": "DUAL_HUMAN_APPROVAL",
|
||||
"owner_approval_contract": {
|
||||
"dismissed": false,
|
||||
"does_not_claim_old_dual_approval_compliance": true,
|
||||
"head_change_invalidates_owner_approval": true,
|
||||
"owner_review_is_break_glass_signature": true,
|
||||
"review_created_before_approval_expires_at": true,
|
||||
"review_state": "APPROVED",
|
||||
"reviewed_head_oid_must_equal_current_pr_head_oid": true,
|
||||
"reviewer_must_equal": "owner",
|
||||
"stale": false
|
||||
},
|
||||
"owner_break_glass_declaration_to_be_attested_by_review": {
|
||||
"attestation_status": "PENDING_OWNER_REVIEW",
|
||||
"expires_at": "2026-07-29T16:22:46Z",
|
||||
"new_policy": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
|
||||
"old_policy": "DUAL_HUMAN_APPROVAL",
|
||||
"owner_acknowledges_old_policy_exception": true,
|
||||
"reason": "single-maintainer-system",
|
||||
"scope": "governance-policy-migration-only"
|
||||
},
|
||||
"owner_is_only_human_approver": true,
|
||||
"policy_effective_at": null,
|
||||
"pre_approval_non_authorizations": {
|
||||
"approval_validator_update_allowed": false,
|
||||
"gitea_branch_protection_update_allowed": false,
|
||||
"merge_allowed": false,
|
||||
"old_dual_rule_disable_allowed": false,
|
||||
"production_change_allowed": false,
|
||||
"state_machine_update_allowed": false,
|
||||
"writer_credential_issue_allowed": false
|
||||
},
|
||||
"prohibited_merge_modes": [
|
||||
"squash",
|
||||
"rebase",
|
||||
"force",
|
||||
"fast_forward_policy_bypass"
|
||||
],
|
||||
"reason": "single-maintainer-system",
|
||||
"repository": "aios/aios",
|
||||
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
|
||||
"requester_must_not_approve": true,
|
||||
"requesters": [
|
||||
"codex-chartgpt-bridge",
|
||||
"aios-governance-agent",
|
||||
"aios-release-agent",
|
||||
"aios-governed-development-writer"
|
||||
],
|
||||
"required_distinct_human_approvers": 1,
|
||||
"required_human_approvals": 1,
|
||||
"required_owner_approvals": 1,
|
||||
"resolved_owner": {
|
||||
"confidence_before_review": "MEDIUM",
|
||||
"confirmation_rule": "valid APPROVED review by owner on this exact immutable head",
|
||||
"email_sha256": "f5dc2928f6202c24fa4db8bb33db84e1b77c6eff30d1b95abdadeaae209bc4c9",
|
||||
"identity_source": [
|
||||
"gitea_repository_owner_account_aios_is_login_prohibited",
|
||||
"all_login_enabled_site_admins_enumerated_owner_and_never_logged_in_service_gito",
|
||||
"only_interactive_site_admin_named_owner",
|
||||
"gitea_owner_last_login_present",
|
||||
"historical_governance_records_use_approved_by_role_owner",
|
||||
"user_declared_single_natural_person_maintainer"
|
||||
],
|
||||
"login_enabled_site_admins": [
|
||||
{
|
||||
"observed_prior_login": true,
|
||||
"user_id": 2,
|
||||
"username": "owner"
|
||||
},
|
||||
{
|
||||
"classification": "service_admin_not_human_approver",
|
||||
"observed_prior_login": false,
|
||||
"user_id": 3,
|
||||
"username": "gito"
|
||||
}
|
||||
],
|
||||
"user_id": 2,
|
||||
"username": "owner"
|
||||
},
|
||||
"schema": "aios.single-owner-governance-policy/v1",
|
||||
"scope": "governance-policy-migration-only",
|
||||
"standing_autonomous_development_policy": {
|
||||
"allowed_without_repeated_owner_approval": [
|
||||
"patch_branch_create",
|
||||
"allowlisted_development_write",
|
||||
"patch_branch_commit_and_push",
|
||||
"test_execute",
|
||||
"artifact_build",
|
||||
"approval_pr_create",
|
||||
"read_only_evidence",
|
||||
"task_writer_renewal"
|
||||
],
|
||||
"owner_approval_still_required": [
|
||||
"protected_branch_merge",
|
||||
"tag",
|
||||
"registry_update",
|
||||
"production_baseline_update",
|
||||
"production_deploy",
|
||||
"production_rollback",
|
||||
"governance_policy_change"
|
||||
],
|
||||
"status": "PROPOSED_AFTER_MIGRATION"
|
||||
},
|
||||
"standing_single_owner_governance_migration_authorization": true,
|
||||
"state_machine": {
|
||||
"auto_merge_ready_state": "AUTO_MERGE_READY",
|
||||
"execution_ready_state": "OWNER_APPROVED_EXECUTION_READY",
|
||||
"machine_verified_state": "MACHINE_GATES_VERIFIED",
|
||||
"owner_approval_alone_is_not_execution_ready": true,
|
||||
"owner_verified_state": "OWNER_APPROVAL_VERIFIED",
|
||||
"pending_state": "HUMAN_OWNER_APPROVAL_PENDING"
|
||||
},
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729",
|
||||
"this_manifest_authorizes_only_governance_policy_migration": true,
|
||||
"this_manifest_grants_standing_development_authorization": false
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"approval_type": "IMPLEMENTATION",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"repository": "aios/aios",
|
||||
"branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer",
|
||||
"risk": "high",
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_operations": [
|
||||
"repository.source_map.read",
|
||||
"runtime.unit_metadata.read",
|
||||
"git.ref.read",
|
||||
"git.diff.read",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback",
|
||||
"secret.read",
|
||||
"secret.export",
|
||||
"bare_ref_direct_write"
|
||||
],
|
||||
"expires_at": "2026-07-27T03:35:55Z"
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"approval_type": "IMPLEMENTATION",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"repository": "aios/aios",
|
||||
"branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer",
|
||||
"risk": "high",
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_operations": [
|
||||
"repository.source_map.read",
|
||||
"runtime.unit_metadata.read",
|
||||
"git.ref.read",
|
||||
"git.diff.read",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback",
|
||||
"secret.read",
|
||||
"secret.export",
|
||||
"bare_ref_direct_write"
|
||||
],
|
||||
"expires_at": "2026-07-27T13:00:11Z"
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION",
|
||||
"artifact_sha256": "01e0226f0302bb8e2f8d81299393075bf0332c6e22e00877469fb297c51f3802",
|
||||
"authorization_scope": [
|
||||
"deploy_live_activation_implementation",
|
||||
"execute_one_atomic_governance_activation",
|
||||
"execute_acceptance_tests",
|
||||
"control_plane_rollback_on_failure",
|
||||
"write_activation_receipt"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite"
|
||||
],
|
||||
"evidence_closure_oid": "c6316943d2485f06f97242df6338ded21de1ee38",
|
||||
"evidence_closure_sha256": "bd521103c907d6194acbbbd42a947c5af2ccbda91797e5dc75e81887a042becd",
|
||||
"execution_binding": {
|
||||
"backend": "sqlite",
|
||||
"branch": "governance/approvals",
|
||||
"evidence_closure_oid": "c6316943d2485f06f97242df6338ded21de1ee38",
|
||||
"executor": "aios-governance-single-owner-activation",
|
||||
"expected_branch_protection_sha256": "f27992c8024426708d93bce072d231707bae47a927aa2393d566aae64d5ed079",
|
||||
"expected_gitea_schema_sha256": "3524f4912165939cb746c2c65d70583a909f5abbd00c4e07ae76f7206d49aa54",
|
||||
"expected_state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"expected_validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"forbidden_operations": {
|
||||
"baseline": false,
|
||||
"force_push": false,
|
||||
"governance_direct": false,
|
||||
"history_rewrite": false,
|
||||
"integration": false,
|
||||
"main": false,
|
||||
"production": false,
|
||||
"registry": false,
|
||||
"tags": false
|
||||
},
|
||||
"operation_id": "single-owner-live-activation-20260729T033228Z",
|
||||
"repo_id": 1,
|
||||
"requester": "codex-agent",
|
||||
"target_branch_protection_sha256": "61b424b0c495eed71c73171c5c4434ac66d074de36886a73ec4c445fd1905db8",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729"
|
||||
},
|
||||
"execution_binding_sha256": "6b2ed671a94fa83704383728fd2bb2df3b67523b4273100fa371777d984be102",
|
||||
"expected_governance_base_oid": "8b7f9d9028f6ff9beb225bf85f53dbeaac6830e7",
|
||||
"expires_at": "2026-07-29T15:32:28Z",
|
||||
"human_authorizer": "owner",
|
||||
"implementation_commit_oid": "e1e438564ce8b987a36df0947e6eaa324dfede74",
|
||||
"implementation_tree_oid": "dd93dc1df10cb8b8fe59b09d3214a9ae6fee671e",
|
||||
"issued_at": "2026-07-29T03:32:28Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"machine_gate_contexts": [
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator"
|
||||
],
|
||||
"pr30_merge_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
|
||||
"pr31_merge_oid": "8b7f9d9028f6ff9beb225bf85f53dbeaac6830e7",
|
||||
"rollback_artifact_sha256": "ed9307cc2f07767b75866fcb370fa3f98b76c5147e4b7fc49ecf04cf21cf7c31",
|
||||
"signed_merge_requirement_effective": false,
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
|
||||
"test_evidence_sha256": "172a7cf812c6b901524138fb72851a9b7d2e895dedc98893407d0bab9b52c6c1"
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"approval_type": "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION",
|
||||
"artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"artifact_sha256": "32bc49c207ec454b6fd81b0484f5b257bb789481b37175995029a2ec78cc62fa",
|
||||
"authorization_scope": [
|
||||
"deploy_exact_approved_artifact",
|
||||
"publish_exact_head_machine_statuses",
|
||||
"execute_one_atomic_governance_activation",
|
||||
"verify_and_rollback_exact_transaction"
|
||||
],
|
||||
"denied_operations": [
|
||||
"main.write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"registry.write",
|
||||
"baseline.write",
|
||||
"production.deploy",
|
||||
"force_push",
|
||||
"history_rewrite"
|
||||
],
|
||||
"evidence_closure_blob_oid": "6af6d017f22c4e0c512b0cd62644cdc6ab2135d5",
|
||||
"evidence_closure_oid": "310ab2af902a9fde9108d227077f433cc743ce7c",
|
||||
"evidence_closure_path": "evidence/single-owner-governance/AIOS-SINGLE-OWNER-LIVE-ACTIVATION-HARDENING-20260729T043010Z.json",
|
||||
"evidence_closure_sha256": "e482fe52c8381f765ee508d2de1fe2e77dff52e6e342f01be4ca481ff2f76820",
|
||||
"execution_binding": {
|
||||
"artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"artifact_sha256": "32bc49c207ec454b6fd81b0484f5b257bb789481b37175995029a2ec78cc62fa",
|
||||
"backend": "sqlite",
|
||||
"branch": "governance/approvals",
|
||||
"evidence_closure_oid": "310ab2af902a9fde9108d227077f433cc743ce7c",
|
||||
"evidence_closure_path": "evidence/single-owner-governance/AIOS-SINGLE-OWNER-LIVE-ACTIVATION-HARDENING-20260729T043010Z.json",
|
||||
"evidence_closure_sha256": "e482fe52c8381f765ee508d2de1fe2e77dff52e6e342f01be4ca481ff2f76820",
|
||||
"executor": "aios-governance-single-owner-activation",
|
||||
"expected_branch_protection_sha256": "f27992c8024426708d93bce072d231707bae47a927aa2393d566aae64d5ed079",
|
||||
"expected_gitea_schema_sha256": "3524f4912165939cb746c2c65d70583a909f5abbd00c4e07ae76f7206d49aa54",
|
||||
"expected_state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
|
||||
"expected_validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
|
||||
"forbidden_operations": {
|
||||
"baseline": false,
|
||||
"force_push": false,
|
||||
"governance_direct": false,
|
||||
"history_rewrite": false,
|
||||
"integration": false,
|
||||
"main": false,
|
||||
"production": false,
|
||||
"registry": false,
|
||||
"tags": false
|
||||
},
|
||||
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
|
||||
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
|
||||
"machine_status_producer_id": 13,
|
||||
"machine_status_producer_name": "gitops-bot",
|
||||
"operation_id": "single-owner-live-activation-hardening-20260729T043149Z",
|
||||
"repo_id": 1,
|
||||
"requester": "codex-agent",
|
||||
"rollback_artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-rollback-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"rollback_artifact_sha256": "4efe1ea73d4fd72e5364fc91a16ad5e9d1e6ebe78d8aa466f7b11c4915b08403",
|
||||
"runtime_sources": [
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"sha256": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"sha256": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"sha256": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
|
||||
}
|
||||
],
|
||||
"target_branch_protection_sha256": "4713f0494b79103668fbe5bd8fd7da5d60a43e56fb545aa6e7c773f43dc07c10",
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
|
||||
"test_evidence_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/test-evidence-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.json",
|
||||
"test_evidence_sha256": "c1633732767f3f051ffb01404d12d05a6168a4b01b91efdb34a64988d6dee684"
|
||||
},
|
||||
"execution_binding_sha256": "63705dd0f5cc25debde132179f96819a844a9703b33c092db4ac48d5680d536a",
|
||||
"expected_governance_base_oid": "1071979079deab8a4028473d967c2a205c3b111e",
|
||||
"expires_at": "2026-07-29T16:31:49Z",
|
||||
"human_authorizer": "owner",
|
||||
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
|
||||
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
|
||||
"issued_at": "2026-07-29T04:31:49Z",
|
||||
"legacy_mechanical_approvals": [
|
||||
"codex",
|
||||
"gpt"
|
||||
],
|
||||
"machine_gate_contexts": [
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator"
|
||||
],
|
||||
"machine_status_producer_id": 13,
|
||||
"machine_status_producer_name": "gitops-bot",
|
||||
"rollback_artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-rollback-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
|
||||
"rollback_artifact_sha256": "4efe1ea73d4fd72e5364fc91a16ad5e9d1e6ebe78d8aa466f7b11c4915b08403",
|
||||
"runtime_sources": [
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
|
||||
"sha256": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
|
||||
"sha256": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
|
||||
"sha256": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
|
||||
"sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e"
|
||||
},
|
||||
{
|
||||
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
|
||||
"sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
|
||||
}
|
||||
],
|
||||
"target_branch_protection": {
|
||||
"approvals_whitelist_team_i_ds": "null",
|
||||
"approvals_whitelist_user_i_ds": "[2]",
|
||||
"block_on_official_review_requests": 1,
|
||||
"block_on_outdated_branch": 1,
|
||||
"block_on_rejected_reviews": 1,
|
||||
"branch_name": "governance/approvals",
|
||||
"can_push": 0,
|
||||
"created_unix": 1785093201,
|
||||
"dismiss_stale_approvals": 1,
|
||||
"enable_approvals_whitelist": 1,
|
||||
"enable_merge_whitelist": 1,
|
||||
"enable_status_check": 1,
|
||||
"enable_whitelist": 0,
|
||||
"id": 1,
|
||||
"ignore_stale_approvals": 0,
|
||||
"merge_whitelist_team_i_ds": "null",
|
||||
"merge_whitelist_user_i_ds": "[42,43]",
|
||||
"protected_file_patterns": "",
|
||||
"repo_id": 1,
|
||||
"require_signed_commits": 0,
|
||||
"required_approvals": 1,
|
||||
"status_check_contexts": "[\"aios/branch-protection\",\"aios/evidence-closure\",\"aios/secret-scan\",\"aios/state-machine\",\"aios/validator\"]",
|
||||
"unprotected_file_patterns": "",
|
||||
"updated_unix": 1785299509,
|
||||
"whitelist_deploy_keys": 0,
|
||||
"whitelist_team_i_ds": "null",
|
||||
"whitelist_user_i_ds": "null"
|
||||
},
|
||||
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
|
||||
"test_evidence_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/test-evidence-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.json",
|
||||
"test_evidence_sha256": "c1633732767f3f051ffb01404d12d05a6168a4b01b91efdb34a64988d6dee684"
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"allowed_operations": [
|
||||
"runtime.source.read.exact",
|
||||
"runtime.source.hash",
|
||||
"runtime.unit_metadata.read",
|
||||
"package.ownership.read",
|
||||
"git.object.lookup.by_blob_oid",
|
||||
"secret.scan.local_source",
|
||||
"repository.source_import.byte_exact",
|
||||
"repository.module.create",
|
||||
"source_map.evidence.write",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/aios_autonomous_release_authorization/**",
|
||||
"active-runtime/app/aios_chatgpt_action_task_router/**",
|
||||
"active-runtime/app/aios_isolated_writer_api/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_runtime_reads": [
|
||||
"/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py"
|
||||
],
|
||||
"approval_type": "SOURCE_MAPPING_AND_IMPORT",
|
||||
"canonical_paths": {
|
||||
"controller": "active-runtime/app/aios_autonomous_release_authorization/controller.py",
|
||||
"isolated_writer_api": "active-runtime/app/aios_isolated_writer_api/**",
|
||||
"router": "active-runtime/app/aios_chatgpt_action_task_router/action_task_router.py"
|
||||
},
|
||||
"denied_operations": [
|
||||
"secret.value.return",
|
||||
"secret.matching_line.return",
|
||||
"secret.context.return",
|
||||
"runtime.source.modify",
|
||||
"runtime.unit.modify",
|
||||
"arbitrary_path.read",
|
||||
"secret.read.by_agent",
|
||||
"secret.export",
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"governance.approvals.direct_write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"bare_ref_direct_write",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback"
|
||||
],
|
||||
"expires_at": "2026-07-27T22:36:27Z",
|
||||
"extends_approval_merge_oid": "45f3226662965c8d41ec1775e8c8857f2b986397",
|
||||
"finding_policy": {
|
||||
"controller_byte_exact_import_allowed": true,
|
||||
"controller_finding_status": "PASS",
|
||||
"controller_sanitized_source_creation_allowed": false,
|
||||
"finding_classification_by_automation": "PROHIBITED",
|
||||
"isolated_writer_api_module_create_allowed": true,
|
||||
"matching_source_lines_disclosed": false,
|
||||
"router_byte_exact_import_allowed": true,
|
||||
"router_finding_status": "PASS",
|
||||
"router_sanitized_source_creation_allowed": false,
|
||||
"scan_decision": "MAPPING_IMPORT_ONLY",
|
||||
"secret_content_disclosed": false
|
||||
},
|
||||
"implementation_base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"issued_at": "2026-07-27T10:36:27Z",
|
||||
"repository": "aios/aios",
|
||||
"required_controls": {
|
||||
"controller_finding_must_be_resolved": false,
|
||||
"controller_import_blob_must_equal_runtime_blob": true,
|
||||
"controller_import_must_be_byte_exact": true,
|
||||
"controller_import_sha256_must_equal_runtime_sha256": true,
|
||||
"dual_human_approval_required": true,
|
||||
"global_gitops_writes_must_remain_disabled": true,
|
||||
"new_task_bound_grant_required": true,
|
||||
"old_implementation_token_auth_status": 401,
|
||||
"old_implementation_writer_revoked": true,
|
||||
"old_secret_revocation_must_be_verified": false,
|
||||
"router_finding_must_be_resolved": false,
|
||||
"router_import_blob_must_equal_runtime_blob": true,
|
||||
"router_import_must_be_byte_exact": true,
|
||||
"router_import_sha256_must_equal_runtime_sha256": true,
|
||||
"sanitized_source_behavioral_equivalence_required": false,
|
||||
"sanitized_source_secret_scan_must_pass": false,
|
||||
"true_positive_requires_rotation": false
|
||||
},
|
||||
"risk": "high",
|
||||
"runtime_source_evidence": {
|
||||
"controller": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
|
||||
"group_gid": 0,
|
||||
"mode": "0o700",
|
||||
"mtime_ns": 1785043975531298047,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-autonomous-release-authorization-controller.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "39b532c3058c4ae49ed691d7c66c3b7745a25111fa61039f388c8335fd262c50",
|
||||
"size": 24204,
|
||||
"utf8_python": true
|
||||
},
|
||||
"isolated_writer_api": {
|
||||
"canonical_source_path": "NOT_ESTABLISHED",
|
||||
"runtime_module_status": "ABSENT"
|
||||
},
|
||||
"router": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
|
||||
"group_gid": 0,
|
||||
"mode": "0o644",
|
||||
"mtime_ns": 1785042222580725999,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-chatgpt-autonomous-release-route.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "738fb7035a6b792115d6467b4d1e4ffd47a8c5d5646f7032ebc68ea587cbd554",
|
||||
"size": 25952,
|
||||
"utf8_python": true
|
||||
}
|
||||
},
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer"
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"actor_identity": "chatgpt-gitops-bot",
|
||||
"allowed_operations": [
|
||||
"runtime.source.read.exact",
|
||||
"runtime.source.hash",
|
||||
"runtime.unit_metadata.read",
|
||||
"package.ownership.read",
|
||||
"git.object.lookup.by_blob_oid",
|
||||
"secret.scan.local_source",
|
||||
"repository.source_import.byte_exact",
|
||||
"repository.module.create",
|
||||
"source_map.evidence.write",
|
||||
"git.branch.patch.create",
|
||||
"commit_file.atomic",
|
||||
"git.push.patch_branch",
|
||||
"test.execute.allowlisted",
|
||||
"artifact.build.control_plane_patch",
|
||||
"evidence.write.task_runtime"
|
||||
],
|
||||
"allowed_paths": [
|
||||
"active-runtime/app/aios_autonomous_release_authorization/**",
|
||||
"active-runtime/app/aios_chatgpt_action_task_router/**",
|
||||
"active-runtime/app/aios_isolated_writer_api/**",
|
||||
"tests/test_integration_refresh_provisioner.py",
|
||||
"tests/test_isolated_writer_integration_merge.py",
|
||||
"tests/test_gitea_branch_read_projection.py",
|
||||
"tests/test_isolated_writer_token_policy_v2.py",
|
||||
"deploy/systemd/aios-integration-refresh-*.service",
|
||||
"docs/autonomous-release/**"
|
||||
],
|
||||
"allowed_runtime_reads": [
|
||||
"/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py"
|
||||
],
|
||||
"approval_type": "SOURCE_MAPPING_AND_IMPORT_EXECUTION_RENEWAL",
|
||||
"canonical_paths": {
|
||||
"controller": "active-runtime/app/aios_autonomous_release_authorization/controller.py",
|
||||
"isolated_writer_api": "active-runtime/app/aios_isolated_writer_api/**",
|
||||
"router": "active-runtime/app/aios_chatgpt_action_task_router/action_task_router.py"
|
||||
},
|
||||
"denied_operations": [
|
||||
"secret.value.return",
|
||||
"secret.matching_line.return",
|
||||
"secret.context.return",
|
||||
"runtime.source.modify",
|
||||
"runtime.unit.modify",
|
||||
"arbitrary_path.read",
|
||||
"secret.read.by_agent",
|
||||
"secret.export",
|
||||
"main.write",
|
||||
"main.merge",
|
||||
"governance.approvals.direct_write",
|
||||
"integration.write",
|
||||
"tag.write",
|
||||
"tag.delete",
|
||||
"tag.rewrite",
|
||||
"force_push",
|
||||
"history.rewrite",
|
||||
"bare_ref_direct_write",
|
||||
"baseline.write",
|
||||
"registry.write",
|
||||
"production.deploy",
|
||||
"production.rollback"
|
||||
],
|
||||
"expected_governance_base_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6",
|
||||
"expires_at": "2026-07-28T18:10:44Z",
|
||||
"extends_approval_merge_oid": "45f3226662965c8d41ec1775e8c8857f2b986397",
|
||||
"finding_policy": {
|
||||
"controller_byte_exact_import_allowed": true,
|
||||
"controller_finding_status": "PASS",
|
||||
"controller_sanitized_source_creation_allowed": false,
|
||||
"finding_classification_by_automation": "PROHIBITED",
|
||||
"isolated_writer_api_module_create_allowed": true,
|
||||
"matching_source_lines_disclosed": false,
|
||||
"router_byte_exact_import_allowed": true,
|
||||
"router_finding_status": "PASS",
|
||||
"router_sanitized_source_creation_allowed": false,
|
||||
"scan_decision": "MAPPING_IMPORT_ONLY",
|
||||
"secret_content_disclosed": false
|
||||
},
|
||||
"implementation_base_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
|
||||
"issued_at": "2026-07-28T06:10:44Z",
|
||||
"renews_source_mapping_approval_merge_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6",
|
||||
"renews_source_mapping_manifest_blob_oid": "00c5e90e529cb98c7750a5761b0a0d6ccf507174",
|
||||
"renews_source_mapping_manifest_sha256": "17af8ebc43673c0e46bb9ab1bf44497e8c147f1b4a9d7a8a404edd5c0abd6f21",
|
||||
"repository": "aios/aios",
|
||||
"required_controls": {
|
||||
"controller_finding_must_be_resolved": false,
|
||||
"controller_import_blob_must_equal_runtime_blob": true,
|
||||
"controller_import_must_be_byte_exact": true,
|
||||
"controller_import_sha256_must_equal_runtime_sha256": true,
|
||||
"dual_human_approval_required": true,
|
||||
"global_gitops_writes_must_remain_disabled": true,
|
||||
"new_task_bound_grant_required": true,
|
||||
"old_implementation_token_auth_status": 401,
|
||||
"old_implementation_writer_revoked": true,
|
||||
"old_secret_revocation_must_be_verified": false,
|
||||
"router_finding_must_be_resolved": false,
|
||||
"router_import_blob_must_equal_runtime_blob": true,
|
||||
"router_import_must_be_byte_exact": true,
|
||||
"router_import_sha256_must_equal_runtime_sha256": true,
|
||||
"sanitized_source_behavioral_equivalence_required": false,
|
||||
"sanitized_source_secret_scan_must_pass": false,
|
||||
"true_positive_requires_rotation": false
|
||||
},
|
||||
"risk": "high",
|
||||
"runtime_source_evidence": {
|
||||
"controller": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
|
||||
"group_gid": 0,
|
||||
"mode": "0o700",
|
||||
"mtime_ns": 1785043975531298047,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-autonomous-release-authorization-controller.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-autonomous-release-authorization/controller.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "39b532c3058c4ae49ed691d7c66c3b7745a25111fa61039f388c8335fd262c50",
|
||||
"size": 24204,
|
||||
"utf8_python": true
|
||||
},
|
||||
"isolated_writer_api": {
|
||||
"canonical_source_path": "NOT_ESTABLISHED",
|
||||
"runtime_module_status": "ABSENT"
|
||||
},
|
||||
"observed_at": "2026-07-28T05:53:01.253909Z",
|
||||
"router": {
|
||||
"finding_count": 0,
|
||||
"findings": [],
|
||||
"git_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
|
||||
"group_gid": 0,
|
||||
"mode": "0o644",
|
||||
"mtime_ns": 1785042222580725999,
|
||||
"owner_uid": 0,
|
||||
"package_owner": "UNPACKAGED",
|
||||
"referencing_systemd_units": [
|
||||
"aios-chatgpt-autonomous-release-route.service"
|
||||
],
|
||||
"resolved_path": "/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py",
|
||||
"secret_content_returned": false,
|
||||
"secret_scan": "PASS",
|
||||
"sha256": "738fb7035a6b792115d6467b4d1e4ffd47a8c5d5646f7032ebc68ea587cbd554",
|
||||
"size": 25952,
|
||||
"utf8_python": true
|
||||
}
|
||||
},
|
||||
"runtime_source_read_projection_evidence_sha256": "23f03d9c6abe6f11e1acda5210b8708696e3b77caa0083b6bfaef30a44b2a35f",
|
||||
"schema": "aios.gitea-native-approval/v1",
|
||||
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
|
||||
"writer_audience": "aios-integration-refresh-provisioner-development-writer"
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
[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
|
||||
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=AIOS root task credential broker (%i)
|
||||
ConditionPathExists=/var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
Group=root
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_credential_broker --grant /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers --audit-key %d/audit-key --audit-dir /var/lib/aios-gitops/runtime/task-status-merge/%i/audit --request /var/lib/aios-gitops/runtime/task-status-merge/%i/credential-request.json
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=false
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadOnlyPaths=/data/aios-gitops/runtime/bin/gitea /data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig /var/lib/aios-gitops/runtime/task-status-merge/%i/credential-request.json /etc/aios-gitops/task-status-merge/allowed_signers /data/aios-gitops/runtime/gitea-bin/data/actions_artifacts /data/aios-gitops/runtime/gitea-bin/data/actions_log /data/aios-gitops/runtime/gitea-bin/data/attachments /data/aios-gitops/runtime/gitea-bin/data/avatars /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak.20260708133830 /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak-20260710-0105 /data/aios-gitops/runtime/gitea-bin/data/home /data/aios-gitops/runtime/gitea-bin/data/indexers /data/aios-gitops/runtime/gitea-bin/data/jwt /data/aios-gitops/runtime/gitea-bin/data/packages /data/aios-gitops/runtime/gitea-bin/data/queues /data/aios-gitops/runtime/gitea-bin/data/repo-archive /data/aios-gitops/runtime/gitea-bin/data/repo-avatars /data/aios-gitops/runtime/gitea-bin/data/repositories /data/aios-gitops/runtime/gitea-bin/data/tmp
|
||||
ReadWritePaths=/data/aios-gitops/runtime/gitea-bin/data /run/aios-task-credential-broker /var/lib/aios-gitops/runtime/task-status-merge/%i/audit
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,23 @@
|
||||
[Unit]
|
||||
Description=AIOS task credential expiry and completion sweeper
|
||||
After=aios-gitea.service
|
||||
Requires=aios-gitea.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
Group=root
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_credential_expiry_sweeper --runtime-root /var/lib/aios-gitops/runtime/task-status-merge --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadOnlyPaths=/etc/aios-gitops/task-status-merge/allowed_signers /data/aios-gitops/runtime/gitea-bin/data/actions_artifacts /data/aios-gitops/runtime/gitea-bin/data/actions_log /data/aios-gitops/runtime/gitea-bin/data/attachments /data/aios-gitops/runtime/gitea-bin/data/avatars /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak.20260708133830 /data/aios-gitops/runtime/gitea-bin/data/gitea.db.bak-20260710-0105 /data/aios-gitops/runtime/gitea-bin/data/home /data/aios-gitops/runtime/gitea-bin/data/indexers /data/aios-gitops/runtime/gitea-bin/data/jwt /data/aios-gitops/runtime/gitea-bin/data/packages /data/aios-gitops/runtime/gitea-bin/data/queues /data/aios-gitops/runtime/gitea-bin/data/repo-archive /data/aios-gitops/runtime/gitea-bin/data/repo-avatars /data/aios-gitops/runtime/gitea-bin/data/repositories /data/aios-gitops/runtime/gitea-bin/data/tmp
|
||||
ReadWritePaths=/data/aios-gitops/runtime/gitea-bin/data /run/aios-task-credential-broker /var/lib/aios-gitops/runtime/task-status-merge
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Run AIOS task credential expiry sweeper every minute
|
||||
|
||||
[Timer]
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=1min
|
||||
AccuracySec=5s
|
||||
Persistent=true
|
||||
Unit=aios-task-credential-expiry-sweeper.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=AIOS task-scoped Gitea fast-forward-only merge broker (%i)
|
||||
After=network.target aios-gitea.service aios-task-status-broker@%i.service
|
||||
Requires=aios-gitea.service aios-task-status-broker@%i.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=aios-task-merge-bot
|
||||
Group=aios-task-merge-bot
|
||||
LoadCredentialEncrypted=gitea-token:/run/aios-task-credential-broker/%i-merge.cred
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_status_merge_broker --mode merge --grant /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers --token %d/gitea-token --audit-key %d/audit-key --journal /var/lib/aios-gitops/runtime/task-status-merge/%i/merge-journal/journal.sqlite3
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
ReadOnlyPaths=/var/lib/aios-gitops/runtime/task-status-merge/%i /etc/aios-gitops/task-status-merge/allowed_signers
|
||||
ReadWritePaths=/var/lib/aios-gitops/runtime/task-status-merge/%i/merge-journal
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=AIOS task-scoped Gitea status broker (%i)
|
||||
After=network.target aios-gitea.service
|
||||
Requires=aios-gitea.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=aios-task-status-bot
|
||||
Group=aios-task-status-bot
|
||||
LoadCredentialEncrypted=gitea-token:/run/aios-task-credential-broker/%i-status.cred
|
||||
LoadCredentialEncrypted=audit-key:/run/aios-task-credential-broker/%i-audit.cred
|
||||
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.task_status_merge_broker --mode status --grant /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.json --grant-signature /var/lib/aios-gitops/runtime/task-status-merge/%i/grant.sig --allowed-signers /etc/aios-gitops/task-status-merge/allowed_signers --evidence /var/lib/aios-gitops/runtime/task-status-merge/%i/status-evidence.json --token %d/gitea-token --audit-key %d/audit-key --journal /var/lib/aios-gitops/runtime/task-status-merge/%i/status-journal/journal.sqlite3
|
||||
Environment=AIOS_GITOPS_WRITES_ENABLED=false
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
ReadOnlyPaths=/var/lib/aios-gitops/runtime/task-status-merge/%i /etc/aios-gitops/task-status-merge/allowed_signers
|
||||
ReadWritePaths=/var/lib/aios-gitops/runtime/task-status-merge/%i/status-journal
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictSUIDSGID=true
|
||||
@@ -0,0 +1,68 @@
|
||||
# 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.
|
||||
@@ -51,6 +51,31 @@ 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:
|
||||
@@ -428,6 +453,25 @@ 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
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"task": "codex-review-test-worker",
|
||||
"writer_role": "isolated-writer",
|
||||
"status": "READY"
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
# 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.
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"evidence_type": "TWO_STAGE_OWNER_CAMPAIGN_V2_PHASE_A_CANDIDATE",
|
||||
"generated_at": "2026-07-30T02:35:51Z",
|
||||
"execution_status": "PARTIALLY_COMPLETED",
|
||||
"blocking_layer": "PHASE_A_OWNER_EXACT_HEAD_APPROVAL",
|
||||
"repository": "aios/aios",
|
||||
"governance_base_oid": "ebd74398743e36523b593fcc395b73945d4924c5",
|
||||
"governance_tree_oid": "ad1255a1127204fad1d1208d8f31dc94fa64e6e1",
|
||||
"authority_main_oid": "875d41c3101093149414292067fd700dc214d4d3",
|
||||
"schema_seed_commit_oid": "8da47545a507cb798d3d6ab30ff8552a2d0a5ecf",
|
||||
"schema_seed_tree_oid": "991e68577a469657c1c4996814b6634ce5ed752f",
|
||||
"candidate_source_commit_oid": "a637a9dbb6e0021cb7dcf0d6b0ca00dfe10dce38",
|
||||
"candidate_source_tree_oid": "bf6e0c16fcad7515465bec4aefdd2e77b1e9bef0",
|
||||
"candidate_diff_sha256": "c06d002db06da6cbf16f9c8321f1acbfd431f009b5127a13fd2cb5d63b278867",
|
||||
"candidate_files_sha256": "5061402d516c0e9413208ce64e2b52f77d224e6eec1f7b07c5b3a10b58df5eaf",
|
||||
"candidate_file_count": 44,
|
||||
"schema_document_path": "services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json",
|
||||
"schema_document_sha256": "6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd",
|
||||
"validator_policy_path": "services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py",
|
||||
"validator_policy_sha256": "de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac",
|
||||
"two_stage_policy_schema_sha256": "6b4def7c1eef19c01ef73c43252eb0cd1a87c103e1047249a3cfcc75c7922bf6",
|
||||
"two_stage_release_schema_sha256": "82ee589dc87db6998ddf0779ce264194cadea0deca66088b0797378f6ce89ad3",
|
||||
"runtime_observer_sha256": "181e632192f1417a98c1efb35631cf80246759080b2e04243576abcdc7da22af",
|
||||
"journal_anchor_sha256": "18feb0792b95f1060aa696cc60943379695a246984aecf76098838f9ed37426e",
|
||||
"package_zip_sha256": "668239a549b4a2b2094fe9b5104b84703fe8ed75011fd8ed71ed3958a6dc0706",
|
||||
"candidate_bundle_sha256": "15711b225b9ef41cca0588d21b2b95d7a95ac155efc27fcb8772c435fcdab147",
|
||||
"anchor_install_wheel_sha256": "abbf616366eb3467d4e651bb5306e22ae5e931b613df5362a0892935c4a1c0c7",
|
||||
"tests": {
|
||||
"two_stage_campaign": {"passed": 67, "failed": 0, "skipped": 0, "collection_errors": 0},
|
||||
"single_owner_governance": {"passed": 105, "failed": 0, "subtests_passed": 17, "collection_errors": 0},
|
||||
"task_scoped_bootstrap": {"passed": 15, "failed": 0, "collection_errors": 0},
|
||||
"compileall": "PASS",
|
||||
"quarantine_fsck": "PASS",
|
||||
"systemd_verify": "PASS_WITH_UNRELATED_HOST_WARNINGS"
|
||||
},
|
||||
"anchor": {
|
||||
"service_status": "active",
|
||||
"anchor_uid": 988,
|
||||
"runner_uid": 986,
|
||||
"uids_distinct": true,
|
||||
"runner_database_write": "DENIED",
|
||||
"unauthorized_socket_peer": "DENIED",
|
||||
"runner_registration_action": "ANCHOR_REQUEST_INVALID",
|
||||
"journal_mode": "WAL",
|
||||
"synchronous": "FULL",
|
||||
"foreign_keys": "ON",
|
||||
"restart_open_reservation": "PASS",
|
||||
"selftest_open_reservation_cleanup": "ABORTED"
|
||||
},
|
||||
"phase_separation": {
|
||||
"required_owner_decisions": 2,
|
||||
"phase_a_authorization": "GITEA_EXACT_HEAD_REVIEW",
|
||||
"phase_b_authorization": "DETACHED_SSH_SIGNATURE",
|
||||
"phase_a_release_authority": false,
|
||||
"one_shot_authority": false
|
||||
},
|
||||
"isolation": {
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false,
|
||||
"publisher_created": false,
|
||||
"writer_created": false,
|
||||
"credential_projection_created": false,
|
||||
"target_branch_created": false,
|
||||
"tag_created": false,
|
||||
"registry_modified": false,
|
||||
"baseline_modified": false,
|
||||
"production_modified": false
|
||||
},
|
||||
"governance": {
|
||||
"force_push_used": false,
|
||||
"tag_rewritten": false,
|
||||
"direct_main_push": false,
|
||||
"secrets_committed": false,
|
||||
"owner_approval_respected": true
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
1f4a83cf91e9aacb6f50bc4fb24d4f745a7a31fd1dd7f2d3d3ed0de05e38234f deploy/systemd/aios-campaign-anchor.service
|
||||
6b2525d31b8544488bce1544deb39a087cc2d830fb1b16fda26cde6ce430dbd2 deploy/systemd/aios-policy-activation-verification@.service
|
||||
fad52587775ff5f1c5cc16862c5fb1d910bb097c6b3330c00d75db4f7bef0bbd deploy/systemd/aios-policy-campaign-register@.service
|
||||
de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py
|
||||
6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json
|
||||
5b6d65ae34b2c3bebc1bde8f38c6b0bb73269336d7d4ee5e3416f79c323134d6 services/aios-two-stage-owner-campaign/ANCHOR_REGISTRATION_CONTRACT.md
|
||||
8ba5278f96447bf4308813b49fcc433d0cc7a5019cd9e78aeb0d8ada5f00d53b services/aios-two-stage-owner-campaign/README.md
|
||||
7285527358bc8da1d4bc7ff70162dfc3a734086851b734daac89d6f9b330798d services/aios-two-stage-owner-campaign/SECURITY_DELTA_V2.md
|
||||
89f03fdf156d35807bf39f2f1dbf304c01e7e9ad64399276fb72c68d795eda65 services/aios-two-stage-owner-campaign/STEP_EXECUTOR_CONTRACT.md
|
||||
7453177223b62509e4854051a75d53f24d8d1f737986f309cc6ae7a2e721459f services/aios-two-stage-owner-campaign/TWO_STAGE_OWNER_INTERACTION_SPEC.md
|
||||
9308ee245a28a0f0ff623fbfc8e3961204b8e67194bffe536a9c3485ca126ac7 services/aios-two-stage-owner-campaign/examples/allowed_signers.example
|
||||
9ac7b7fdeb73a99d4f41bf3410ef16f67091d85f9082075287d388e6e5e37eed services/aios-two-stage-owner-campaign/examples/campaign-anchor.env.example
|
||||
926f0c0cfb7bd6db0ebab6bcb5a0530057b8d38ae1e0b436a69a12b44039462b services/aios-two-stage-owner-campaign/examples/executor-release.example.json
|
||||
8d46ec9279eb58c03337d41b63de2abdd9eb0d03f98f0806768dcd309d481ee9 services/aios-two-stage-owner-campaign/examples/policy-activation-verification.example.json
|
||||
b34d8895fe872ef27c62c759449757def698eb05157daff8badd0c86f64ee18c services/aios-two-stage-owner-campaign/examples/release-campaign-manifest.example.json
|
||||
a2992f493cf0547e1f4acd3fcd9b20e03c912d8b255e196f640d47e291ec9d8e services/aios-two-stage-owner-campaign/examples/runtime-observer-policy.example.json
|
||||
150c9a8debdcaa4dbb9fd51817ca8710ef805a32f2f96dc5490d0c4f77f2cb6f services/aios-two-stage-owner-campaign/examples/runtime-observer-release.example.json
|
||||
675b39d13223ad281a4d14a04e739639e470e4e2a2591b85264ba37f840f2b49 services/aios-two-stage-owner-campaign/payload/scripts/generate_diff_chunks.py
|
||||
5751764275e626c787dad17c2561edd86b2710ae0b3cfeafeec44d98126d9015 services/aios-two-stage-owner-campaign/payload/services/aios-autonomous-control-plane/aios_autonomous_control_plane/lineage_audit.py
|
||||
2b501bf32a22e1b10c7254d2268d11aa2424aac5756c52441557c25dbaaaa8d5 services/aios-two-stage-owner-campaign/payload/tests/autonomous_control_plane/test_lineage_audit_v3.py
|
||||
3f118bc7a4f2bdfdd47e2c86112e87f95162dba4d88c1579337bd1d442ec9832 services/aios-two-stage-owner-campaign/pyproject.toml
|
||||
6b4def7c1eef19c01ef73c43252eb0cd1a87c103e1047249a3cfcc75c7922bf6 services/aios-two-stage-owner-campaign/schemas/governance-policy-activation-verification-v1.schema.json
|
||||
82ee589dc87db6998ddf0779ce264194cadea0deca66088b0797378f6ce89ad3 services/aios-two-stage-owner-campaign/schemas/task-scoped-release-campaign-v1.schema.json
|
||||
d04543b5407dcefc0a41af1b8a65ebc2e62f779582ae959449f1ac37b305eecf services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/__init__.py
|
||||
935a1c1166b0c1ea35a82256345000bf2c73ded718d77773bc27a71ecce28f7d services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/__main__.py
|
||||
4e317e2fbf912662bfbf57d2f7fe1a9f7fd3d8c00f7ad09602d9e885ee4ef3cc services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/canonical.py
|
||||
863be28723553e5bf5df90eda49e96edf8417e765da7b41579f050b04336d480 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/cli.py
|
||||
dae1e0ed0f841d928a1dd5094c64b1f39b3213301c1da747f45d032a18833c1e services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/errors.py
|
||||
baf8931566d2c2fb2985d1814b5ded89719bb4b0eba01e94cca9f3690baadeb2 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/executor.py
|
||||
18feb0792b95f1060aa696cc60943379695a246984aecf76098838f9ed37426e services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/journal_anchor.py
|
||||
e24765d3c62f9934a921f099e43a002d349a6c9934438da82040e40238070c58 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/orchestrator.py
|
||||
181e632192f1417a98c1efb35631cf80246759080b2e04243576abcdc7da22af services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/runtime_observer.py
|
||||
3f3ce81490b6f0893665ba296fc23be904ae0e5dcbcd3b602e25feb2571262b0 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/signature.py
|
||||
c71d85d5df2ed0e5367de9aacd230f586e77f3b4c11cf52960c84bbdeec43eb7 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/state_machine.py
|
||||
75ea8ed1c9b49b1ca3ce452fe229524ebd0ccd802945d3362dd668602ce1f98c services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/validator.py
|
||||
b1be7e1ed0fa0e16e041f77200d651cf46f6bb029c89d31f0b286d32c026cb66 services/aios-two-stage-owner-campaign/tests/test_canonical_snapshot.py
|
||||
0bb956135c90cf71fcb655c4d700c1f71c3442b9e9fddc376959890cdad823de services/aios-two-stage-owner-campaign/tests/test_executor_v2.py
|
||||
9bb8a9f1c3bd9aaf44db3dd2f5b810eb0cb650da0e5a5f5e2ea50be46f12664d services/aios-two-stage-owner-campaign/tests/test_lineage_payload.py
|
||||
681c14ddc0be90f94fd3c3d8a6768fe602ec8acd130ed00a8668505d2db57aa2 services/aios-two-stage-owner-campaign/tests/test_orchestrator_v2.py
|
||||
9edf44fe0129098c31d66f6b6f7ab252879b425e2844969d09e22c3c88e6b77d services/aios-two-stage-owner-campaign/tests/test_runtime_observer.py
|
||||
204e77c760380f8e29d10fc0afa4227497109d27493245c55a851de6ff69ac86 services/aios-two-stage-owner-campaign/tests/test_signature.py
|
||||
9bc5dfa237e6bf37ccc90bbc618a807892d323cdd41345717b296838b44cd22a services/aios-two-stage-owner-campaign/tests/test_state_machine.py
|
||||
a9ef9c11120f480e0cde3ccbf8a816705621506657d8ac10faf7e38fb54bf367 services/aios-two-stage-owner-campaign/tests/test_validator.py
|
||||
4ec67865474c6c7ebaa7d125490486e8644618b1666cbde597420e4e6fe8f1d4 tests/single_owner_governance/test_task_scoped_bootstrap.py
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"plan_id": "AIOS-BOOTSTRAP-RFC3339-FIX-ROLLBACK-V1",
|
||||
"schema_version": 1,
|
||||
"repository": "aios/aios",
|
||||
"approved_base_oid": "8814dff20222625b60454c613b61ccf15fa20e51",
|
||||
"governance_history_rewrite_allowed": false,
|
||||
"steps": [
|
||||
"stop_uncompleted_task_status_merge_units",
|
||||
"revoke_exact_task_tokens_and_verify_http_401",
|
||||
"remove_all_runtime_credential_projections",
|
||||
"remove_unactivated_runtime_units_and_non_root_task_accounts",
|
||||
"verify_governance_refs_and_production_refs_unchanged"
|
||||
],
|
||||
"internal_token_rotation": {
|
||||
"rollback_allowed_before_old_secret_revocation": true,
|
||||
"rollback_source": "root_only_encrypted_runtime_credential",
|
||||
"rollback_allowed_after_old_secret_revocation": false,
|
||||
"post_revocation_recovery": "issue_another_replacement_secret_under_new_governed_operation",
|
||||
"plaintext_secret_in_evidence_allowed": false
|
||||
},
|
||||
"required_closure": {
|
||||
"temporary_accounts_remaining": 0,
|
||||
"temporary_credentials_remaining": 0,
|
||||
"credential_projections_remaining": 0,
|
||||
"old_task_tokens_http_status": 401,
|
||||
"AIOS_GITOPS_WRITES_ENABLED": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_id": "AIOS_BOOTSTRAP_RFC3339_FIX_TEST_SUMMARY",
|
||||
"schema_version": 1,
|
||||
"candidate_tree_before_manifest": "c72b13bfe0656de9deeff9652e3e13ada81b110e",
|
||||
"local_unittest": {
|
||||
"tests_passed": 19,
|
||||
"tests_failed": 0,
|
||||
"tests_skipped": 0
|
||||
},
|
||||
"remote_posix": {
|
||||
"pytest_tests_passed": 19,
|
||||
"pytest_subtests_passed": 16,
|
||||
"tests_failed": 0,
|
||||
"tests_skipped": 0,
|
||||
"compileall": "PASS",
|
||||
"json_schema": "PASS",
|
||||
"systemd_validation": "PASS"
|
||||
},
|
||||
"secret_scan": {
|
||||
"scanner": "gitleaks",
|
||||
"exact_changed_paths": "PASS",
|
||||
"exact_changed_path_findings": 0,
|
||||
"whole_tree_existing_findings": 10,
|
||||
"whole_tree_findings_claimed_as_new": false
|
||||
},
|
||||
"python_executable": {
|
||||
"path": "/usr/bin/python3.12",
|
||||
"sha256": "e1efa562c2cc2e35521a5c9c9b9939921001ff8ca9708a13ef15ace68cc2ccd7"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"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,138 +0,0 @@
|
||||
#!/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())
|
||||
@@ -1,159 +0,0 @@
|
||||
#!/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())
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from .task_status_merge_broker import ContractDenied, TaskGrant, canonical, load_signed_task_grant, read_runtime_secret
|
||||
|
||||
|
||||
GITEA_BIN = Path("/data/aios-gitops/runtime/bin/gitea")
|
||||
GITEA_CONFIG = Path("/data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini")
|
||||
GITEA_DB = Path("/data/aios-gitops/runtime/gitea-bin/data/gitea.db")
|
||||
RUNUSER = Path("/usr/sbin/runuser")
|
||||
SYSTEMD_CREDS = Path("/usr/bin/systemd-creds")
|
||||
GIT = Path("/usr/bin/git")
|
||||
GITEA_REPOSITORY = Path("/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git")
|
||||
|
||||
|
||||
def _git(*arguments: str, input_bytes: bytes | None = None) -> bytes:
|
||||
return subprocess.run(
|
||||
[str(GIT), f"--git-dir={GITEA_REPOSITORY}", *arguments], input=input_bytes,
|
||||
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
).stdout
|
||||
|
||||
|
||||
def _verify_local_governance_binding(grant: TaskGrant) -> None:
|
||||
"""Verify the exact open PR and latest Owner review without issuing a token."""
|
||||
connection = sqlite3.connect(f"file:{GITEA_DB}?mode=ro", uri=True)
|
||||
connection.row_factory = sqlite3.Row
|
||||
try:
|
||||
pr = connection.execute(
|
||||
"SELECT pr.head_branch,pr.base_branch,pr.merge_base,pr.has_merged,i.is_closed "
|
||||
"FROM pull_request pr JOIN issue i ON i.id=pr.issue_id JOIN repository repo ON repo.id=i.repo_id "
|
||||
"WHERE repo.owner_name='aios' AND repo.lower_name='aios' AND pr.[index]=?",
|
||||
(grant.raw["pr_number"],),
|
||||
).fetchone()
|
||||
review = connection.execute(
|
||||
"SELECT r.type,r.commit_id,r.official,r.stale,r.dismissed,r.created_unix,u.id,u.lower_name "
|
||||
"FROM review r JOIN user u ON u.id=r.reviewer_id JOIN issue i ON i.id=r.issue_id "
|
||||
"JOIN repository repo ON repo.id=i.repo_id "
|
||||
"WHERE repo.owner_name='aios' AND repo.lower_name='aios' AND i.[index]=? "
|
||||
"AND u.id=2 AND u.lower_name='owner' ORDER BY r.id DESC LIMIT 1",
|
||||
(grant.raw["pr_number"],),
|
||||
).fetchone()
|
||||
finally:
|
||||
connection.close()
|
||||
if pr is None or pr["base_branch"] != "governance/approvals" or pr["merge_base"] != grant.raw["base_oid"] or pr["has_merged"] != 0 or pr["is_closed"] != 0:
|
||||
raise ContractDenied("LOCAL_PR_BINDING_INVALID")
|
||||
head = _git("rev-parse", f"refs/heads/{pr['head_branch']}").decode().strip()
|
||||
base = _git("rev-parse", "refs/heads/governance/approvals").decode().strip()
|
||||
if head != grant.raw["target_commit"] or base != grant.raw["base_oid"]:
|
||||
raise ContractDenied("LOCAL_REF_BINDING_INVALID")
|
||||
if review is None or review["type"] != 1 or review["commit_id"] != head or review["official"] != 1 or review["stale"] != 0 or review["dismissed"] != 0:
|
||||
raise ContractDenied("LOCAL_OWNER_APPROVAL_MISSING")
|
||||
submitted = datetime.fromtimestamp(review["created_unix"], tz=timezone.utc)
|
||||
if submitted < grant.issued_at or submitted >= grant.expires_at:
|
||||
raise ContractDenied("LOCAL_OWNER_APPROVAL_EXPIRED")
|
||||
tree = _git("show", "-s", "--format=%T", head).decode().strip()
|
||||
if tree != grant.raw["approved_tree_oid"]:
|
||||
raise ContractDenied("APPROVED_TREE_MISMATCH")
|
||||
entry = _git("ls-tree", head, "--", grant.raw["manifest_path"]).decode().strip().split()
|
||||
if len(entry) < 3 or entry[1] != "blob" or entry[2] != grant.raw["manifest_blob_oid"]:
|
||||
raise ContractDenied("MANIFEST_BLOB_BINDING_INVALID")
|
||||
manifest = _git("cat-file", "blob", grant.raw["manifest_blob_oid"])
|
||||
if hashlib.sha256(manifest).hexdigest() != grant.raw["manifest_sha256"]:
|
||||
raise ContractDenied("MANIFEST_SHA256_MISMATCH")
|
||||
|
||||
|
||||
def _safe_absolute(path: Path, expected_parent: Path) -> None:
|
||||
if not path.is_absolute() or path.is_symlink() or path.parent != expected_parent:
|
||||
raise ContractDenied("CREDENTIAL_PATH_UNSAFE")
|
||||
|
||||
|
||||
class TaskCredentialBroker:
|
||||
def __init__(self, grant: TaskGrant, audit_dir: Path, audit_key: bytes):
|
||||
self.grant = grant
|
||||
self.audit_key = bytes(audit_key)
|
||||
if not self.audit_key:
|
||||
raise ContractDenied("CREDENTIAL_AUDIT_KEY_EMPTY")
|
||||
self.audit_dir = Path(audit_dir)
|
||||
if not self.audit_dir.is_absolute() or self.audit_dir.is_symlink():
|
||||
raise ContractDenied("AUDIT_DIR_UNSAFE")
|
||||
|
||||
def provision(self, *, identity: str, token_name: str, encrypted_path: Path) -> dict:
|
||||
if os.geteuid() != 0:
|
||||
raise ContractDenied("ROOT_BROKER_REQUIRED")
|
||||
expected = self.grant.raw["status_identity"] if token_name.endswith("-status") else self.grant.raw["merge_identity"]
|
||||
if identity != expected or identity not in {"codex", "gpt"}:
|
||||
raise ContractDenied("IDENTITY_BINDING_INVALID")
|
||||
prefix = f"pr{self.grant.raw['pr_number']}-{self.grant.raw['target_commit'][:12]}"
|
||||
if token_name not in {f"{prefix}-status", f"{prefix}-merge"}:
|
||||
raise ContractDenied("TOKEN_NAME_INVALID")
|
||||
_safe_absolute(encrypted_path, Path("/run/aios-task-credential-broker"))
|
||||
if encrypted_path.exists():
|
||||
raise ContractDenied("CREDENTIAL_PATH_EXISTS")
|
||||
_verify_local_governance_binding(self.grant)
|
||||
command = [str(RUNUSER), "-u", "aiman", "--", str(GITEA_BIN), "admin", "user", "generate-access-token", "--config", str(GITEA_CONFIG), "--username", identity, "--token-name", token_name, "--scopes", "write:repository", "--raw"]
|
||||
issued = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip()
|
||||
if len(issued) < 32 or b"\n" in issued or b"\x00" in issued:
|
||||
raise ContractDenied("ISSUED_TOKEN_INVALID")
|
||||
subprocess.run([str(SYSTEMD_CREDS), "encrypt", "--name=gitea-token", "-", str(encrypted_path)], input=issued, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
os.chmod(encrypted_path, 0o400)
|
||||
connection = sqlite3.connect(f"file:{GITEA_DB}?mode=ro", uri=True)
|
||||
try:
|
||||
rows = connection.execute("SELECT t.id,u.id FROM access_token t JOIN user u ON u.id=t.uid WHERE u.lower_name=? AND t.name=? AND t.scope='write:repository'", (identity, token_name)).fetchall()
|
||||
finally:
|
||||
connection.close()
|
||||
if len(rows) != 1:
|
||||
raise ContractDenied("TOKEN_DATABASE_BINDING_MISSING")
|
||||
row = rows[0]
|
||||
receipt = {"status": "PASS", "operation": "credential_provision", "identity": identity, "token_name": token_name, "token_id": int(row[0]), "token_uid": int(row[1]), "grant_sha256": self.grant.digest, "credential_ref": f"systemd-credential-encrypted://{encrypted_path.name}"}
|
||||
return self._audit(receipt)
|
||||
|
||||
def revoke(self, *, receipt: dict, encrypted_path: Path) -> dict:
|
||||
if os.geteuid() != 0:
|
||||
raise ContractDenied("ROOT_BROKER_REQUIRED")
|
||||
_safe_absolute(encrypted_path, Path("/run/aios-task-credential-broker"))
|
||||
self.validate_provision_receipt(receipt, encrypted_path)
|
||||
decrypted = subprocess.run([str(SYSTEMD_CREDS), "decrypt", str(encrypted_path), "-"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip()
|
||||
if len(decrypted) < 32 or b"\n" in decrypted or b"\x00" in decrypted:
|
||||
raise ContractDenied("PROJECTED_TOKEN_INVALID")
|
||||
token_header = {"Authorization": "token " + decrypted.decode("utf-8")}
|
||||
preflight = urllib.request.Request("http://127.0.0.1:3000/api/v1/repos/aios/aios", headers=token_header)
|
||||
try:
|
||||
with urllib.request.urlopen(preflight, timeout=10) as response:
|
||||
if response.status != 200:
|
||||
raise ContractDenied("TOKEN_PREFLIGHT_STATUS_INVALID")
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise ContractDenied(f"TOKEN_PREFLIGHT_FAILED:{exc.code}") from exc
|
||||
connection = sqlite3.connect(GITEA_DB)
|
||||
try:
|
||||
connection.execute("BEGIN IMMEDIATE")
|
||||
cursor = connection.execute(
|
||||
"DELETE FROM access_token WHERE id=? AND uid=? AND name=? AND scope='write:repository'",
|
||||
(receipt["token_id"], receipt["token_uid"], receipt["token_name"]),
|
||||
)
|
||||
if cursor.rowcount != 1:
|
||||
raise ContractDenied("TOKEN_REVOCATION_MISMATCH")
|
||||
connection.commit()
|
||||
except Exception:
|
||||
connection.rollback()
|
||||
raise
|
||||
finally:
|
||||
connection.close()
|
||||
connection = sqlite3.connect(f"file:{GITEA_DB}?mode=ro", uri=True)
|
||||
try:
|
||||
remaining = connection.execute("SELECT COUNT(*) FROM access_token WHERE id=?", (receipt["token_id"],)).fetchone()[0]
|
||||
finally:
|
||||
connection.close()
|
||||
if remaining != 0:
|
||||
raise ContractDenied("TOKEN_REVOCATION_READBACK_MISMATCH")
|
||||
request = urllib.request.Request("http://127.0.0.1:3000/api/v1/repos/aios/aios", headers=token_header)
|
||||
try:
|
||||
urllib.request.urlopen(request, timeout=10)
|
||||
raise ContractDenied("REVOKED_TOKEN_STILL_ACCEPTED")
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code != 401:
|
||||
raise ContractDenied("REVOCATION_HTTP_STATUS_INVALID") from exc
|
||||
encrypted_path.unlink(missing_ok=True)
|
||||
result = {"status": "PASS", "operation": "credential_revoke", "identity": receipt["identity"], "token_name": receipt["token_name"], "token_id": receipt["token_id"], "grant_sha256": self.grant.digest, "token_rows_remaining": 0, "credential_projection_remaining": False, "old_token_http_status": 401}
|
||||
return self._audit(result)
|
||||
|
||||
def validate_provision_receipt(self, receipt: dict, encrypted_path: Path) -> None:
|
||||
required = {"status", "operation", "identity", "token_name", "token_id", "token_uid", "grant_sha256", "credential_ref", "audit_sha256", "audit_hmac_signature"}
|
||||
expected_credential_ref = f"systemd-credential-encrypted://{encrypted_path.name}"
|
||||
unsigned_receipt = {key: value for key, value in receipt.items() if key not in {"audit_sha256", "audit_hmac_signature"}}
|
||||
expected_name_prefix = f"pr{self.grant.raw['pr_number']}-{self.grant.raw['target_commit'][:12]}"
|
||||
if (set(receipt) != required or receipt["status"] != "PASS" or receipt["operation"] != "credential_provision"
|
||||
or receipt["grant_sha256"] != self.grant.digest or receipt["identity"] not in {self.grant.raw["status_identity"], self.grant.raw["merge_identity"]}
|
||||
or receipt["token_name"] not in {f"{expected_name_prefix}-status", f"{expected_name_prefix}-merge"}
|
||||
or receipt["credential_ref"] != expected_credential_ref
|
||||
or hashlib.sha256(canonical(unsigned_receipt)).hexdigest() != receipt["audit_sha256"]
|
||||
or not hmac.compare_digest(receipt["audit_hmac_signature"], hmac.new(self.audit_key, canonical(unsigned_receipt), hashlib.sha256).hexdigest())):
|
||||
raise ContractDenied("PROVISION_RECEIPT_INVALID")
|
||||
|
||||
def _audit(self, value: dict) -> dict:
|
||||
raw = canonical(value)
|
||||
result = dict(value)
|
||||
result["audit_sha256"] = hashlib.sha256(raw).hexdigest()
|
||||
result["audit_hmac_signature"] = hmac.new(self.audit_key, raw, hashlib.sha256).hexdigest()
|
||||
self.audit_dir.mkdir(mode=0o750, parents=True, exist_ok=True)
|
||||
destination = self.audit_dir / f"{value['operation']}-{value['token_name']}.json"
|
||||
descriptor = os.open(destination, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0), 0o640)
|
||||
try:
|
||||
os.write(descriptor, canonical(result) + b"\n")
|
||||
os.fsync(descriptor)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="aios-task-credential-broker")
|
||||
parser.add_argument("--grant", required=True, type=Path)
|
||||
parser.add_argument("--audit-dir", required=True, type=Path)
|
||||
parser.add_argument("--operation", choices=("provision", "revoke"))
|
||||
parser.add_argument("--identity")
|
||||
parser.add_argument("--token-name")
|
||||
parser.add_argument("--encrypted-path", type=Path)
|
||||
parser.add_argument("--receipt", type=Path)
|
||||
parser.add_argument("--request", type=Path)
|
||||
parser.add_argument("--grant-signature", required=True, type=Path)
|
||||
parser.add_argument("--allowed-signers", required=True, type=Path)
|
||||
parser.add_argument("--audit-key", required=True, type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
grant = load_signed_task_grant(args.grant, args.grant_signature, args.allowed_signers)
|
||||
broker = TaskCredentialBroker(grant, args.audit_dir, read_runtime_secret(args.audit_key))
|
||||
if args.request is not None:
|
||||
request = json.loads(args.request.read_text(encoding="utf-8"))
|
||||
if not isinstance(request, dict) or set(request) not in ({"operation", "identity", "token_name", "encrypted_path"}, {"operation", "identity", "token_name", "encrypted_path", "receipt"}):
|
||||
raise ContractDenied("CREDENTIAL_REQUEST_INVALID")
|
||||
args.operation = request["operation"]
|
||||
args.identity = request["identity"]
|
||||
args.token_name = request["token_name"]
|
||||
args.encrypted_path = Path(request["encrypted_path"])
|
||||
args.receipt = Path(request["receipt"]) if "receipt" in request else None
|
||||
if args.operation is None or args.identity is None or args.token_name is None or args.encrypted_path is None:
|
||||
raise ContractDenied("CREDENTIAL_REQUEST_INCOMPLETE")
|
||||
if args.operation == "provision":
|
||||
result = broker.provision(identity=args.identity, token_name=args.token_name, encrypted_path=args.encrypted_path)
|
||||
else:
|
||||
if args.receipt is None:
|
||||
raise ContractDenied("RECEIPT_REQUIRED")
|
||||
result = broker.revoke(receipt=json.loads(args.receipt.read_text(encoding="utf-8")), encrypted_path=args.encrypted_path)
|
||||
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import subprocess
|
||||
|
||||
from .task_credential_broker import SYSTEMD_CREDS, TaskCredentialBroker
|
||||
from .task_status_merge_broker import ContractDenied, load_signed_task_grant
|
||||
|
||||
|
||||
RUNTIME_ROOT = Path("/var/lib/aios-gitops/runtime/task-status-merge")
|
||||
CREDENTIAL_ROOT = Path("/run/aios-task-credential-broker")
|
||||
|
||||
|
||||
def _operation_complete(task_dir: Path, token_name: str) -> bool:
|
||||
if token_name.endswith("-status"):
|
||||
journal, operation = task_dir / "status-journal" / "journal.sqlite3", "statuses"
|
||||
elif token_name.endswith("-merge"):
|
||||
journal, operation = task_dir / "merge-journal" / "journal.sqlite3", "merge"
|
||||
else:
|
||||
raise ContractDenied("SWEEPER_TOKEN_NAME_INVALID")
|
||||
if not journal.is_file() or journal.is_symlink():
|
||||
return False
|
||||
connection = sqlite3.connect(f"file:{journal}?mode=ro", uri=True)
|
||||
try:
|
||||
row = connection.execute("SELECT state FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
finally:
|
||||
connection.close()
|
||||
return row == ("COMMITTED",)
|
||||
|
||||
|
||||
def sweep(runtime_root: Path, allowed_signers: Path, *, now: datetime | None = None) -> list[dict]:
|
||||
if os.geteuid() != 0:
|
||||
raise ContractDenied("ROOT_SWEEPER_REQUIRED")
|
||||
if runtime_root != RUNTIME_ROOT or runtime_root.is_symlink():
|
||||
raise ContractDenied("SWEEPER_ROOT_INVALID")
|
||||
current = now or datetime.now(timezone.utc)
|
||||
results: list[dict] = []
|
||||
for task_dir in sorted(runtime_root.iterdir() if runtime_root.exists() else []):
|
||||
if not task_dir.is_dir() or task_dir.is_symlink():
|
||||
raise ContractDenied("SWEEPER_TASK_DIR_INVALID")
|
||||
grant_path, signature_path = task_dir / "grant.json", task_dir / "grant.sig"
|
||||
if not grant_path.is_file() or not signature_path.is_file():
|
||||
raise ContractDenied("SWEEPER_GRANT_INCOMPLETE")
|
||||
grant = load_signed_task_grant(grant_path, signature_path, allowed_signers, now=current, allow_expired=True)
|
||||
audit_credential = CREDENTIAL_ROOT / f"{task_dir.name}-audit.cred"
|
||||
if not audit_credential.is_file() or audit_credential.is_symlink():
|
||||
raise ContractDenied("SWEEPER_TASK_AUDIT_CREDENTIAL_MISSING")
|
||||
audit_key = subprocess.run(
|
||||
[str(SYSTEMD_CREDS), "decrypt", str(audit_credential), "-"], check=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
).stdout.strip()
|
||||
if not audit_key or b"\n" in audit_key or b"\x00" in audit_key:
|
||||
raise ContractDenied("SWEEPER_TASK_AUDIT_KEY_INVALID")
|
||||
broker = TaskCredentialBroker(grant, task_dir / "audit", audit_key)
|
||||
for receipt_path in sorted((task_dir / "audit").glob("credential_provision-*.json")):
|
||||
if receipt_path.is_symlink():
|
||||
raise ContractDenied("SWEEPER_RECEIPT_UNSAFE")
|
||||
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
|
||||
token_name = str(receipt.get("token_name") or "")
|
||||
credential_ref = str(receipt.get("credential_ref") or "")
|
||||
prefix = "systemd-credential-encrypted://"
|
||||
if not credential_ref.startswith(prefix):
|
||||
raise ContractDenied("SWEEPER_CREDENTIAL_REF_INVALID")
|
||||
encrypted_path = CREDENTIAL_ROOT / credential_ref.removeprefix(prefix)
|
||||
revoked_receipt = task_dir / "audit" / f"credential_revoke-{token_name}.json"
|
||||
if revoked_receipt.exists():
|
||||
if encrypted_path.exists():
|
||||
raise ContractDenied("SWEEPER_REVOKED_PROJECTION_REAPPEARED")
|
||||
continue
|
||||
if current < grant.expires_at and not _operation_complete(task_dir, token_name):
|
||||
continue
|
||||
if not encrypted_path.is_file() or encrypted_path.is_symlink():
|
||||
raise ContractDenied("SWEEPER_CREDENTIAL_PROJECTION_MISSING")
|
||||
results.append(broker.revoke(receipt=receipt, encrypted_path=encrypted_path))
|
||||
return results
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="aios-task-credential-expiry-sweeper")
|
||||
parser.add_argument("--runtime-root", required=True, type=Path)
|
||||
parser.add_argument("--allowed-signers", required=True, type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
result = sweep(args.runtime_root, args.allowed_signers)
|
||||
print(json.dumps({"status": "PASS", "revoked": result}, sort_keys=True, separators=(",", ":")))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+1090
File diff suppressed because it is too large
Load Diff
+472
@@ -0,0 +1,472 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sqlite3
|
||||
import stat
|
||||
import subprocess
|
||||
from typing import Any, Protocol
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
ALLOWED_CONTEXTS = frozenset({
|
||||
"aios/branch-protection",
|
||||
"aios/evidence-closure",
|
||||
"aios/secret-scan",
|
||||
"aios/state-machine",
|
||||
"aios/validator",
|
||||
})
|
||||
ALLOWED_IDENTITIES = frozenset({"codex", "gpt"})
|
||||
OID = re.compile(r"^[0-9a-f]{40}$")
|
||||
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
||||
SECRET_REF = re.compile(r"^systemd-credential://[A-Za-z0-9_.-]+$")
|
||||
SUPERSEDED_PR_NUMBER = 36
|
||||
SUPERSEDED_HEAD_OID = "a8644fc172bd8ebbba17a16e6bf586c93fd1e793"
|
||||
FIELDS = frozenset({
|
||||
"schema_id", "schema_version", "repository", "pr_number", "base_oid",
|
||||
"target_commit", "approved_tree_oid", "manifest_path", "manifest_blob_oid", "manifest_sha256",
|
||||
"payload_sha256", "status_identity", "merge_identity", "status_contexts",
|
||||
"status_write_quota", "merge_quota", "merge_method", "branch_push_allowed",
|
||||
"tag_write_allowed", "main_write_allowed", "registry_write_allowed",
|
||||
"baseline_write_allowed", "production_write_allowed", "issued_at", "expires_at",
|
||||
"audit_key_ref", "grant_signer_identity", "grant_signature_namespace",
|
||||
"status_evidence_sha256", "supersedes_pr_number", "supersedes_head_oid",
|
||||
"renewal_required", "timestamp_contract", "canonical_timezone",
|
||||
"raw_timestamp_preserved", "naive_datetime_allowed",
|
||||
})
|
||||
RFC3339 = re.compile(
|
||||
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$"
|
||||
)
|
||||
|
||||
|
||||
class ContractDenied(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def canonical(value: Any) -> bytes:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def _time(value: str) -> datetime:
|
||||
if not isinstance(value, str) or value != value.strip() or not RFC3339.fullmatch(value):
|
||||
raise ContractDenied("TIMESTAMP_INVALID")
|
||||
try:
|
||||
normalized = value[:-1] + "+00:00" if value.endswith("Z") else value
|
||||
parsed = datetime.fromisoformat(normalized)
|
||||
except ValueError as exc:
|
||||
raise ContractDenied("TIMESTAMP_INVALID") from exc
|
||||
if parsed.tzinfo is None or parsed.utcoffset() is None:
|
||||
raise ContractDenied("TIMESTAMP_TIMEZONE_REQUIRED")
|
||||
return parsed.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def _utc_timestamp(value: datetime) -> str:
|
||||
normalized = value.astimezone(timezone.utc).isoformat()
|
||||
return normalized[:-6] + "Z" if normalized.endswith("+00:00") else normalized
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TaskGrant:
|
||||
raw: dict[str, Any]
|
||||
issued_at: datetime
|
||||
expires_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, value: dict[str, Any], *, now: datetime | None = None,
|
||||
allow_expired: bool = False) -> "TaskGrant":
|
||||
if not isinstance(value, dict) or set(value) != FIELDS:
|
||||
raise ContractDenied("GRANT_FIELDS_INVALID")
|
||||
if value["schema_id"] != "AIOS_TASK_STATUS_MERGE_GRANT" or value["schema_version"] != 1:
|
||||
raise ContractDenied("GRANT_SCHEMA_INVALID")
|
||||
if value["repository"] != "aios/aios" or type(value["pr_number"]) is not int or value["pr_number"] <= 0:
|
||||
raise ContractDenied("REPOSITORY_OR_PR_MISMATCH")
|
||||
for field in ("base_oid", "target_commit", "approved_tree_oid", "manifest_blob_oid"):
|
||||
if not isinstance(value[field], str) or not OID.fullmatch(value[field]):
|
||||
raise ContractDenied("OID_INVALID")
|
||||
if not isinstance(value["manifest_path"], str) or not re.fullmatch(r"approvals/[A-Za-z0-9._/-]+\.json", value["manifest_path"]) or ".." in value["manifest_path"].split("/"):
|
||||
raise ContractDenied("MANIFEST_PATH_INVALID")
|
||||
for field in ("manifest_sha256", "payload_sha256"):
|
||||
if not isinstance(value[field], str) or not SHA256.fullmatch(value[field]):
|
||||
raise ContractDenied("SHA256_INVALID")
|
||||
evidence = value["status_evidence_sha256"]
|
||||
if not isinstance(evidence, dict) or set(evidence) != ALLOWED_CONTEXTS or any(not isinstance(item, str) or not SHA256.fullmatch(item) for item in evidence.values()):
|
||||
raise ContractDenied("STATUS_EVIDENCE_BINDING_INVALID")
|
||||
if value["supersedes_pr_number"] != SUPERSEDED_PR_NUMBER or value["pr_number"] == SUPERSEDED_PR_NUMBER:
|
||||
raise ContractDenied("RENEWAL_PR_INVALID")
|
||||
if value["supersedes_head_oid"] != SUPERSEDED_HEAD_OID or value["supersedes_head_oid"] == value["target_commit"] or value["renewal_required"] is not True:
|
||||
raise ContractDenied("RENEWAL_BINDING_INVALID")
|
||||
if value["status_identity"] not in ALLOWED_IDENTITIES or value["merge_identity"] not in ALLOWED_IDENTITIES:
|
||||
raise ContractDenied("IDENTITY_NOT_ALLOWED")
|
||||
if set(value["status_contexts"]) != ALLOWED_CONTEXTS or len(value["status_contexts"]) != 5:
|
||||
raise ContractDenied("STATUS_CONTEXTS_INVALID")
|
||||
if value["status_write_quota"] != 5 or value["merge_quota"] != 1:
|
||||
raise ContractDenied("QUOTA_INVALID")
|
||||
if value["merge_method"] != "fast-forward-only":
|
||||
raise ContractDenied("MERGE_METHOD_INVALID")
|
||||
denied = (
|
||||
"branch_push_allowed", "tag_write_allowed", "main_write_allowed",
|
||||
"registry_write_allowed", "baseline_write_allowed", "production_write_allowed",
|
||||
)
|
||||
if any(value[field] is not False for field in denied):
|
||||
raise ContractDenied("WRITE_SCOPE_EXPANDED")
|
||||
if not isinstance(value["audit_key_ref"], str) or not SECRET_REF.fullmatch(value["audit_key_ref"]):
|
||||
raise ContractDenied("AUDIT_KEY_REFERENCE_INVALID")
|
||||
if value["grant_signer_identity"] != "aios-task-grant-signer" or value["grant_signature_namespace"] != "aios-task-status-merge-grant-v1":
|
||||
raise ContractDenied("GRANT_SIGNATURE_POLICY_INVALID")
|
||||
if (value["timestamp_contract"] != "RFC3339_OFFSET_AWARE_V2"
|
||||
or value["canonical_timezone"] != "UTC"
|
||||
or value["raw_timestamp_preserved"] is not True
|
||||
or value["naive_datetime_allowed"] is not False):
|
||||
raise ContractDenied("TIMESTAMP_CONTRACT_INVALID")
|
||||
issued, expires = _time(value["issued_at"]), _time(value["expires_at"])
|
||||
current = now or datetime.now(timezone.utc)
|
||||
lifetime = (expires - issued).total_seconds()
|
||||
if lifetime <= 0 or lifetime > 12 * 3600 or (not allow_expired and current >= expires):
|
||||
raise ContractDenied("GRANT_EXPIRED_OR_LIFETIME_INVALID")
|
||||
return cls(dict(value), issued, expires)
|
||||
|
||||
@property
|
||||
def digest(self) -> str:
|
||||
return hashlib.sha256(canonical(self.raw)).hexdigest()
|
||||
|
||||
|
||||
def load_signed_task_grant(grant_path: Path, signature_path: Path, allowed_signers: Path,
|
||||
*, now: datetime | None = None, allow_expired: bool = False) -> TaskGrant:
|
||||
for path in (grant_path, signature_path, allowed_signers):
|
||||
if not path.is_absolute() or path.is_symlink():
|
||||
raise ContractDenied("GRANT_SIGNATURE_PATH_UNSAFE")
|
||||
raw = grant_path.read_bytes()
|
||||
if len(raw) > 1024 * 1024:
|
||||
raise ContractDenied("GRANT_TOO_LARGE")
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise ContractDenied("GRANT_JSON_INVALID") from exc
|
||||
if raw != canonical(value) + b"\n":
|
||||
raise ContractDenied("GRANT_NOT_CANONICAL")
|
||||
grant = TaskGrant.from_dict(value, now=now, allow_expired=allow_expired)
|
||||
command = [
|
||||
"/usr/bin/ssh-keygen", "-Y", "verify", "-f", str(allowed_signers),
|
||||
"-I", "aios-task-grant-signer", "-n", "aios-task-status-merge-grant-v1",
|
||||
"-s", str(signature_path),
|
||||
]
|
||||
try:
|
||||
subprocess.run(command, input=raw, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
except (OSError, subprocess.CalledProcessError) as exc:
|
||||
raise ContractDenied("GRANT_SIGNATURE_INVALID") from exc
|
||||
return grant
|
||||
|
||||
|
||||
class API(Protocol):
|
||||
def get(self, path: str) -> Any: ...
|
||||
def post(self, path: str, payload: dict[str, Any]) -> Any: ...
|
||||
|
||||
|
||||
def read_runtime_secret(path: Path) -> bytes:
|
||||
flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
|
||||
descriptor = os.open(path, flags)
|
||||
try:
|
||||
metadata = os.fstat(descriptor)
|
||||
mode = stat.S_IMODE(metadata.st_mode)
|
||||
credential_directory = os.environ.get("CREDENTIALS_DIRECTORY")
|
||||
systemd_projection = (
|
||||
credential_directory is not None
|
||||
and path.parent == Path(credential_directory)
|
||||
and mode == 0o440
|
||||
and metadata.st_uid == 0
|
||||
and metadata.st_gid == 0
|
||||
)
|
||||
if not stat.S_ISREG(metadata.st_mode) or (mode != 0o400 and not systemd_projection):
|
||||
raise ContractDenied("CREDENTIAL_FILE_UNSAFE")
|
||||
value = os.read(descriptor, 16 * 1024 + 1)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
if not value or len(value) > 16 * 1024 or b"\x00" in value or b"\n" in value.strip():
|
||||
raise ContractDenied("CREDENTIAL_CONTENT_INVALID")
|
||||
return value.strip()
|
||||
|
||||
|
||||
class _NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
return None
|
||||
|
||||
|
||||
class GiteaClient:
|
||||
def __init__(self, token_path: Path, api_base: str = "http://127.0.0.1:3000/api/v1"):
|
||||
parsed = urllib.parse.urlparse(api_base)
|
||||
if parsed.scheme != "http" or parsed.hostname != "127.0.0.1" or parsed.path.rstrip("/") != "/api/v1" or parsed.username or parsed.password or parsed.query or parsed.fragment:
|
||||
raise ContractDenied("GITEA_API_ORIGIN_INVALID")
|
||||
self.token_path = Path(token_path)
|
||||
self.api_base = api_base.rstrip("/")
|
||||
self.opener = urllib.request.build_opener(_NoRedirect)
|
||||
|
||||
def _request(self, method: str, path: str, payload: dict[str, Any] | None = None) -> Any:
|
||||
if not path.startswith("/") or ".." in path.split("/"):
|
||||
raise ContractDenied("GITEA_API_PATH_INVALID")
|
||||
token = read_runtime_secret(self.token_path).decode("utf-8")
|
||||
body = canonical(payload) if payload is not None else None
|
||||
request = urllib.request.Request(self.api_base + path, data=body, method=method, headers={"Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json"})
|
||||
try:
|
||||
with self.opener.open(request, timeout=20) as response:
|
||||
raw = response.read(2 * 1024 * 1024 + 1)
|
||||
except (urllib.error.URLError, OSError) as exc:
|
||||
raise ContractDenied(f"GITEA_API_FAILED:{type(exc).__name__}") from exc
|
||||
if len(raw) > 2 * 1024 * 1024:
|
||||
raise ContractDenied("GITEA_API_RESPONSE_TOO_LARGE")
|
||||
return json.loads(raw.decode("utf-8")) if raw else None
|
||||
|
||||
def get(self, path: str) -> Any:
|
||||
return self._request("GET", path)
|
||||
|
||||
def post(self, path: str, payload: dict[str, Any]) -> Any:
|
||||
return self._request("POST", path, payload)
|
||||
|
||||
|
||||
class QuotaJournal:
|
||||
def __init__(self, path: Path, grant: TaskGrant):
|
||||
self.path = Path(path)
|
||||
self.grant = grant
|
||||
self.connection = sqlite3.connect(self.path, isolation_level=None)
|
||||
journal_mode = self.connection.execute("PRAGMA journal_mode=WAL").fetchone()
|
||||
if journal_mode is None or str(journal_mode[0]).lower() != "wal":
|
||||
raise ContractDenied("JOURNAL_MODE_INVALID")
|
||||
self.connection.execute("PRAGMA synchronous=FULL")
|
||||
synchronous = self.connection.execute("PRAGMA synchronous").fetchone()
|
||||
if synchronous is None or int(synchronous[0]) != 2:
|
||||
raise ContractDenied("JOURNAL_SYNCHRONOUS_INVALID")
|
||||
self.connection.execute("CREATE TABLE IF NOT EXISTS quota(operation TEXT PRIMARY KEY, state TEXT NOT NULL, effect_sha256 TEXT)")
|
||||
self.connection.execute("CREATE TABLE IF NOT EXISTS audit(seq INTEGER PRIMARY KEY AUTOINCREMENT, event BLOB NOT NULL, previous_hash TEXT NOT NULL, event_hash TEXT NOT NULL UNIQUE)")
|
||||
|
||||
def close(self) -> None:
|
||||
self.connection.close()
|
||||
|
||||
def reserve(self, operation: str) -> str:
|
||||
limit = {"statuses": 1, "merge": 1}.get(operation)
|
||||
if limit is None:
|
||||
raise ContractDenied("OPERATION_INVALID")
|
||||
self.connection.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
row = self.connection.execute("SELECT state FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
if row and row[0] == "COMMITTED":
|
||||
raise ContractDenied("STATUS_QUOTA_EXHAUSTED" if operation == "statuses" else "MERGE_QUOTA_EXHAUSTED")
|
||||
if row and row[0] != "RESERVED":
|
||||
raise ContractDenied("JOURNAL_STATE_INVALID")
|
||||
if not row:
|
||||
self.connection.execute("INSERT INTO quota(operation,state,effect_sha256) VALUES(?,'RESERVED',NULL)", (operation,))
|
||||
self.connection.execute("COMMIT")
|
||||
return "RESERVED"
|
||||
except Exception:
|
||||
self.connection.execute("ROLLBACK")
|
||||
raise
|
||||
|
||||
def state(self, operation: str) -> str | None:
|
||||
row = self.connection.execute("SELECT state FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
return str(row[0]) if row else None
|
||||
|
||||
def commit(self, operation: str, effect: dict[str, Any]) -> None:
|
||||
digest = hashlib.sha256(canonical(effect)).hexdigest()
|
||||
self.connection.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
row = self.connection.execute("SELECT state,effect_sha256 FROM quota WHERE operation=?", (operation,)).fetchone()
|
||||
if row is None or row[0] not in {"RESERVED", "COMMITTED"}:
|
||||
raise ContractDenied("RESERVATION_MISSING")
|
||||
if row[0] == "COMMITTED" and row[1] != digest:
|
||||
raise ContractDenied("COMMITTED_EFFECT_MISMATCH")
|
||||
self.connection.execute("UPDATE quota SET state='COMMITTED',effect_sha256=? WHERE operation=?", (digest, operation))
|
||||
self.connection.execute("COMMIT")
|
||||
except Exception:
|
||||
self.connection.execute("ROLLBACK")
|
||||
raise
|
||||
|
||||
def append(self, event: dict[str, Any]) -> str:
|
||||
row = self.connection.execute("SELECT event_hash FROM audit ORDER BY seq DESC LIMIT 1").fetchone()
|
||||
previous = row[0] if row else "0" * 64
|
||||
raw = canonical(event)
|
||||
digest = hashlib.sha256(previous.encode() + raw).hexdigest()
|
||||
self.connection.execute("INSERT INTO audit(event,previous_hash,event_hash) VALUES(?,?,?)", (raw, previous, digest))
|
||||
return digest
|
||||
|
||||
def verify_chain(self) -> bool:
|
||||
previous = "0" * 64
|
||||
for raw, observed_previous, observed_hash in self.connection.execute("SELECT event,previous_hash,event_hash FROM audit ORDER BY seq"):
|
||||
if observed_previous != previous or hashlib.sha256(previous.encode() + raw).hexdigest() != observed_hash:
|
||||
return False
|
||||
previous = observed_hash
|
||||
return True
|
||||
|
||||
|
||||
def _latest_statuses(values: Any) -> dict[str, dict[str, Any]]:
|
||||
if not isinstance(values, list):
|
||||
raise ContractDenied("STATUS_RESPONSE_INVALID")
|
||||
latest: dict[str, tuple[tuple[str, int], dict[str, Any]]] = {}
|
||||
for item in values:
|
||||
if not isinstance(item, dict) or item.get("context") not in ALLOWED_CONTEXTS:
|
||||
continue
|
||||
key = (str(item.get("created_at") or ""), int(item.get("id") or 0))
|
||||
context = item["context"]
|
||||
if context not in latest or key > latest[context][0]:
|
||||
latest[context] = (key, item)
|
||||
return {context: item for context, (_, item) in latest.items()}
|
||||
|
||||
|
||||
def _verify_owner_binding(api: API, grant: TaskGrant) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
pr_number = grant.raw["pr_number"]
|
||||
head, base = grant.raw["target_commit"], grant.raw["base_oid"]
|
||||
pr = api.get(f"/repos/aios/aios/pulls/{pr_number}")
|
||||
reviews = api.get(f"/repos/aios/aios/pulls/{pr_number}/reviews")
|
||||
if not isinstance(pr, dict) or pr.get("state") != "open" or pr.get("mergeable") is not True or (pr.get("head") or {}).get("sha") != head or (pr.get("base") or {}).get("sha") != base or (pr.get("base") or {}).get("ref") != "governance/approvals":
|
||||
raise ContractDenied("PR_BINDING_INVALID")
|
||||
if not isinstance(reviews, list):
|
||||
raise ContractDenied("REVIEW_RESPONSE_INVALID")
|
||||
owners = [item for item in reviews if isinstance(item, dict) and (item.get("user") or {}).get("login") == "owner" and (item.get("user") or {}).get("id") == 2]
|
||||
if not owners:
|
||||
raise ContractDenied("OWNER_EXACT_HEAD_APPROVAL_MISSING")
|
||||
latest = max(owners, key=lambda item: int(item.get("id") or -1))
|
||||
if latest.get("state") != "APPROVED" or latest.get("commit_id") != head or latest.get("official") is not True or latest.get("stale") is not False or latest.get("dismissed") is not False:
|
||||
raise ContractDenied("OWNER_EXACT_HEAD_APPROVAL_MISSING")
|
||||
raw_timestamp = str(latest.get("submitted_at") or "")
|
||||
submitted = _time(raw_timestamp)
|
||||
if submitted < grant.issued_at or submitted >= grant.expires_at:
|
||||
raise ContractDenied("OWNER_APPROVAL_EXPIRED")
|
||||
normalized_review = dict(latest)
|
||||
normalized_review["review_timestamp_raw"] = raw_timestamp
|
||||
normalized_review["review_timestamp_utc"] = _utc_timestamp(submitted)
|
||||
return pr, normalized_review
|
||||
|
||||
|
||||
def _signed_receipt(value: dict[str, Any], key: bytes) -> dict[str, Any]:
|
||||
if not key:
|
||||
raise ContractDenied("AUDIT_KEY_EMPTY")
|
||||
result = dict(value)
|
||||
result["audit_signature"] = hmac.new(key, canonical(value), hashlib.sha256).hexdigest()
|
||||
return result
|
||||
|
||||
|
||||
class StatusBroker:
|
||||
def __init__(self, grant: TaskGrant, journal: QuotaJournal, api: API, audit_key: bytes):
|
||||
self.grant, self.journal, self.api, self.audit_key = grant, journal, api, audit_key
|
||||
|
||||
def publish(self, evidence: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
if not isinstance(evidence, list) or len(evidence) != 5:
|
||||
raise ContractDenied("EVIDENCE_SET_INVALID")
|
||||
mapped = {item.get("context"): item for item in evidence if isinstance(item, dict)}
|
||||
if set(mapped) != ALLOWED_CONTEXTS or any(set(item) != {"context", "evidence_sha256"} or not SHA256.fullmatch(str(item["evidence_sha256"])) or item["evidence_sha256"] != self.grant.raw["status_evidence_sha256"].get(context) for context, item in mapped.items()):
|
||||
raise ContractDenied("EVIDENCE_SET_INVALID")
|
||||
_, review = _verify_owner_binding(self.api, self.grant)
|
||||
self.journal.reserve("statuses")
|
||||
published = []
|
||||
head = self.grant.raw["target_commit"]
|
||||
pr_number = self.grant.raw["pr_number"]
|
||||
observed_before = self.api.get(f"/repos/aios/aios/commits/{head}/statuses")
|
||||
already = _latest_statuses(observed_before)
|
||||
already = {context: item for context, item in already.items()
|
||||
if item.get("status", item.get("state")) == "success"
|
||||
and item.get("sha", head) == head
|
||||
and (item.get("creator") or {}).get("login") == self.grant.raw["status_identity"]
|
||||
and str(item.get("description") or "").endswith(mapped[context]["evidence_sha256"])}
|
||||
for context in sorted(ALLOWED_CONTEXTS):
|
||||
digest = mapped[context]["evidence_sha256"]
|
||||
if context in already:
|
||||
published.append({"context": context, "evidence_sha256": digest, "status_id": already[context].get("id"), "reconciled": True})
|
||||
continue
|
||||
response = self.api.post(f"/repos/aios/aios/statuses/{head}", {
|
||||
"state": "success", "context": context,
|
||||
"description": f"task-scoped PASS evidence_sha256={digest}",
|
||||
"target_url": f"https://g.cnnetmkt.top/git/aios/aios/pulls/{pr_number}",
|
||||
})
|
||||
if not isinstance(response, dict) or response.get("context") != context or response.get("status", response.get("state")) != "success":
|
||||
raise ContractDenied("STATUS_PUBLICATION_FAILED")
|
||||
published.append({"context": context, "evidence_sha256": digest, "status_id": response.get("id")})
|
||||
observed = self.api.get(f"/repos/aios/aios/commits/{head}/statuses")
|
||||
latest = _latest_statuses(observed)
|
||||
if set(latest) != ALLOWED_CONTEXTS or any(
|
||||
item.get("status", item.get("state")) != "success"
|
||||
or item.get("sha", head) != head
|
||||
or (item.get("creator") or {}).get("login") != self.grant.raw["status_identity"]
|
||||
or not str(item.get("description") or "").endswith(mapped[context]["evidence_sha256"])
|
||||
for context, item in latest.items()
|
||||
):
|
||||
raise ContractDenied("STATUS_READBACK_INVALID")
|
||||
receipt = {"status": "PASS", "operation": "publish_statuses", "grant_sha256": self.grant.digest, "target_commit": head, "published_count": 5, "published": published, "review_timestamp_raw": review["review_timestamp_raw"], "review_timestamp_utc": review["review_timestamp_utc"]}
|
||||
receipt["audit_signature"] = hmac.new(self.audit_key, canonical(receipt), hashlib.sha256).hexdigest()
|
||||
receipt["audit_chain_hash"] = self.journal.append(receipt)
|
||||
self.journal.commit("statuses", receipt)
|
||||
return receipt
|
||||
|
||||
|
||||
class MergeBroker:
|
||||
def __init__(self, grant: TaskGrant, journal: QuotaJournal, api: API, audit_key: bytes):
|
||||
self.grant, self.journal, self.api, self.audit_key = grant, journal, api, audit_key
|
||||
|
||||
def merge(self) -> dict[str, Any]:
|
||||
head, base = self.grant.raw["target_commit"], self.grant.raw["base_oid"]
|
||||
pr_number = self.grant.raw["pr_number"]
|
||||
pr = self.api.get(f"/repos/aios/aios/pulls/{pr_number}")
|
||||
if isinstance(pr, dict) and pr.get("merged") is True:
|
||||
observed_branch = self.api.get("/repos/aios/aios/branches/governance%2Fapprovals")
|
||||
if pr.get("merge_commit_sha") != head or (observed_branch.get("commit") or {}).get("id") != head or self.journal.state("merge") != "RESERVED":
|
||||
raise ContractDenied("FAST_FORWARD_RESULT_MISMATCH")
|
||||
receipt = {"status": "PASS", "operation": "fast_forward_merge", "grant_sha256": self.grant.digest, "pr_number": pr_number, "governance_oid": head, "merge_method": "fast-forward-only", "reconciled": True}
|
||||
receipt["audit_signature"] = hmac.new(self.audit_key, canonical(receipt), hashlib.sha256).hexdigest()
|
||||
receipt["audit_chain_hash"] = self.journal.append(receipt)
|
||||
self.journal.commit("merge", receipt)
|
||||
return receipt
|
||||
pr, review = _verify_owner_binding(self.api, self.grant)
|
||||
statuses = self.api.get(f"/repos/aios/aios/commits/{head}/statuses")
|
||||
latest = _latest_statuses(statuses)
|
||||
if set(latest) != ALLOWED_CONTEXTS or any(item.get("status", item.get("state")) != "success" or item.get("sha", head) != head or (item.get("creator") or {}).get("login") != self.grant.raw["status_identity"] or not str(item.get("description") or "").endswith(self.grant.raw["status_evidence_sha256"][context]) for context, item in latest.items()):
|
||||
raise ContractDenied("MACHINE_STATUS_INVALID")
|
||||
self.journal.reserve("merge")
|
||||
self.api.post(f"/repos/aios/aios/pulls/{pr_number}/merge", {"Do": "fast-forward-only", "head_commit_id": head, "force_merge": False, "delete_branch_after_merge": False})
|
||||
observed_pr = self.api.get(f"/repos/aios/aios/pulls/{pr_number}")
|
||||
observed_branch = self.api.get("/repos/aios/aios/branches/governance%2Fapprovals")
|
||||
if observed_pr.get("merged") is not True or observed_pr.get("merge_commit_sha") != head or (observed_branch.get("commit") or {}).get("id") != head:
|
||||
raise ContractDenied("FAST_FORWARD_RESULT_MISMATCH")
|
||||
receipt = {"status": "PASS", "operation": "fast_forward_merge", "grant_sha256": self.grant.digest, "pr_number": pr_number, "governance_oid": head, "merge_method": "fast-forward-only", "review_timestamp_raw": review["review_timestamp_raw"], "review_timestamp_utc": review["review_timestamp_utc"]}
|
||||
receipt["audit_signature"] = hmac.new(self.audit_key, canonical(receipt), hashlib.sha256).hexdigest()
|
||||
receipt["audit_chain_hash"] = self.journal.append(receipt)
|
||||
self.journal.commit("merge", receipt)
|
||||
return receipt
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="aios-task-status-merge-broker")
|
||||
parser.add_argument("--mode", required=True, choices=("status", "merge"))
|
||||
parser.add_argument("--grant", required=True, type=Path)
|
||||
parser.add_argument("--token", required=True, type=Path)
|
||||
parser.add_argument("--audit-key", required=True, type=Path)
|
||||
parser.add_argument("--grant-signature", required=True, type=Path)
|
||||
parser.add_argument("--allowed-signers", required=True, type=Path)
|
||||
parser.add_argument("--journal", required=True, type=Path)
|
||||
parser.add_argument("--evidence", type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
grant = load_signed_task_grant(args.grant, args.grant_signature, args.allowed_signers)
|
||||
journal = QuotaJournal(args.journal, grant)
|
||||
try:
|
||||
client = GiteaClient(args.token)
|
||||
key = read_runtime_secret(args.audit_key)
|
||||
if args.mode == "status":
|
||||
if args.evidence is None:
|
||||
raise ContractDenied("EVIDENCE_REQUIRED")
|
||||
result = StatusBroker(grant, journal, client, key).publish(json.loads(args.evidence.read_text(encoding="utf-8")))
|
||||
else:
|
||||
if args.evidence is not None:
|
||||
raise ContractDenied("EVIDENCE_FORBIDDEN")
|
||||
result = MergeBroker(grant, journal, client, key).merge()
|
||||
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
|
||||
return 0
|
||||
finally:
|
||||
journal.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP",
|
||||
"title": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP observed approval envelope",
|
||||
"description": "Versioned fail-closed validation envelope assembled from an immutable request plus observed Owner, Git, machine-gate, runtime, and mirror evidence.",
|
||||
"x-schema-version": 1,
|
||||
"x-schema-status": "CANDIDATE",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"document_kind",
|
||||
"request_id",
|
||||
"schema_id",
|
||||
"schema_version",
|
||||
"request_created_at",
|
||||
"request_expires_at",
|
||||
"requester_identity",
|
||||
"repository_identity",
|
||||
"approval_plan_path",
|
||||
"approval_plan_blob_oid",
|
||||
"approval_plan_sha256",
|
||||
"schema_document_sha256",
|
||||
"validator_policy_sha256",
|
||||
"required_machine_contexts",
|
||||
"governance_base_ref",
|
||||
"governance_base_oid",
|
||||
"request_head_ref",
|
||||
"request_head_oid",
|
||||
"code_base_ref",
|
||||
"code_base_oid",
|
||||
"candidate_commit_oid",
|
||||
"candidate_parent_oid",
|
||||
"candidate_tree_oid",
|
||||
"candidate_diff_sha256",
|
||||
"candidate_files_sha256",
|
||||
"target_ref",
|
||||
"target_initial_state",
|
||||
"provision_publisher",
|
||||
"provision_writer",
|
||||
"provision_credential_projection",
|
||||
"publisher_identity",
|
||||
"writer_identity",
|
||||
"credential_secret_ref",
|
||||
"publisher_scope",
|
||||
"writer_scope",
|
||||
"credential_scope",
|
||||
"observed_preapproval_operations",
|
||||
"allowed_operations",
|
||||
"allowed_operations_sha256",
|
||||
"denied_operations",
|
||||
"maximum_operation_count",
|
||||
"maximum_pr_count",
|
||||
"maximum_branch_count",
|
||||
"maximum_commit_count",
|
||||
"allowed_paths",
|
||||
"allowed_paths_sha256",
|
||||
"approved_at",
|
||||
"expires_at",
|
||||
"maximum_lifetime_seconds",
|
||||
"auto_revoke",
|
||||
"required_human_approvals",
|
||||
"allowed_human_approvers",
|
||||
"approved_by",
|
||||
"owner_review_id",
|
||||
"owner_review_commit_oid",
|
||||
"approval_plan_id",
|
||||
"approval_audit_event_id",
|
||||
"production_runtime_anchor_change",
|
||||
"production_writes_enabled",
|
||||
"tag_operations",
|
||||
"registry_operations",
|
||||
"baseline_operations",
|
||||
"deployment_operations",
|
||||
"force_push_allowed",
|
||||
"tag_rewrite_allowed",
|
||||
"direct_main_push_allowed"
|
||||
],
|
||||
"properties": {
|
||||
"document_kind": {"const": "ENVELOPE"},
|
||||
"request_id": {"type": "string", "minLength": 3, "maxLength": 192, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]+$"},
|
||||
"schema_id": {"const": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP"},
|
||||
"schema_version": {"const": 1},
|
||||
"request_created_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"request_expires_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"requester_identity": {"$ref": "#/$defs/identity"},
|
||||
"repository_identity": {"const": "aios/aios"},
|
||||
"approval_plan_path": {"type": "string", "pattern": "^approvals/governance-bootstrap/[A-Za-z0-9._-]+\\.json$"},
|
||||
"approval_plan_blob_oid": {"$ref": "#/$defs/oid"},
|
||||
"approval_plan_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"schema_document_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"validator_policy_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"required_machine_contexts": {
|
||||
"type": "array",
|
||||
"minItems": 5,
|
||||
"maxItems": 5,
|
||||
"uniqueItems": true,
|
||||
"items": {"enum": [
|
||||
"governance/authority-mirror",
|
||||
"governance/fault-injection",
|
||||
"governance/runtime-isolation",
|
||||
"governance/schema-contract",
|
||||
"governance/validator-security"
|
||||
]}
|
||||
},
|
||||
"governance_base_ref": {"const": "refs/heads/governance/approvals"},
|
||||
"governance_base_oid": {"$ref": "#/$defs/oid"},
|
||||
"request_head_ref": {"type": "string", "pattern": "^refs/heads/governance/request/[A-Za-z0-9._/-]+$"},
|
||||
"request_head_oid": {"$ref": "#/$defs/oid"},
|
||||
"code_base_ref": {"type": "string", "pattern": "^refs/heads/patch/[A-Za-z0-9._/-]+$"},
|
||||
"code_base_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_commit_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_parent_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_tree_oid": {"$ref": "#/$defs/oid"},
|
||||
"candidate_diff_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"candidate_files_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"target_ref": {"type": "string", "pattern": "^refs/heads/patch/[A-Za-z0-9._/-]+$"},
|
||||
"target_initial_state": {"const": "ABSENT"},
|
||||
"provision_publisher": {"const": true},
|
||||
"provision_writer": {"const": true},
|
||||
"provision_credential_projection": {"const": true},
|
||||
"publisher_identity": {"$ref": "#/$defs/identity"},
|
||||
"writer_identity": {"$ref": "#/$defs/identity"},
|
||||
"credential_secret_ref": {"type": "string", "pattern": "^(systemd-credential|secret-ref)://[A-Za-z0-9_.-]+$"},
|
||||
"publisher_scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["repository", "local_code_write", "administrator"],
|
||||
"properties": {
|
||||
"repository": {"const": "aios/aios"},
|
||||
"local_code_write": {"const": false},
|
||||
"administrator": {"const": false}
|
||||
}
|
||||
},
|
||||
"writer_scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["repository", "remote_credentials", "remote_push", "run_as_root"],
|
||||
"properties": {
|
||||
"repository": {"const": "aios/aios"},
|
||||
"remote_credentials": {"const": false},
|
||||
"remote_push": {"const": false},
|
||||
"run_as_root": {"const": false}
|
||||
}
|
||||
},
|
||||
"credential_scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"provider", "projection", "audience", "writer_visible",
|
||||
"runtime_anchor_visible", "expires_with_request", "revocation_required",
|
||||
"post_revoke_http_status"
|
||||
],
|
||||
"properties": {
|
||||
"provider": {"const": "systemd"},
|
||||
"projection": {"const": "LoadCredential"},
|
||||
"audience": {"$ref": "#/$defs/identity"},
|
||||
"writer_visible": {"const": false},
|
||||
"runtime_anchor_visible": {"const": false},
|
||||
"expires_with_request": {"const": true},
|
||||
"revocation_required": {"const": true},
|
||||
"post_revoke_http_status": {"const": 401}
|
||||
}
|
||||
},
|
||||
"observed_preapproval_operations": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"uniqueItems": true,
|
||||
"items": {"enum": [
|
||||
"create_governance_request_branch",
|
||||
"create_governance_approval_pr"
|
||||
]}
|
||||
},
|
||||
"allowed_operations": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"items": {"enum": [
|
||||
"provision_task_publisher",
|
||||
"provision_task_writer",
|
||||
"provision_task_credential_projection",
|
||||
"create_approved_code_branch",
|
||||
"push_approved_code_branch",
|
||||
"read_remote_refs",
|
||||
"revoke_task_credentials",
|
||||
"remove_task_identities"
|
||||
]}
|
||||
},
|
||||
"allowed_operations_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"denied_operations": {"type": "array", "minItems": 12, "uniqueItems": true, "items": {"type": "string"}},
|
||||
"maximum_operation_count": {"type": "integer", "minimum": 1, "maximum": 10},
|
||||
"maximum_pr_count": {"const": 1},
|
||||
"maximum_branch_count": {"const": 1},
|
||||
"maximum_commit_count": {"const": 1},
|
||||
"allowed_paths": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
|
||||
"allowed_paths_sha256": {"$ref": "#/$defs/sha256"},
|
||||
"approved_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"expires_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
|
||||
"maximum_lifetime_seconds": {"type": "integer", "minimum": 1, "maximum": 43200},
|
||||
"auto_revoke": {"const": true},
|
||||
"required_human_approvals": {"const": 1},
|
||||
"allowed_human_approvers": {"const": ["owner"]},
|
||||
"approved_by": {"const": "owner"},
|
||||
"owner_review_id": {"type": "integer", "minimum": 1},
|
||||
"owner_review_commit_oid": {"$ref": "#/$defs/oid"},
|
||||
"approval_plan_id": {"type": "string", "minLength": 3, "maxLength": 192},
|
||||
"approval_audit_event_id": {"type": "string", "minLength": 3, "maxLength": 192},
|
||||
"production_runtime_anchor_change": {"const": false},
|
||||
"production_writes_enabled": {"const": false},
|
||||
"tag_operations": {"const": false},
|
||||
"registry_operations": {"const": false},
|
||||
"baseline_operations": {"const": false},
|
||||
"deployment_operations": {"const": false},
|
||||
"force_push_allowed": {"const": false},
|
||||
"tag_rewrite_allowed": {"const": false},
|
||||
"direct_main_push_allowed": {"const": false}
|
||||
},
|
||||
"$defs": {
|
||||
"plan": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"document_kind", "schema_id", "schema_version", "payload_sha256",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"document_kind": {"const": "PLAN"},
|
||||
"schema_id": {"const": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP_PLAN"},
|
||||
"schema_version": {"const": 1},
|
||||
"payload_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"payload": {"$ref": "#/$defs/planPayload"}
|
||||
}
|
||||
},
|
||||
"planPayload": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"allowed_human_approvers", "allowed_operations", "allowed_operations_sha256",
|
||||
"allowed_paths", "allowed_paths_sha256", "approval_plan_id", "approval_plan_path",
|
||||
"approved_by", "auto_revoke", "baseline_operations", "candidate_commit_oid",
|
||||
"candidate_diff_sha256", "candidate_files_sha256", "candidate_parent_oid",
|
||||
"candidate_tree_oid", "code_base_oid", "code_base_ref", "credential_scope",
|
||||
"credential_secret_ref", "denied_operations", "deployment_operations",
|
||||
"direct_main_push_allowed", "expires_at", "force_push_allowed",
|
||||
"governance_base_oid", "governance_base_ref", "maximum_branch_count",
|
||||
"maximum_commit_count", "maximum_lifetime_seconds", "maximum_operation_count",
|
||||
"maximum_pr_count", "observed_preapproval_operations",
|
||||
"production_runtime_anchor_change", "production_writes_enabled",
|
||||
"provision_credential_projection", "provision_publisher", "provision_writer",
|
||||
"publisher_identity", "publisher_scope", "registry_operations",
|
||||
"repository_identity", "request_created_at", "request_expires_at",
|
||||
"request_head_ref", "request_id", "requester_identity",
|
||||
"required_human_approvals", "required_machine_contexts",
|
||||
"schema_document_sha256", "schema_id", "schema_version", "tag_operations",
|
||||
"tag_rewrite_allowed", "target_initial_state", "target_ref",
|
||||
"validator_policy_sha256", "writer_identity", "writer_scope"
|
||||
],
|
||||
"properties": {
|
||||
"allowed_human_approvers": {"$ref": "#/properties/allowed_human_approvers"},
|
||||
"allowed_operations": {"$ref": "#/properties/allowed_operations"},
|
||||
"allowed_operations_sha256": {"$ref": "#/properties/allowed_operations_sha256"},
|
||||
"allowed_paths": {"$ref": "#/properties/allowed_paths"},
|
||||
"allowed_paths_sha256": {"$ref": "#/properties/allowed_paths_sha256"},
|
||||
"approval_plan_id": {"$ref": "#/properties/approval_plan_id"},
|
||||
"approval_plan_path": {"$ref": "#/properties/approval_plan_path"},
|
||||
"approved_by": {"$ref": "#/properties/approved_by"},
|
||||
"auto_revoke": {"$ref": "#/properties/auto_revoke"},
|
||||
"baseline_operations": {"$ref": "#/properties/baseline_operations"},
|
||||
"candidate_commit_oid": {"$ref": "#/properties/candidate_commit_oid"},
|
||||
"candidate_diff_sha256": {"$ref": "#/properties/candidate_diff_sha256"},
|
||||
"candidate_files_sha256": {"$ref": "#/properties/candidate_files_sha256"},
|
||||
"candidate_parent_oid": {"$ref": "#/properties/candidate_parent_oid"},
|
||||
"candidate_tree_oid": {"$ref": "#/properties/candidate_tree_oid"},
|
||||
"code_base_oid": {"$ref": "#/properties/code_base_oid"},
|
||||
"code_base_ref": {"$ref": "#/properties/code_base_ref"},
|
||||
"credential_scope": {"$ref": "#/properties/credential_scope"},
|
||||
"credential_secret_ref": {"$ref": "#/properties/credential_secret_ref"},
|
||||
"denied_operations": {"$ref": "#/properties/denied_operations"},
|
||||
"deployment_operations": {"$ref": "#/properties/deployment_operations"},
|
||||
"direct_main_push_allowed": {"$ref": "#/properties/direct_main_push_allowed"},
|
||||
"expires_at": {"$ref": "#/properties/expires_at"},
|
||||
"force_push_allowed": {"$ref": "#/properties/force_push_allowed"},
|
||||
"governance_base_oid": {"$ref": "#/properties/governance_base_oid"},
|
||||
"governance_base_ref": {"$ref": "#/properties/governance_base_ref"},
|
||||
"maximum_branch_count": {"$ref": "#/properties/maximum_branch_count"},
|
||||
"maximum_commit_count": {"$ref": "#/properties/maximum_commit_count"},
|
||||
"maximum_lifetime_seconds": {"$ref": "#/properties/maximum_lifetime_seconds"},
|
||||
"maximum_operation_count": {"$ref": "#/properties/maximum_operation_count"},
|
||||
"maximum_pr_count": {"$ref": "#/properties/maximum_pr_count"},
|
||||
"observed_preapproval_operations": {"$ref": "#/properties/observed_preapproval_operations"},
|
||||
"production_runtime_anchor_change": {"$ref": "#/properties/production_runtime_anchor_change"},
|
||||
"production_writes_enabled": {"$ref": "#/properties/production_writes_enabled"},
|
||||
"provision_credential_projection": {"$ref": "#/properties/provision_credential_projection"},
|
||||
"provision_publisher": {"$ref": "#/properties/provision_publisher"},
|
||||
"provision_writer": {"$ref": "#/properties/provision_writer"},
|
||||
"publisher_identity": {"$ref": "#/properties/publisher_identity"},
|
||||
"publisher_scope": {"$ref": "#/properties/publisher_scope"},
|
||||
"registry_operations": {"$ref": "#/properties/registry_operations"},
|
||||
"repository_identity": {"$ref": "#/properties/repository_identity"},
|
||||
"request_created_at": {"$ref": "#/properties/request_created_at"},
|
||||
"request_expires_at": {"$ref": "#/properties/request_expires_at"},
|
||||
"request_head_ref": {"$ref": "#/properties/request_head_ref"},
|
||||
"request_id": {"$ref": "#/properties/request_id"},
|
||||
"requester_identity": {"$ref": "#/properties/requester_identity"},
|
||||
"required_human_approvals": {"$ref": "#/properties/required_human_approvals"},
|
||||
"required_machine_contexts": {"$ref": "#/properties/required_machine_contexts"},
|
||||
"schema_document_sha256": {"$ref": "#/properties/schema_document_sha256"},
|
||||
"schema_id": {"$ref": "#/properties/schema_id"},
|
||||
"schema_version": {"$ref": "#/properties/schema_version"},
|
||||
"tag_operations": {"$ref": "#/properties/tag_operations"},
|
||||
"tag_rewrite_allowed": {"$ref": "#/properties/tag_rewrite_allowed"},
|
||||
"target_initial_state": {"$ref": "#/properties/target_initial_state"},
|
||||
"target_ref": {"$ref": "#/properties/target_ref"},
|
||||
"validator_policy_sha256": {"$ref": "#/properties/validator_policy_sha256"},
|
||||
"writer_identity": {"$ref": "#/properties/writer_identity"},
|
||||
"writer_scope": {"$ref": "#/properties/writer_scope"}
|
||||
}
|
||||
},
|
||||
"oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"identity": {"type": "string", "minLength": 3, "maxLength": 128, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]+$"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "AIOS_TASK_STATUS_MERGE_GRANT_V1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_id", "schema_version", "repository", "pr_number", "base_oid",
|
||||
"target_commit", "approved_tree_oid", "manifest_path", "manifest_blob_oid", "manifest_sha256",
|
||||
"payload_sha256", "status_identity", "merge_identity", "status_contexts",
|
||||
"status_write_quota", "merge_quota", "merge_method", "branch_push_allowed",
|
||||
"tag_write_allowed", "main_write_allowed", "registry_write_allowed",
|
||||
"baseline_write_allowed", "production_write_allowed", "issued_at", "expires_at",
|
||||
"audit_key_ref", "grant_signer_identity", "grant_signature_namespace",
|
||||
"status_evidence_sha256", "supersedes_pr_number", "supersedes_head_oid",
|
||||
"renewal_required", "timestamp_contract", "canonical_timezone",
|
||||
"raw_timestamp_preserved", "naive_datetime_allowed"
|
||||
],
|
||||
"properties": {
|
||||
"schema_id": {"const": "AIOS_TASK_STATUS_MERGE_GRANT"},
|
||||
"schema_version": {"const": 1},
|
||||
"repository": {"const": "aios/aios"},
|
||||
"pr_number": {"type": "integer", "minimum": 1},
|
||||
"base_oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"target_commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"approved_tree_oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"manifest_path": {"type": "string", "pattern": "^approvals/[A-Za-z0-9._/-]+\\.json$"},
|
||||
"manifest_blob_oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"manifest_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"payload_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"status_evidence_sha256": {
|
||||
"type": "object", "additionalProperties": false,
|
||||
"required": ["aios/branch-protection", "aios/evidence-closure", "aios/secret-scan", "aios/state-machine", "aios/validator"],
|
||||
"properties": {
|
||||
"aios/branch-protection": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/evidence-closure": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/secret-scan": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/state-machine": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"aios/validator": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
|
||||
}
|
||||
},
|
||||
"supersedes_pr_number": {"const": 36},
|
||||
"supersedes_head_oid": {"const": "a8644fc172bd8ebbba17a16e6bf586c93fd1e793"},
|
||||
"renewal_required": {"const": true},
|
||||
"timestamp_contract": {"const": "RFC3339_OFFSET_AWARE_V2"},
|
||||
"canonical_timezone": {"const": "UTC"},
|
||||
"raw_timestamp_preserved": {"const": true},
|
||||
"naive_datetime_allowed": {"const": false},
|
||||
"status_identity": {"enum": ["codex", "gpt"]},
|
||||
"merge_identity": {"enum": ["codex", "gpt"]},
|
||||
"status_contexts": {
|
||||
"type": "array", "minItems": 5, "maxItems": 5, "uniqueItems": true,
|
||||
"items": {"enum": ["aios/branch-protection", "aios/evidence-closure", "aios/secret-scan", "aios/state-machine", "aios/validator"]}
|
||||
},
|
||||
"status_write_quota": {"const": 5},
|
||||
"merge_quota": {"const": 1},
|
||||
"merge_method": {"const": "fast-forward-only"},
|
||||
"branch_push_allowed": {"const": false},
|
||||
"tag_write_allowed": {"const": false},
|
||||
"main_write_allowed": {"const": false},
|
||||
"registry_write_allowed": {"const": false},
|
||||
"baseline_write_allowed": {"const": false},
|
||||
"production_write_allowed": {"const": false},
|
||||
"issued_at": {"type": "string", "format": "date-time"},
|
||||
"expires_at": {"type": "string", "format": "date-time"},
|
||||
"audit_key_ref": {"type": "string", "pattern": "^systemd-credential://[A-Za-z0-9_.-]+$"},
|
||||
"grant_signer_identity": {"const": "aios-task-grant-signer"},
|
||||
"grant_signature_namespace": {"const": "aios-task-status-merge-grant-v1"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
import urllib.error
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "services" / "aios-single-owner-governance"))
|
||||
|
||||
from aios_single_owner_governance.task_status_merge_broker import ( # noqa: E402
|
||||
ALLOWED_CONTEXTS,
|
||||
ContractDenied,
|
||||
MergeBroker,
|
||||
QuotaJournal,
|
||||
StatusBroker,
|
||||
TaskGrant,
|
||||
_time,
|
||||
_utc_timestamp,
|
||||
canonical,
|
||||
load_signed_task_grant,
|
||||
read_runtime_secret,
|
||||
)
|
||||
from aios_single_owner_governance.task_credential_broker import TaskCredentialBroker # noqa: E402
|
||||
from aios_single_owner_governance import task_credential_expiry_sweeper as sweeper # noqa: E402
|
||||
|
||||
|
||||
HEAD = "d" * 40
|
||||
OLD_HEAD = "a8644fc172bd8ebbba17a16e6bf586c93fd1e793"
|
||||
BASE = "8814dff20222625b60454c613b61ccf15fa20e51"
|
||||
|
||||
|
||||
def grant() -> dict:
|
||||
now = datetime.now(timezone.utc).replace(microsecond=0)
|
||||
value = {
|
||||
"schema_id": "AIOS_TASK_STATUS_MERGE_GRANT",
|
||||
"schema_version": 1,
|
||||
"repository": "aios/aios",
|
||||
"pr_number": 36,
|
||||
"base_oid": BASE,
|
||||
"target_commit": HEAD,
|
||||
"approved_tree_oid": "3649be2dac8fcea3a79cb1604b2cfb63f6cc0307",
|
||||
"manifest_path": "approvals/release-campaign/AIOS-V2R1C4-PHASE-B-RENEWAL.json",
|
||||
"manifest_blob_oid": "d6e4431992e4315799c7f0487f2b12a20d0e2890",
|
||||
"manifest_sha256": "2555c5e37f77f1d0aac51864733b55ee3e91169ae76bf67e8817de6d1874eea4",
|
||||
"payload_sha256": "4db7d3fd5aaa00a65eabfce7501f275b5f6c8a2587b6153fa8066ec0dafa5d6d",
|
||||
"status_evidence_sha256": {context: "1" * 64 for context in ALLOWED_CONTEXTS},
|
||||
"supersedes_pr_number": 36,
|
||||
"supersedes_head_oid": OLD_HEAD,
|
||||
"renewal_required": True,
|
||||
"timestamp_contract": "RFC3339_OFFSET_AWARE_V2",
|
||||
"canonical_timezone": "UTC",
|
||||
"raw_timestamp_preserved": True,
|
||||
"naive_datetime_allowed": False,
|
||||
"status_identity": "codex",
|
||||
"merge_identity": "codex",
|
||||
"status_contexts": sorted(ALLOWED_CONTEXTS),
|
||||
"status_write_quota": 5,
|
||||
"merge_quota": 1,
|
||||
"merge_method": "fast-forward-only",
|
||||
"branch_push_allowed": False,
|
||||
"tag_write_allowed": False,
|
||||
"main_write_allowed": False,
|
||||
"registry_write_allowed": False,
|
||||
"baseline_write_allowed": False,
|
||||
"production_write_allowed": False,
|
||||
"issued_at": now.isoformat().replace("+00:00", "Z"),
|
||||
"expires_at": (now + timedelta(hours=1)).isoformat().replace("+00:00", "Z"),
|
||||
"audit_key_ref": "systemd-credential://task-audit-hmac",
|
||||
"grant_signer_identity": "aios-task-grant-signer",
|
||||
"grant_signature_namespace": "aios-task-status-merge-grant-v1",
|
||||
}
|
||||
value["pr_number"] = 37
|
||||
return value
|
||||
|
||||
|
||||
class FakeAPI:
|
||||
def __init__(self, *, with_statuses: bool = False, fail_status_after: int | None = None,
|
||||
fail_after_merge: bool = False, review_timestamp: str | None = None,
|
||||
reviewer: str = "owner", review_commit: str = HEAD):
|
||||
self.posts: list[tuple[str, dict]] = []
|
||||
self.merged = False
|
||||
self.status_rows = [{"id": index + 1, "context": value, "status": "success", "sha": HEAD,
|
||||
"description": "task-scoped PASS evidence_sha256=" + "1" * 64,
|
||||
"creator": {"login": "codex"}} for index, value in enumerate(ALLOWED_CONTEXTS)] if with_statuses else []
|
||||
self.fail_status_after = fail_status_after
|
||||
self.fail_after_merge = fail_after_merge
|
||||
self.base = BASE
|
||||
self.review_timestamp = review_timestamp or datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
||||
self.reviewer = reviewer
|
||||
self.review_commit = review_commit
|
||||
|
||||
def get(self, path: str):
|
||||
if path.endswith("/reviews"):
|
||||
return [{
|
||||
"id": 76,
|
||||
"state": "APPROVED",
|
||||
"commit_id": self.review_commit,
|
||||
"official": True,
|
||||
"stale": False,
|
||||
"dismissed": False,
|
||||
"submitted_at": self.review_timestamp,
|
||||
"user": {"id": 2 if self.reviewer == "owner" else 42, "login": self.reviewer},
|
||||
}]
|
||||
if path.endswith("/statuses"):
|
||||
return list(self.status_rows)
|
||||
if path.endswith("/branches/governance%2Fapprovals"):
|
||||
return {"name": "governance/approvals", "commit": {"id": HEAD if self.merged else BASE}}
|
||||
if path.endswith("/pulls/37"):
|
||||
return {
|
||||
"number": 37,
|
||||
"state": "closed" if self.merged else "open",
|
||||
"merged": self.merged,
|
||||
"mergeable": True,
|
||||
"head": {"sha": HEAD},
|
||||
"base": {"sha": self.base, "ref": "governance/approvals"},
|
||||
"merge_commit_sha": HEAD if self.merged else None,
|
||||
}
|
||||
raise AssertionError(path)
|
||||
|
||||
def post(self, path: str, payload: dict):
|
||||
self.posts.append((path, payload))
|
||||
if path.endswith("/merge"):
|
||||
assert payload == {
|
||||
"Do": "fast-forward-only",
|
||||
"head_commit_id": HEAD,
|
||||
"force_merge": False,
|
||||
"delete_branch_after_merge": False,
|
||||
}
|
||||
self.merged = True
|
||||
if self.fail_after_merge:
|
||||
raise ContractDenied("SIMULATED_POST_MERGE_LOSS")
|
||||
return {"merged": True}
|
||||
if self.fail_status_after is not None and len(self.status_rows) >= self.fail_status_after:
|
||||
raise ContractDenied("SIMULATED_STATUS_FAILURE")
|
||||
row = {"id": len(self.posts), "context": payload["context"], "status": "success", "sha": HEAD,
|
||||
"description": payload["description"],
|
||||
"creator": {"login": "codex"}}
|
||||
self.status_rows.append(row)
|
||||
return row
|
||||
|
||||
|
||||
def evidence() -> list[dict]:
|
||||
return [{"context": value, "evidence_sha256": "1" * 64} for value in sorted(ALLOWED_CONTEXTS)]
|
||||
|
||||
|
||||
class TaskStatusMergeBrokerTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temporary.name)
|
||||
self.journals: list[QuotaJournal] = []
|
||||
|
||||
def tearDown(self):
|
||||
for journal in self.journals:
|
||||
journal.close()
|
||||
self.temporary.cleanup()
|
||||
|
||||
def journal(self, item: TaskGrant) -> QuotaJournal:
|
||||
value = QuotaJournal(self.root / "journal.sqlite3", item)
|
||||
self.journals.append(value)
|
||||
return value
|
||||
|
||||
def test_status_broker_publishes_exact_five_and_quota_is_single_use(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI()
|
||||
journal = self.journal(item)
|
||||
receipt = StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(receipt["status"], "PASS")
|
||||
self.assertEqual(receipt["published_count"], 5)
|
||||
self.assertEqual({entry[1]["context"] for entry in api.posts}, ALLOWED_CONTEXTS)
|
||||
with self.assertRaisesRegex(ContractDenied, "STATUS_QUOTA_EXHAUSTED"):
|
||||
StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
|
||||
def test_rfc3339_offset_parser_normalizes_to_utc(self):
|
||||
cases = {
|
||||
"2026-08-01T02:03:02Z": "2026-08-01T02:03:02Z",
|
||||
"2026-08-01T10:03:02+08:00": "2026-08-01T02:03:02Z",
|
||||
"2026-07-31T18:03:02-08:00": "2026-08-01T02:03:02Z",
|
||||
"2026-08-01T10:03:02.123456+08:00": "2026-08-01T02:03:02.123456Z",
|
||||
}
|
||||
for raw, expected in cases.items():
|
||||
with self.subTest(raw=raw):
|
||||
self.assertEqual(_utc_timestamp(_time(raw)), expected)
|
||||
|
||||
def test_rfc3339_offset_parser_rejects_ambiguous_or_invalid_values(self):
|
||||
invalid = [
|
||||
"2026-08-01T10:03:02", "2026-08-01", "2026-08-01T10:03:02+0800",
|
||||
"2026-08-01T10:03:02+25:00", "2026-08-01T10:03:02z",
|
||||
" 2026-08-01T10:03:02+08:00", "2026-08-01T10:03:02+08:00 ",
|
||||
None, 1, [],
|
||||
]
|
||||
for raw in invalid:
|
||||
with self.subTest(raw=raw):
|
||||
with self.assertRaisesRegex(ContractDenied, "TIMESTAMP_INVALID"):
|
||||
_time(raw) # type: ignore[arg-type]
|
||||
|
||||
def test_offset_review_preserves_raw_and_canonical_utc_in_receipt(self):
|
||||
value = grant()
|
||||
value["issued_at"] = "2026-08-01T00:00:00Z"
|
||||
value["expires_at"] = "2026-08-01T12:00:00Z"
|
||||
item = TaskGrant.from_dict(value, now=datetime(2026, 8, 1, 3, tzinfo=timezone.utc))
|
||||
api = FakeAPI(review_timestamp="2026-08-01T10:03:02+08:00")
|
||||
receipt = StatusBroker(item, self.journal(item), api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(receipt["review_timestamp_raw"], "2026-08-01T10:03:02+08:00")
|
||||
self.assertEqual(receipt["review_timestamp_utc"], "2026-08-01T02:03:02Z")
|
||||
|
||||
def test_review_expiry_boundary_is_fail_closed(self):
|
||||
value = grant()
|
||||
value["issued_at"] = "2026-08-01T00:00:00Z"
|
||||
value["expires_at"] = "2026-08-01T02:03:02Z"
|
||||
item = TaskGrant.from_dict(value, now=datetime(2026, 8, 1, 1, tzinfo=timezone.utc))
|
||||
for raw in ("2026-08-01T02:03:02Z", "2026-08-01T02:03:03Z"):
|
||||
with self.subTest(raw=raw), self.assertRaisesRegex(ContractDenied, "OWNER_APPROVAL_EXPIRED"):
|
||||
StatusBroker(item, self.journal(item), FakeAPI(review_timestamp=raw), b"audit-key").publish(evidence())
|
||||
|
||||
def test_wrong_reviewer_and_reviewed_head_remain_rejected(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
for api in (FakeAPI(reviewer="codex"), FakeAPI(review_commit="e" * 40)):
|
||||
with self.assertRaisesRegex(ContractDenied, "OWNER_EXACT_HEAD_APPROVAL_MISSING"):
|
||||
StatusBroker(item, self.journal(item), api, b"audit-key").publish(evidence())
|
||||
|
||||
def test_merge_broker_uses_fast_forward_only_and_binds_exact_head(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI(with_statuses=True)
|
||||
journal = self.journal(item)
|
||||
receipt = MergeBroker(item, journal, api, b"audit-key").merge()
|
||||
self.assertEqual(receipt["status"], "PASS")
|
||||
self.assertEqual(receipt["governance_oid"], HEAD)
|
||||
self.assertTrue(api.merged)
|
||||
with self.assertRaisesRegex(ContractDenied, "MERGE_QUOTA_EXHAUSTED"):
|
||||
MergeBroker(item, journal, FakeAPI(with_statuses=True), b"audit-key").merge()
|
||||
|
||||
def test_contract_rejects_scope_expansion_secret_and_wrong_identity(self):
|
||||
value = grant()
|
||||
value["main_write_allowed"] = True
|
||||
with self.assertRaisesRegex(ContractDenied, "WRITE_SCOPE_EXPANDED"):
|
||||
TaskGrant.from_dict(value)
|
||||
|
||||
def test_unbound_evidence_fails_closed(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
changed = evidence()
|
||||
changed[0]["evidence_sha256"] = "2" * 64
|
||||
with self.assertRaisesRegex(ContractDenied, "EVIDENCE_SET_INVALID"):
|
||||
StatusBroker(item, self.journal(item), FakeAPI(), b"audit-key").publish(changed)
|
||||
|
||||
def test_grant_requires_detached_asymmetric_signature(self):
|
||||
grant_path = self.root / "grant.json"
|
||||
signature_path = self.root / "grant.sig"
|
||||
allowed_signers = self.root / "allowed_signers"
|
||||
raw = canonical(grant()) + b"\n"
|
||||
grant_path.write_bytes(raw)
|
||||
signature_path.write_text("ssh-signature", encoding="utf-8")
|
||||
allowed_signers.write_text("aios-task-grant-signer ssh-ed25519 AAAA", encoding="utf-8")
|
||||
with mock.patch("aios_single_owner_governance.task_status_merge_broker.subprocess.run") as runner:
|
||||
item = load_signed_task_grant(grant_path, signature_path, allowed_signers)
|
||||
self.assertEqual(item.raw["pr_number"], 37)
|
||||
self.assertEqual(runner.call_args.kwargs["input"], raw)
|
||||
with mock.patch(
|
||||
"aios_single_owner_governance.task_status_merge_broker.subprocess.run",
|
||||
side_effect=subprocess.CalledProcessError(255, ["ssh-keygen"]),
|
||||
):
|
||||
with self.assertRaisesRegex(ContractDenied, "GRANT_SIGNATURE_INVALID"):
|
||||
load_signed_task_grant(grant_path, signature_path, allowed_signers)
|
||||
|
||||
def test_base_movement_blocks_status_publication_before_side_effect(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI()
|
||||
api.base = "c" * 40
|
||||
with self.assertRaisesRegex(ContractDenied, "PR_BINDING_INVALID"):
|
||||
StatusBroker(item, self.journal(item), api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(api.posts, [])
|
||||
|
||||
def test_credential_provision_rechecks_local_owner_and_refs_before_issuance(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "audit", b"audit-key")
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=0, create=True), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._safe_absolute"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._verify_local_governance_binding", side_effect=ContractDenied("LOCAL_OWNER_APPROVAL_MISSING")), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.subprocess.run") as runner:
|
||||
with self.assertRaisesRegex(ContractDenied, "LOCAL_OWNER_APPROVAL_MISSING"):
|
||||
broker.provision(
|
||||
identity="codex",
|
||||
token_name="pr37-dddddddddddd-status",
|
||||
encrypted_path=Path("/run/aios-task-credential-broker/status.cred"),
|
||||
)
|
||||
runner.assert_not_called()
|
||||
value = grant()
|
||||
value["status_identity"] = "gitops-bot"
|
||||
with self.assertRaisesRegex(ContractDenied, "IDENTITY_NOT_ALLOWED"):
|
||||
TaskGrant.from_dict(value)
|
||||
|
||||
def test_systemd_credential_projection_mode_and_name_are_supported_exactly(self):
|
||||
metadata = mock.Mock(st_mode=0o100440, st_uid=0, st_gid=0)
|
||||
credential = Path("/run/credentials/unit/gitea-token")
|
||||
with mock.patch.dict("os.environ", {"CREDENTIALS_DIRECTORY": str(credential.parent)}), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.open", return_value=7), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.fstat", return_value=metadata), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.read", return_value=b"x" * 40), \
|
||||
mock.patch("aios_single_owner_governance.task_status_merge_broker.os.close"):
|
||||
self.assertEqual(read_runtime_secret(credential), b"x" * 40)
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "provision-audit", b"audit-key")
|
||||
connection = mock.MagicMock()
|
||||
connection.execute.return_value.fetchall.return_value = [(101, 42)]
|
||||
issued = mock.Mock(stdout=b"x" * 40)
|
||||
encrypted = mock.Mock(stdout=b"")
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=0, create=True), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._safe_absolute"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._verify_local_governance_binding"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.os.chmod"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.subprocess.run", side_effect=[issued, encrypted]) as runner, \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.sqlite3.connect", return_value=connection):
|
||||
broker.provision(identity="codex", token_name="pr37-dddddddddddd-status", encrypted_path=Path("/run/aios-task-credential-broker/status.cred"))
|
||||
self.assertIn("--name=gitea-token", runner.call_args_list[1].args[0])
|
||||
|
||||
def test_expiry_sweeper_revokes_projected_token_after_grant_expiry(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
runtime_root = self.root / "runtime"
|
||||
credential_root = self.root / "credentials"
|
||||
task_dir = runtime_root / "task-37"
|
||||
audit_dir = task_dir / "audit"
|
||||
audit_dir.mkdir(parents=True)
|
||||
credential_root.mkdir()
|
||||
(task_dir / "grant.json").write_text("{}", encoding="utf-8")
|
||||
(task_dir / "grant.sig").write_text("signature", encoding="utf-8")
|
||||
token_name = "pr37-dddddddddddd-status"
|
||||
encrypted_path = credential_root / "status.cred"
|
||||
encrypted_path.write_text("encrypted", encoding="utf-8")
|
||||
(credential_root / "task-37-audit.cred").write_text("encrypted-audit-key", encoding="utf-8")
|
||||
real_broker = TaskCredentialBroker(item, audit_dir, b"audit-key")
|
||||
receipt = real_broker._audit({
|
||||
"status": "PASS", "operation": "credential_provision", "identity": "codex",
|
||||
"token_name": token_name, "token_id": 101, "token_uid": 7,
|
||||
"grant_sha256": item.digest,
|
||||
"credential_ref": "systemd-credential-encrypted://status.cred",
|
||||
})
|
||||
real_broker.validate_provision_receipt(receipt, encrypted_path)
|
||||
tampered = dict(receipt)
|
||||
tampered["token_id"] = 102
|
||||
with self.assertRaisesRegex(ContractDenied, "PROVISION_RECEIPT_INVALID"):
|
||||
real_broker.validate_provision_receipt(tampered, encrypted_path)
|
||||
fake_broker = mock.Mock()
|
||||
fake_broker.revoke.return_value = {"status": "PASS", "old_token_http_status": 401}
|
||||
with mock.patch.object(sweeper, "RUNTIME_ROOT", runtime_root), \
|
||||
mock.patch.object(sweeper, "CREDENTIAL_ROOT", credential_root), \
|
||||
mock.patch.object(sweeper.os, "geteuid", return_value=0, create=True), \
|
||||
mock.patch.object(sweeper, "load_signed_task_grant", return_value=item), \
|
||||
mock.patch.object(sweeper.subprocess, "run", return_value=mock.Mock(stdout=b"audit-key")), \
|
||||
mock.patch.object(sweeper, "TaskCredentialBroker", return_value=fake_broker) as broker_type:
|
||||
results = sweeper.sweep(runtime_root, self.root / "allowed_signers", now=item.expires_at)
|
||||
self.assertEqual(results, [{"status": "PASS", "old_token_http_status": 401}])
|
||||
broker_type.assert_called_once_with(item, audit_dir, b"audit-key")
|
||||
fake_broker.revoke.assert_called_once()
|
||||
|
||||
def test_revocation_is_exact_access_token_row_only_and_proves_http_401(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "revoke-audit", b"audit-key")
|
||||
token_name = "pr37-dddddddddddd-status"
|
||||
receipt = broker._audit({
|
||||
"status": "PASS", "operation": "credential_provision", "identity": "codex",
|
||||
"token_name": token_name, "token_id": 101, "token_uid": 7,
|
||||
"grant_sha256": item.digest,
|
||||
"credential_ref": "systemd-credential-encrypted://status.cred",
|
||||
})
|
||||
response = mock.MagicMock()
|
||||
response.__enter__.return_value.status = 200
|
||||
unauthorized = urllib.error.HTTPError("http://127.0.0.1:3000/api/v1/repos/aios/aios", 401, "Unauthorized", {}, None)
|
||||
write_connection = mock.MagicMock()
|
||||
write_connection.execute.return_value.rowcount = 1
|
||||
read_connection = mock.MagicMock()
|
||||
read_connection.execute.return_value.fetchone.return_value = (0,)
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=0, create=True), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker._safe_absolute"), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.subprocess.run", return_value=mock.Mock(stdout=b"x" * 40)), \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.sqlite3.connect", side_effect=[write_connection, read_connection]) as connect, \
|
||||
mock.patch("aios_single_owner_governance.task_credential_broker.urllib.request.urlopen", side_effect=[response, unauthorized]) as urlopen:
|
||||
result = broker.revoke(receipt=receipt, encrypted_path=Path("/run/aios-task-credential-broker/status.cred"))
|
||||
self.assertEqual(result["old_token_http_status"], 401)
|
||||
self.assertIn("/repos/aios/aios", urlopen.call_args_list[0].args[0].full_url)
|
||||
delete_sql = write_connection.execute.call_args_list[1].args[0]
|
||||
self.assertIn("DELETE FROM access_token", delete_sql)
|
||||
self.assertNotIn("commit_status", delete_sql)
|
||||
self.assertIn("mode=ro", connect.call_args_list[1].args[0])
|
||||
value = grant()
|
||||
value["token"] = "plaintext"
|
||||
with self.assertRaisesRegex(ContractDenied, "GRANT_FIELDS_INVALID"):
|
||||
TaskGrant.from_dict(value)
|
||||
|
||||
def test_audit_chain_is_content_addressed_and_contains_no_secret(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
journal = self.journal(item)
|
||||
receipt = StatusBroker(item, journal, FakeAPI(), b"audit-key").publish(evidence())
|
||||
serialized = json.dumps(receipt, sort_keys=True)
|
||||
self.assertNotIn("audit-key", serialized)
|
||||
self.assertEqual(len(receipt["audit_signature"]), 64)
|
||||
self.assertTrue(journal.verify_chain())
|
||||
|
||||
def test_credential_broker_is_root_only_and_never_returns_a_token(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
broker = TaskCredentialBroker(item, self.root / "audit", b"audit-key")
|
||||
with mock.patch("aios_single_owner_governance.task_credential_broker.os.geteuid", return_value=1000, create=True):
|
||||
with self.assertRaisesRegex(ContractDenied, "ROOT_BROKER_REQUIRED"):
|
||||
broker.provision(
|
||||
identity="codex",
|
||||
token_name="pr36-a8644fc172bd-status",
|
||||
encrypted_path=Path("/run/aios-task-credential-broker/status.cred"),
|
||||
)
|
||||
|
||||
def test_partial_status_side_effects_are_reconciled_without_duplicate_writes(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI(fail_status_after=2)
|
||||
journal = self.journal(item)
|
||||
with self.assertRaisesRegex(ContractDenied, "SIMULATED_STATUS_FAILURE"):
|
||||
StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(len(api.status_rows), 2)
|
||||
api.fail_status_after = None
|
||||
receipt = StatusBroker(item, journal, api, b"audit-key").publish(evidence())
|
||||
self.assertEqual(receipt["published_count"], 5)
|
||||
self.assertEqual(len(api.status_rows), 5)
|
||||
|
||||
def test_post_merge_response_loss_is_reconciled_from_exact_remote_ref(self):
|
||||
item = TaskGrant.from_dict(grant())
|
||||
api = FakeAPI(with_statuses=True, fail_after_merge=True)
|
||||
journal = self.journal(item)
|
||||
with self.assertRaisesRegex(ContractDenied, "SIMULATED_POST_MERGE_LOSS"):
|
||||
MergeBroker(item, journal, api, b"audit-key").merge()
|
||||
api.fail_after_merge = False
|
||||
receipt = MergeBroker(item, journal, api, b"audit-key").merge()
|
||||
self.assertTrue(receipt["reconciled"])
|
||||
self.assertEqual(receipt["governance_oid"], HEAD)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,228 +0,0 @@
|
||||
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)
|
||||
@@ -1,90 +0,0 @@
|
||||
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)
|
||||
@@ -1,83 +0,0 @@
|
||||
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"})
|
||||
@@ -0,0 +1,417 @@
|
||||
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()
|
||||
@@ -15,6 +15,10 @@ 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()
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user