Compare commits

...
33 changed files with 9174 additions and 58 deletions
@@ -0,0 +1,470 @@
#!/usr/bin/env python3
"""Canonical AIOS all-modified-files evidence packager.
The bundle contract is intentionally compatible with the historical
aios_package_all_modified_files_v1.sh output while keeping secret scanning in
version-controlled, tested code.
"""
from __future__ import annotations
from datetime import datetime, timezone
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import tempfile
import zipfile
from typing import Iterable
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TASK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
# A PEM private-key finding requires the complete PEM header. Source code,
# tests, docs, or error text containing only "BEGIN PRIVATE KEY" are not
# credentials and must not be rejected.
PEM_PRIVATE_KEY_RE = re.compile(
rb"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----",
re.IGNORECASE,
)
BEARER_RE = re.compile(
rb"authorization\s*:\s*bearer\s+[A-Za-z0-9._~+/=-]{20,}",
re.IGNORECASE,
)
KEY_VALUE_SECRET_RE = re.compile(
rb"(?:password|passwd|secret|api[_-]?key)\s*[:=]\s*[^\s,;]{12,}",
re.IGNORECASE,
)
DOCUMENT_SUFFIXES = {".md", ".rst", ".adoc", ".txt"}
DOCUMENT_PREFIXES = (
"README",
"CHANGELOG",
"RUNBOOK",
"ADR",
"ARCHITECTURE",
"DEPLOYMENT",
"TROUBLESHOOTING",
)
class PackagerError(RuntimeError):
pass
def sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def secret_finding(data: bytes) -> str | None:
if PEM_PRIVATE_KEY_RE.search(data):
return "PEM_PRIVATE_KEY"
if BEARER_RE.search(data):
return "AUTHORIZATION_BEARER"
if KEY_VALUE_SECRET_RE.search(data):
return "KEY_VALUE_SECRET"
return None
def scan_secret(data: bytes, label: str) -> None:
finding = secret_finding(data)
if finding is not None:
raise PackagerError(
f"MODIFIED_FILES_SECRET_SCAN_MATCH:{finding}:{label}"
)
def git_bytes(git_dir: Path, oid: str, path: str) -> bytes:
try:
return subprocess.check_output(
[
"/usr/bin/git",
f"--git-dir={git_dir}",
"show",
f"{oid}:{path}",
],
timeout=30,
)
except subprocess.CalledProcessError as exc:
raise PackagerError(
"MODIFIED_FILES_GIT_SHOW_FAILED:" + path
) from exc
def git_text(git_dir: Path, *args: str) -> str:
try:
return subprocess.check_output(
["/usr/bin/git", f"--git-dir={git_dir}", *args],
text=True,
timeout=30,
)
except subprocess.CalledProcessError as exc:
raise PackagerError(
"MODIFIED_FILES_GIT_COMMAND_FAILED:" + " ".join(args)
) from exc
def validate_lineage(git_dir: Path, base: str, candidate: str) -> None:
if OID_RE.fullmatch(base) is None or OID_RE.fullmatch(candidate) is None:
raise PackagerError("MODIFIED_FILES_OID_INVALID")
for oid in (base, candidate):
try:
subprocess.run(
[
"/usr/bin/git",
f"--git-dir={git_dir}",
"cat-file",
"-e",
f"{oid}^{{commit}}",
],
check=True,
timeout=30,
)
except subprocess.CalledProcessError as exc:
raise PackagerError("MODIFIED_FILES_COMMIT_MISSING:" + oid) from exc
if subprocess.run(
[
"/usr/bin/git",
f"--git-dir={git_dir}",
"merge-base",
"--is-ancestor",
base,
candidate,
],
check=False,
timeout=30,
).returncode != 0:
raise PackagerError("MODIFIED_FILES_LINEAGE_INVALID")
def safe_destination(root: Path, relative: str) -> Path:
path = Path(relative)
if path.is_absolute() or ".." in path.parts:
raise PackagerError("MODIFIED_FILE_PATH_INVALID:" + relative)
destination = root / path
destination.parent.mkdir(parents=True, exist_ok=True)
return destination
def is_document_path(path: str) -> bool:
value = Path(path)
if path.startswith("docs/"):
return True
if value.suffix.lower() in DOCUMENT_SUFFIXES:
return True
upper = value.name.upper()
return any(upper.startswith(prefix) for prefix in DOCUMENT_PREFIXES)
def diff_entries(
git_dir: Path,
base: str,
candidate: str,
before_root: Path,
after_root: Path,
) -> list[dict[str, object]]:
raw = git_text(
git_dir,
"diff",
"--name-status",
"-M",
"-C",
base,
candidate,
)
entries: list[dict[str, object]] = []
for line in raw.splitlines():
if not line.strip():
continue
parts = line.split("\t")
status = parts[0]
code = status[0]
old_path: str | None = None
new_path: str | None = None
if code in {"R", "C"}:
if len(parts) != 3:
raise PackagerError(
"MODIFIED_FILES_DIFF_PARSE_INVALID:" + line
)
old_path, new_path = parts[1], parts[2]
else:
if len(parts) != 2:
raise PackagerError(
"MODIFIED_FILES_DIFF_PARSE_INVALID:" + line
)
new_path = parts[1] if code != "D" else None
old_path = parts[1] if code != "A" else None
entry: dict[str, object] = {"status": status}
if old_path is not None:
entry["previous_path"] = old_path
before = git_bytes(git_dir, base, old_path)
scan_secret(before, "before:" + old_path)
safe_destination(before_root, old_path).write_bytes(before)
entry["before_sha256"] = sha256(before)
entry["before_bytes"] = len(before)
if new_path is not None:
entry["path"] = new_path
after = git_bytes(git_dir, candidate, new_path)
scan_secret(after, "after:" + new_path)
safe_destination(after_root, new_path).write_bytes(after)
entry["after_sha256"] = sha256(after)
entry["after_bytes"] = len(after)
entry["deleted"] = code == "D"
entry["added"] = code == "A"
entry["renamed"] = code == "R"
entry["copied"] = code == "C"
entries.append(entry)
return entries
def execution_entries(
extra_root: Path | None,
execution_root: Path,
) -> list[dict[str, object]]:
if extra_root is None or not extra_root.exists():
return []
if extra_root.is_symlink() or not extra_root.is_dir():
raise PackagerError("EXTRA_FILES_DIR_INVALID")
entries: list[dict[str, object]] = []
for path in sorted(extra_root.rglob("*")):
if path.is_symlink():
raise PackagerError(
"EXTRA_FILE_SYMLINK_REJECTED:" + str(path)
)
if not path.is_file():
continue
relative = path.relative_to(extra_root).as_posix()
data = path.read_bytes()
scan_secret(data, "execution:" + relative)
safe_destination(execution_root, relative).write_bytes(data)
entries.append(
{
"path": relative,
"sha256": sha256(data),
"bytes": len(data),
}
)
return entries
def write_checksums(bundle: Path) -> None:
checks: list[tuple[str, str]] = []
for path in sorted(bundle.rglob("*")):
if path.is_file() and path.name != "checksums.sha256":
checks.append(
(
sha256(path.read_bytes()),
path.relative_to(bundle).as_posix(),
)
)
(bundle / "checksums.sha256").write_text(
"".join(f"{digest} {name}\n" for digest, name in checks),
encoding="utf-8",
)
def verify_checksums(bundle: Path) -> None:
checksum_path = bundle / "checksums.sha256"
for line in checksum_path.read_text(encoding="utf-8").splitlines():
digest, relative = line.split(" ", 1)
path = bundle / relative
if not path.is_file() or sha256(path.read_bytes()) != digest:
raise PackagerError(
"MODIFIED_FILES_CHECKSUM_CLOSURE_INVALID:" + relative
)
def build_bundle(
*,
git_dir: Path,
base: str,
candidate: str,
task_id: str,
extra_dir: Path | None = None,
output_root: Path | None = None,
now: datetime | None = None,
) -> dict[str, object]:
if TASK_RE.fullmatch(task_id) is None:
raise PackagerError("TASK_ID_INVALID")
validate_lineage(git_dir, base, candidate)
timestamp = (now or datetime.now(timezone.utc)).strftime(
"%Y%m%dT%H%M%SZ"
)
safe_task = re.sub(r"[^A-Za-z0-9._-]+", "-", task_id).strip("-")
if output_root is None:
output_root = Path(
tempfile.mkdtemp(
prefix=f"aios-modified-files-bundle-{safe_task}-{timestamp}-",
dir="/var/tmp",
)
)
else:
output_root.mkdir(parents=True, exist_ok=True)
bundle = output_root / "bundle"
before_root = bundle / "repo" / "before"
after_root = bundle / "repo" / "after"
execution_root = bundle / "execution"
before_root.mkdir(parents=True)
after_root.mkdir(parents=True)
execution_root.mkdir(parents=True)
changes = diff_entries(
git_dir,
base,
candidate,
before_root,
after_root,
)
execution_files = execution_entries(extra_dir, execution_root)
document_changes = [
entry
for entry in changes
if is_document_path(
str(entry.get("path") or entry.get("previous_path") or "")
)
]
execution_documents = [
entry
for entry in execution_files
if is_document_path(str(entry["path"]))
]
documents_index = {
"schema": "aios.modified-documents.index.v1",
"task_id": task_id,
"base_oid": base,
"candidate_oid": candidate,
"repository_documents": document_changes,
"execution_documents": execution_documents,
"repository_document_count": len(document_changes),
"execution_document_count": len(execution_documents),
}
(bundle / "documents-index.json").write_text(
json.dumps(documents_index, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
manifest = {
"schema": "aios.all-modified-files.bundle.v1",
"task_id": task_id,
"base_oid": base,
"candidate_oid": candidate,
"generated_at": (
now or datetime.now(timezone.utc)
).isoformat(),
"changed_file_count": len(changes),
"execution_file_count": len(execution_files),
"repository_document_count": len(document_changes),
"execution_document_count": len(execution_documents),
"changes": changes,
"execution_files": execution_files,
}
(bundle / "all-modified-files-manifest.json").write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
write_checksums(bundle)
verify_checksums(bundle)
zip_name = (
f"AIOS_{task_id}_ALL_MODIFIED_FILES_{candidate[:12]}.zip"
.replace("/", "-")
)
zip_path = output_root / zip_name
with zipfile.ZipFile(
zip_path,
"w",
compression=zipfile.ZIP_DEFLATED,
) as archive:
for path in sorted(bundle.rglob("*")):
if path.is_symlink():
raise PackagerError(
"BUNDLE_SYMLINK_REJECTED:" + str(path)
)
if path.is_file():
archive.write(
path,
arcname=path.relative_to(bundle).as_posix(),
)
zip_sha = sha256(zip_path.read_bytes())
summary = {
"schema": "aios.all-modified-files.bundle-summary.v1",
"task_id": task_id,
"base_oid": base,
"candidate_oid": candidate,
"bundle_path": str(zip_path),
"bundle_sha256": zip_sha,
"changed_file_count": len(changes),
"execution_file_count": len(execution_files),
"repository_document_count": len(document_changes),
"execution_document_count": len(execution_documents),
}
(output_root / "ALL_MODIFIED_FILES_BUNDLE.json").write_text(
json.dumps(summary, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return summary
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("base")
parser.add_argument("candidate")
parser.add_argument("task_id")
parser.add_argument("extra_dir", nargs="?")
parser.add_argument(
"--authority-git-dir",
default=os.environ.get(
"AIOS_AUTHORITY_GIT_DIR",
"/data/aios-gitops/repo.git",
),
)
parser.add_argument("--output-root")
args = parser.parse_args()
try:
summary = build_bundle(
git_dir=Path(args.authority_git_dir),
base=args.base,
candidate=args.candidate,
task_id=args.task_id,
extra_dir=Path(args.extra_dir) if args.extra_dir else None,
output_root=(
Path(args.output_root) if args.output_root else None
),
)
except PackagerError as exc:
print("FATAL:" + str(exc))
return 20
print("ALL_MODIFIED_FILES_BUNDLE=PASS")
print("ALL_MODIFIED_FILES_BUNDLE_PATH=" + str(summary["bundle_path"]))
print("ALL_MODIFIED_FILES_BUNDLE_SHA256=" + str(summary["bundle_sha256"]))
print("CHANGED_FILE_COUNT=" + str(summary["changed_file_count"]))
print("EXECUTION_FILE_COUNT=" + str(summary["execution_file_count"]))
print(
"REPOSITORY_DOCUMENT_COUNT="
+ str(summary["repository_document_count"])
)
print(
"EXECUTION_DOCUMENT_COUNT="
+ str(summary["execution_document_count"])
)
print(
"ALL_MODIFIED_FILES_EVIDENCE="
+ str(Path(str(summary["bundle_path"])).parent)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,288 @@
"""Unified autonomous control-plane primitives.
This is deliberately a library, not another daemon. Task API, workers and host
recovery code consume the same Admission -> Invariant -> Repair semantics.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
from enum import Enum
import re
from typing import Callable, Iterable, Mapping, Sequence
from aios_capability_contract import (
capability_contract,
has_capabilities,
normalize_required_capabilities,
normalize_worker_capabilities,
registered_capability,
validate_capability_contract_registry,
)
class FailureClass(str, Enum):
CAPABILITY_CONTRACT_ERROR = "CAPABILITY_CONTRACT_ERROR"
CREDENTIAL_PROJECTION_ERROR = "CREDENTIAL_PROJECTION_ERROR"
DEPENDENCY_UNAVAILABLE = "DEPENDENCY_UNAVAILABLE"
POLICY_DENIED = "POLICY_DENIED"
RESOURCE_UNAVAILABLE = "RESOURCE_UNAVAILABLE"
WORKER_CRASH = "WORKER_CRASH"
LEASE_LOST = "LEASE_LOST"
BUSINESS_PERMANENT = "BUSINESS_PERMANENT"
UNKNOWN = "UNKNOWN"
class RepairLevel(int, Enum):
AUTO_LOCAL = 0
AUTO_REPAIR_TASK = 1
OWNER_APPROVAL = 2
MANUAL = 3
@dataclass(frozen=True)
class InvariantResult:
name: str
passed: bool
detail: str = ""
def as_dict(self) -> dict[str, object]:
return asdict(self)
@dataclass(frozen=True)
class AdmissionDecision:
admitted: bool
state: str
invariants: tuple[InvariantResult, ...]
failure_class: FailureClass | None = None
repair_level: RepairLevel | None = None
repair_action: str | None = None
def as_dict(self) -> dict[str, object]:
return {
"schema": "aios.runtime-admission.decision.v1",
"admitted": self.admitted,
"state": self.state,
"invariants": [item.as_dict() for item in self.invariants],
"failure_class": None if self.failure_class is None else self.failure_class.value,
"repair_level": None if self.repair_level is None else int(self.repair_level),
"repair_action": self.repair_action,
}
@dataclass(frozen=True)
class RepairDecision:
failure_class: FailureClass
level: RepairLevel
action: str
consumes_business_attempt: bool
def as_dict(self) -> dict[str, object]:
return {
"failure_class": self.failure_class.value,
"level": int(self.level),
"action": self.action,
"consumes_business_attempt": self.consumes_business_attempt,
}
def classify_failure(error: object, *, phase: str = "") -> FailureClass:
text = (str(error) + " " + str(phase)).lower()
if any(token in text for token in (
"credential", "permission denied", "token registry", "secret", "unauthorized"
)):
return FailureClass.CREDENTIAL_PROJECTION_ERROR
if any(token in text for token in (
"transport", "unavailable", "timeout", "connection", "bad gateway", "http_5"
)):
return FailureClass.DEPENDENCY_UNAVAILABLE
if any(token in text for token in (
"capability", "eligible", "matcher", "contract"
)):
return FailureClass.CAPABILITY_CONTRACT_ERROR
if any(token in text for token in (
"policy", "forbidden", "approval required", "not authorized"
)):
return FailureClass.POLICY_DENIED
if any(token in text for token in ("lease lost", "lease_expired", "heartbeat_failed")):
return FailureClass.LEASE_LOST
if any(token in text for token in ("out of memory", "disk full", "resource")):
return FailureClass.RESOURCE_UNAVAILABLE
if any(token in text for token in ("worker crash", "process exited", "signal")):
return FailureClass.WORKER_CRASH
if any(token in text for token in ("invalid release", "business permanent")):
return FailureClass.BUSINESS_PERMANENT
return FailureClass.UNKNOWN
def repair_for_failure(failure: FailureClass) -> RepairDecision:
table = {
FailureClass.CAPABILITY_CONTRACT_ERROR: RepairDecision(
failure, RepairLevel.AUTO_REPAIR_TASK,
"create_same_lineage_capability_contract_repair", False,
),
FailureClass.CREDENTIAL_PROJECTION_ERROR: RepairDecision(
failure, RepairLevel.AUTO_REPAIR_TASK,
"restore_service_private_credential_projection", False,
),
FailureClass.DEPENDENCY_UNAVAILABLE: RepairDecision(
failure, RepairLevel.AUTO_LOCAL,
"retry_dependency_health_and_resume", False,
),
FailureClass.RESOURCE_UNAVAILABLE: RepairDecision(
failure, RepairLevel.AUTO_LOCAL,
"heal_runtime_resource_and_resume", False,
),
FailureClass.WORKER_CRASH: RepairDecision(
failure, RepairLevel.AUTO_LOCAL,
"restart_worker_and_resume_same_lineage", False,
),
FailureClass.LEASE_LOST: RepairDecision(
failure, RepairLevel.AUTO_LOCAL,
"recover_lease_and_resume_same_lineage", False,
),
FailureClass.POLICY_DENIED: RepairDecision(
failure, RepairLevel.OWNER_APPROVAL,
"request_bound_owner_authorization", False,
),
FailureClass.BUSINESS_PERMANENT: RepairDecision(
failure, RepairLevel.MANUAL,
"terminal_business_failure", True,
),
FailureClass.UNKNOWN: RepairDecision(
failure, RepairLevel.AUTO_REPAIR_TASK,
"create_diagnostic_repair_task", False,
),
}
return table[failure]
def _probe(
name: str,
probes: Mapping[str, Callable[[], bool]],
) -> InvariantResult:
fn = probes.get(name)
if fn is None:
return InvariantResult(name, False, "PROBE_MISSING")
try:
passed = bool(fn())
except Exception as exc:
return InvariantResult(name, False, "PROBE_ERROR:" + type(exc).__name__)
return InvariantResult(name, passed, "PASS" if passed else "NOT_READY")
def runtime_admission(
*,
worker_capabilities: Iterable[object],
required_capabilities: Iterable[object],
requested_operations: Sequence[str] = (),
probes: Mapping[str, Callable[[], bool]] | None = None,
) -> AdmissionDecision:
"""Admission is evaluated before claim; failures do not consume attempts."""
worker_values = tuple(worker_capabilities)
required_values = tuple(required_capabilities)
probe_map = probes or {}
invariants: list[InvariantResult] = []
registry = validate_capability_contract_registry()
invariants.append(InvariantResult(
"capability_contract_registry",
bool(registry.get("valid")),
"PASS" if registry.get("valid") else "REGISTRY_INVALID",
))
task_known = all(registered_capability(item) for item in required_values)
worker_known = all(registered_capability(item, worker=True) for item in worker_values)
invariants.extend((
InvariantResult("task_capabilities_registered", task_known, "PASS" if task_known else "UNKNOWN_TASK_CAPABILITY"),
InvariantResult("worker_capabilities_registered", worker_known, "PASS" if worker_known else "UNKNOWN_WORKER_CAPABILITY"),
))
capability_match = (
task_known
and worker_known
and has_capabilities(worker_values, required_values)
)
invariants.append(InvariantResult(
"capability_match",
capability_match,
"PASS" if capability_match else "WORKER_NOT_ELIGIBLE",
))
normalized = normalize_required_capabilities(required_values)
contracts = [
capability_contract(item)
for item in normalized
if not str(item).startswith("exact:")
]
contracts = [item for item in contracts if item is not None]
forbidden: set[str] = set()
dependencies: set[str] = set()
health: set[str] = set()
for contract in contracts:
forbidden.update(contract.forbidden)
dependencies.update(contract.dependencies)
health.update(contract.health_requirements)
requested = {str(item).strip().lower() for item in requested_operations if str(item).strip()}
forbidden_hit = sorted(requested & {item.lower() for item in forbidden})
invariants.append(InvariantResult(
"forbidden_operations_absent",
not forbidden_hit,
"PASS" if not forbidden_hit else "FORBIDDEN:" + ",".join(forbidden_hit),
))
for name in sorted(dependencies | health):
invariants.append(_probe(name, probe_map))
failed = [item for item in invariants if not item.passed]
if not failed:
return AdmissionDecision(
True,
"EXECUTION_READY",
tuple(invariants),
)
details = " ".join(item.name + ":" + item.detail for item in failed)
failure = classify_failure(details, phase="admission")
if any(item.name == "forbidden_operations_absent" and not item.passed for item in failed):
failure = FailureClass.POLICY_DENIED
elif any("credential" in item.name for item in failed):
failure = FailureClass.CREDENTIAL_PROJECTION_ERROR
elif any(item.name in {"capability_contract_registry", "task_capabilities_registered", "worker_capabilities_registered", "capability_match"} for item in failed):
failure = FailureClass.CAPABILITY_CONTRACT_ERROR
elif any(item.name in dependencies or item.name in health for item in failed):
failure = FailureClass.DEPENDENCY_UNAVAILABLE
repair = repair_for_failure(failure)
return AdmissionDecision(
False,
"EXECUTION_READY_FAILED",
tuple(invariants),
failure,
repair.level,
repair.action,
)
def system_invariant_snapshot(
*,
worker_capabilities: Iterable[object],
required_capabilities: Iterable[object],
requested_operations: Sequence[str] = (),
probes: Mapping[str, Callable[[], bool]] | None = None,
) -> dict[str, object]:
decision = runtime_admission(
worker_capabilities=worker_capabilities,
required_capabilities=required_capabilities,
requested_operations=requested_operations,
probes=probes,
)
return {
"schema": "aios.system-invariants.v1",
"status": "HEALTHY" if decision.admitted else "DEGRADED",
"execution_ready": decision.admitted,
"admission": decision.as_dict(),
}
+173 -2
View File
@@ -3,6 +3,8 @@
from __future__ import annotations
from collections.abc import Iterable
from dataclasses import asdict, dataclass
from typing import Any
_TASK_ALIASES: dict[str, frozenset[str]] = {
@@ -37,6 +39,7 @@ _TASK_ALIASES: dict[str, frozenset[str]] = {
"gpu.train": frozenset({"legacy.gpu.train"}),
"agent.runtime.hermes": frozenset({"agent.runtime.hermes"}),
"agent.runtime.openclaw": frozenset({"agent.runtime.openclaw"}),
"release:candidate": frozenset({"release:candidate"}),
}
_WORKER_ALIASES: dict[str, frozenset[str]] = {
@@ -71,8 +74,172 @@ _WORKER_ALIASES: dict[str, frozenset[str]] = {
"gpu.train": frozenset({"legacy.gpu.train"}),
"agent.runtime.hermes": frozenset({"agent.runtime.hermes"}),
"agent.runtime.openclaw": frozenset({"agent.runtime.openclaw"}),
"release:candidate": frozenset({"release:candidate"}),
}
@dataclass(frozen=True)
class CapabilityContract:
"""Versioned runtime contract attached to one canonical semantic capability."""
name: str
min_version: int = 1
max_version: int = 1
permissions: tuple[str, ...] = ()
forbidden: tuple[str, ...] = ()
dependencies: tuple[str, ...] = ()
health_requirements: tuple[str, ...] = ()
def as_dict(self) -> dict[str, Any]:
return asdict(self)
# This registry is the semantic source of truth. Alias tables below are retained
# as compatibility adapters; startup/tests validate every alias target against
# this registry so a name cannot be accepted in one layer and be unknown in
# another.
_CAPABILITY_CONTRACTS: dict[str, CapabilityContract] = {
"repo.read": CapabilityContract("repo.read", permissions=("repository_read",)),
"test.execute": CapabilityContract("test.execute", permissions=("test_execution",)),
"governed.git.write.branch": CapabilityContract(
"governed.git.write.branch",
permissions=("governed_branch_write",),
dependencies=("writer_lease", "governed_gitops"),
),
"governed.git.write.file": CapabilityContract(
"governed.git.write.file",
permissions=("governed_file_write",),
dependencies=("writer_lease", "governed_gitops"),
),
"governed.git.write": CapabilityContract(
"governed.git.write",
permissions=("governed_git_write",),
dependencies=("writer_lease", "governed_gitops"),
),
"runtime.shell": CapabilityContract("runtime.shell", permissions=("runtime_shell",)),
"runtime.git": CapabilityContract("runtime.git", permissions=("runtime_git",)),
"runtime.python": CapabilityContract("runtime.python", permissions=("runtime_python",)),
"legacy.code.read": CapabilityContract("legacy.code.read"),
"legacy.code.review": CapabilityContract("legacy.code.review"),
"legacy.code.write": CapabilityContract("legacy.code.write"),
"legacy.code.write.patch": CapabilityContract("legacy.code.write.patch"),
"legacy.test.python": CapabilityContract("legacy.test.python"),
"legacy.test.shell.allowlisted": CapabilityContract("legacy.test.shell.allowlisted"),
"legacy.git.commit": CapabilityContract("legacy.git.commit"),
"legacy.git.diff.read": CapabilityContract("legacy.git.diff.read"),
"legacy.git.force_push": CapabilityContract(
"legacy.git.force_push",
forbidden=("protected_ref_bypass",),
),
"legacy.deploy.production": CapabilityContract(
"legacy.deploy.production",
permissions=("production_deploy",),
dependencies=("owner_approval", "deployment_controller"),
health_requirements=("approval_envelope_verified", "runtime_acceptance_available"),
),
"legacy.secret.transform": CapabilityContract("legacy.secret.transform"),
"legacy.secure_file_transformer": CapabilityContract("legacy.secure_file_transformer"),
"legacy.runtime.shadow": CapabilityContract("legacy.runtime.shadow"),
"legacy.review.security": CapabilityContract("legacy.review.security"),
"legacy.review.api-contract": CapabilityContract("legacy.review.api-contract"),
"legacy.gpu.train": CapabilityContract("legacy.gpu.train"),
"agent.runtime.hermes": CapabilityContract(
"agent.runtime.hermes",
dependencies=("hermes_runtime",),
health_requirements=("worker_liveness",),
),
"agent.runtime.openclaw": CapabilityContract(
"agent.runtime.openclaw",
dependencies=("openclaw_runtime",),
health_requirements=("worker_liveness",),
),
"release:candidate": CapabilityContract(
"release:candidate",
min_version=1,
max_version=1,
permissions=("prepare_release",),
forbidden=(
"production_deploy",
"submit_owner_approval",
"tag_publish",
"baseline_registry_finalize",
"runtime_switch",
),
dependencies=(
"credential_projection",
"trusted_builder",
),
health_requirements=(
"authenticated_probe",
"release_api_health",
"worker_liveness",
),
),
}
def capability_contract(name: object) -> CapabilityContract | None:
"""Return the contract for one canonical or registered capability name."""
text = _clean(name)
direct = _CAPABILITY_CONTRACTS.get(text)
if direct is not None:
return direct
mapped = _TASK_ALIASES.get(text) or _WORKER_ALIASES.get(text)
if mapped is None or len(mapped) != 1:
return None
return _CAPABILITY_CONTRACTS.get(next(iter(mapped)))
def registered_capability(name: object, *, worker: bool = False) -> bool:
"""Fail-closed validation used by registration/task admission."""
text = _clean(name)
if "*" in text:
return False
aliases = _WORKER_ALIASES if worker else _TASK_ALIASES
mapped = aliases.get(text)
if mapped is None:
return text in _CAPABILITY_CONTRACTS
return all(item in _CAPABILITY_CONTRACTS for item in mapped)
def validate_capability_contract_registry() -> dict[str, object]:
"""Prove alias/contract closure and contract safety invariants."""
alias_targets: set[str] = set()
for mapping in (_TASK_ALIASES, _WORKER_ALIASES):
for values in mapping.values():
alias_targets.update(values)
missing = sorted(alias_targets - set(_CAPABILITY_CONTRACTS))
invalid_versions = sorted(
name
for name, contract in _CAPABILITY_CONTRACTS.items()
if contract.min_version < 1 or contract.max_version < contract.min_version
)
permission_forbidden_overlap = sorted(
name
for name, contract in _CAPABILITY_CONTRACTS.items()
if set(contract.permissions) & set(contract.forbidden)
)
return {
"schema": "aios.capability-contract-registry.v1",
"contract_count": len(_CAPABILITY_CONTRACTS),
"missing_alias_targets": missing,
"invalid_versions": invalid_versions,
"permission_forbidden_overlap": permission_forbidden_overlap,
"valid": not missing and not invalid_versions and not permission_forbidden_overlap,
}
def capability_contract_snapshot() -> dict[str, dict[str, Any]]:
return {
name: contract.as_dict()
for name, contract in sorted(_CAPABILITY_CONTRACTS.items())
}
_UNKNOWN_PREFIX = "exact:"
@@ -98,7 +265,11 @@ def normalize_required_capabilities(values: Iterable[object]) -> frozenset[str]:
if "*" in text:
result.add(_UNKNOWN_PREFIX + text)
else:
result.update(_TASK_ALIASES.get(text, frozenset({_UNKNOWN_PREFIX + text})))
mapped = _TASK_ALIASES.get(text)
if mapped is None or any(item not in _CAPABILITY_CONTRACTS for item in mapped):
result.add(_UNKNOWN_PREFIX + text)
else:
result.update(mapped)
return frozenset(result)
@@ -111,7 +282,7 @@ def normalize_worker_capabilities(values: Iterable[object]) -> frozenset[str]:
if "*" in text:
continue
mapped = _WORKER_ALIASES.get(text)
if mapped is not None:
if mapped is not None and all(item in _CAPABILITY_CONTRACTS for item in mapped):
result.update(mapped)
if {
"governed.git.write.branch",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,561 @@
#!/usr/bin/env python3
"""Owner-approved activation/rollback controller for the AIOS GPT HTTPS edge.
This controller never creates approval, TLS, or ingress credentials. It consumes
owner-managed inputs, applies one exact public-bind overlay, verifies the live
edge, and provides an idempotent rollback path.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import socket
import ssl
import stat
import subprocess
import tempfile
import time
import urllib.request
from typing import Any, Mapping
APPROVAL_SCHEMA = "aios.gpt-https-edge.public-approval.v1"
STATE_SCHEMA = "aios.gpt-https-edge.activation-state.v1"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
APPROVAL_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$")
DOMAIN_RE = re.compile(
r"(?=.{1,253}\Z)"
r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+"
r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?"
)
EDGE_UNIT = "aios-gpt-https-edge.service"
INGRESS_UNIT = "aios-gpt-online-task-ingress.service"
PUBLIC_HOST = "0.0.0.0"
PUBLIC_PORT = 18443
INGRESS_HEALTH = "http://127.0.0.1:18945/health"
DROPIN_NAME = "zzzzzzzzzz-owner-approved-public.conf"
class ActivationError(RuntimeError):
pass
def canonical(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def sha256_file(path: Path) -> str:
return sha256_bytes(path.read_bytes())
def parse_time(value: object, *, field: str) -> datetime:
if not isinstance(value, str) or not value.strip():
raise ActivationError("APPROVAL_" + field.upper() + "_INVALID")
text = value.strip()
if text.endswith("Z"):
text = text[:-1] + "+00:00"
try:
parsed = datetime.fromisoformat(text)
except ValueError as exc:
raise ActivationError("APPROVAL_" + field.upper() + "_INVALID") from exc
if parsed.tzinfo is None:
raise ActivationError("APPROVAL_" + field.upper() + "_INVALID")
return parsed.astimezone(timezone.utc)
def validate_domain(value: object) -> str:
if not isinstance(value, str):
raise ActivationError("APPROVAL_DOMAIN_INVALID")
domain = value.strip().lower()
if DOMAIN_RE.fullmatch(domain) is None:
raise ActivationError("APPROVAL_DOMAIN_INVALID")
return domain
def validate_root_file(
path: Path,
*,
exact_mode: int | None = None,
max_size: int = 256 * 1024,
) -> Path:
if path.is_symlink() or not path.is_file():
raise ActivationError("OWNER_FILE_INVALID")
info = path.stat()
if info.st_uid != 0 or info.st_gid != 0:
raise ActivationError("OWNER_FILE_OWNERSHIP_INVALID")
mode = stat.S_IMODE(info.st_mode)
if exact_mode is not None and mode != exact_mode:
raise ActivationError("OWNER_FILE_MODE_INVALID")
if info.st_size < 16 or info.st_size > max_size:
raise ActivationError("OWNER_FILE_SIZE_INVALID")
return path
def certificate_dns_names(cert_path: Path) -> tuple[str, ...]:
try:
decoded = ssl._ssl._test_decode_cert(str(cert_path))
except Exception as exc:
raise ActivationError("TLS_CERT_DECODE_INVALID") from exc
names = [
value
for kind, value in decoded.get("subjectAltName", ())
if kind == "DNS"
]
if not names:
for rdn in decoded.get("subject", ()):
for key, value in rdn:
if key == "commonName":
names.append(value)
return tuple(names)
def cert_matches_domain(cert_path: Path, domain: str) -> bool:
names = certificate_dns_names(cert_path)
try:
return bool(names) and any(ssl._dnsname_match(name, domain) for name in names)
except ssl.CertificateError:
return False
def validate_tls_pair(
cert_path: Path,
key_path: Path,
domain: str,
*,
now_fn=time.time,
) -> dict[str, Any]:
validate_root_file(cert_path)
validate_root_file(key_path, exact_mode=0o600)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.options |= ssl.OP_NO_COMPRESSION
try:
context.load_cert_chain(str(cert_path), str(key_path))
except Exception as exc:
raise ActivationError("TLS_CERT_KEY_PAIR_INVALID") from exc
if not cert_matches_domain(cert_path, domain):
raise ActivationError("TLS_CERT_DOMAIN_MISMATCH")
decoded = ssl._ssl._test_decode_cert(str(cert_path))
not_after = decoded.get("notAfter")
if not not_after:
raise ActivationError("TLS_CERT_EXPIRY_MISSING")
remaining = ssl.cert_time_to_seconds(not_after) - now_fn()
if remaining < 7 * 86400:
raise ActivationError("TLS_CERT_EXPIRY_TOO_CLOSE")
return {
"cert_sha256": sha256_file(cert_path),
"remaining_days": int(remaining // 86400),
}
@dataclass(frozen=True)
class Approval:
approval_id: str
candidate_oid: str
domain: str
bind_host: str
port: int
tls_cert_sha256: str
owner: str
issued_at: datetime
not_before: datetime
expires_at: datetime
raw_sha256: str
@classmethod
def load(
cls,
path: Path,
*,
candidate_oid: str,
cert_path: Path,
now: datetime | None = None,
) -> "Approval":
validate_root_file(path, exact_mode=0o600, max_size=64 * 1024)
try:
value = json.loads(path.read_text(encoding="utf-8"))
except Exception as exc:
raise ActivationError("APPROVAL_JSON_INVALID") from exc
if not isinstance(value, Mapping):
raise ActivationError("APPROVAL_JSON_INVALID")
allowed = {
"schema",
"approval_id",
"candidate_oid",
"domain",
"public_bind",
"bind_host",
"port",
"tls_cert_sha256",
"issued_at",
"not_before",
"expires_at",
"owner",
}
if set(value) != allowed or value.get("schema") != APPROVAL_SCHEMA:
raise ActivationError("APPROVAL_FIELDS_INVALID")
approval_id = value.get("approval_id")
if not isinstance(approval_id, str) or APPROVAL_ID_RE.fullmatch(approval_id) is None:
raise ActivationError("APPROVAL_ID_INVALID")
if not OID_RE.fullmatch(str(value.get("candidate_oid") or "")):
raise ActivationError("APPROVAL_CANDIDATE_INVALID")
if value["candidate_oid"] != candidate_oid:
raise ActivationError("APPROVAL_CANDIDATE_MISMATCH")
if value.get("public_bind") is not True:
raise ActivationError("APPROVAL_PUBLIC_BIND_REQUIRED")
if value.get("bind_host") != PUBLIC_HOST or value.get("port") != PUBLIC_PORT:
raise ActivationError("APPROVAL_BINDING_INVALID")
cert_sha = str(value.get("tls_cert_sha256") or "")
if SHA_RE.fullmatch(cert_sha) is None:
raise ActivationError("APPROVAL_CERT_SHA_INVALID")
if not cert_path.is_file() or sha256_file(cert_path) != cert_sha:
raise ActivationError("APPROVAL_CERT_SHA_MISMATCH")
domain = validate_domain(value.get("domain"))
owner = value.get("owner")
if not isinstance(owner, str) or not owner.strip() or len(owner) > 255:
raise ActivationError("APPROVAL_OWNER_INVALID")
issued_at = parse_time(value.get("issued_at"), field="issued_at")
not_before = parse_time(value.get("not_before"), field="not_before")
expires_at = parse_time(value.get("expires_at"), field="expires_at")
current = now or datetime.now(timezone.utc)
if not (issued_at <= not_before <= expires_at):
raise ActivationError("APPROVAL_TIME_ORDER_INVALID")
if current < not_before:
raise ActivationError("APPROVAL_NOT_YET_VALID")
if current >= expires_at:
raise ActivationError("APPROVAL_EXPIRED")
if expires_at - issued_at > __import__("datetime").timedelta(days=7):
raise ActivationError("APPROVAL_VALIDITY_TOO_LONG")
return cls(
approval_id=approval_id,
candidate_oid=candidate_oid,
domain=domain,
bind_host=PUBLIC_HOST,
port=PUBLIC_PORT,
tls_cert_sha256=cert_sha,
owner=owner.strip(),
issued_at=issued_at,
not_before=not_before,
expires_at=expires_at,
raw_sha256=sha256_file(path),
)
def validate_release(release_root: Path) -> dict[str, Path]:
if release_root.is_symlink() or not release_root.is_dir():
raise ActivationError("IMMUTABLE_RELEASE_INVALID")
edge = release_root / "active-runtime/app/aios_gpt_https_edge.py"
controller = release_root / "active-runtime/app/aios_gpt_https_edge_activation.py"
for path in (edge, controller):
if path.is_symlink() or not path.is_file():
raise ActivationError("IMMUTABLE_RELEASE_INVALID")
return {"edge": edge, "controller": controller}
def render_dropin(approval: Approval, release_root: Path) -> str:
app = release_root / "active-runtime/app/aios_gpt_https_edge.py"
return (
"[Service]\n"
f"Environment=AIOS_GPT_EDGE_HOST={approval.bind_host}\n"
f"Environment=AIOS_GPT_EDGE_PORT={approval.port}\n"
"Environment=AIOS_GPT_EDGE_PUBLIC_BIND_APPROVED=1\n"
f"Environment=AIOS_GPT_EDGE_PUBLIC_DOMAIN={approval.domain}\n"
"ExecStart=\n"
f"ExecStart=/usr/bin/python3 -B {app}\n"
f"ReadOnlyPaths={release_root}\n"
)
def atomic_write(path: Path, data: str, mode: int = 0o600) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix="." + path.name + ".", dir=path.parent)
try:
os.fchmod(fd, mode)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
handle.write(data)
handle.flush()
os.fsync(handle.fileno())
os.replace(name, path)
finally:
if os.path.exists(name):
os.unlink(name)
class Systemd:
def run(self, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["/usr/bin/systemctl", *args],
check=check,
text=True,
capture_output=True,
timeout=30,
)
def active(self, unit: str) -> bool:
return self.run("is-active", "--quiet", unit, check=False).returncode == 0
def enabled(self, unit: str) -> bool:
return self.run("is-enabled", "--quiet", unit, check=False).returncode == 0
def show(self, unit: str, prop: str) -> str:
return self.run("show", unit, f"--property={prop}", "--value").stdout.strip()
def require_ingress_ready(systemd: Systemd) -> None:
if not systemd.active(INGRESS_UNIT):
raise ActivationError("INGRESS_SERVICE_NOT_ACTIVE")
try:
with urllib.request.urlopen(INGRESS_HEALTH, timeout=5) as response:
if response.status != 200:
raise ActivationError("INGRESS_HEALTH_FAILED")
value = json.loads(response.read().decode())
except ActivationError:
raise
except Exception as exc:
raise ActivationError("INGRESS_HEALTH_FAILED") from exc
if value.get("status") != "ok":
raise ActivationError("INGRESS_HEALTH_FAILED")
def tls_probe(domain: str, cert_path: Path, *, port: int = PUBLIC_PORT) -> None:
context = ssl.create_default_context(cafile=str(cert_path))
context.check_hostname = True
context.verify_mode = ssl.CERT_REQUIRED
try:
with socket.create_connection(("127.0.0.1", port), timeout=5) as sock:
with context.wrap_socket(sock, server_hostname=domain) as tls_sock:
if tls_sock.version() not in {"TLSv1.2", "TLSv1.3"}:
raise ActivationError("EDGE_TLS_VERSION_INVALID")
except ActivationError:
raise
except Exception as exc:
raise ActivationError("EDGE_TLS_PROBE_FAILED") from exc
def receipt_for(
approval: Approval,
release_root: Path,
*,
state: str,
) -> dict[str, Any]:
return {
"schema": STATE_SCHEMA,
"state": state,
"approval_id": approval.approval_id,
"approval_sha256": approval.raw_sha256,
"candidate_oid": approval.candidate_oid,
"domain": approval.domain,
"bind_host": approval.bind_host,
"port": approval.port,
"tls_cert_sha256": approval.tls_cert_sha256,
"release_root": str(release_root),
"updated_at": datetime.now(timezone.utc).isoformat(),
}
def activate(
*,
approval: Approval,
release_root: Path,
cert_path: Path,
key_path: Path,
dropin_path: Path,
state_path: Path,
systemd: Systemd,
) -> dict[str, Any]:
if os.geteuid() != 0:
raise ActivationError("ROOT_REQUIRED")
release = validate_release(release_root)
app = release["edge"]
require_ingress_ready(systemd)
validate_tls_pair(cert_path, key_path, approval.domain)
if state_path.is_file():
try:
existing = json.loads(state_path.read_text(encoding="utf-8"))
except Exception:
existing = {}
if (
existing.get("schema") == STATE_SCHEMA
and existing.get("state") == "ACTIVE"
and existing.get("approval_sha256") == approval.raw_sha256
and existing.get("candidate_oid") == approval.candidate_oid
and systemd.active(EDGE_UNIT)
):
return existing
dropin = render_dropin(approval, release_root)
modified = False
try:
atomic_write(dropin_path, dropin, mode=0o644)
modified = True
systemd.run("daemon-reload")
systemd.run("enable", EDGE_UNIT)
systemd.run("restart", EDGE_UNIT)
if not systemd.active(EDGE_UNIT):
raise ActivationError("EDGE_SERVICE_NOT_ACTIVE")
exec_start = systemd.show(EDGE_UNIT, "ExecStart")
environment = systemd.show(EDGE_UNIT, "Environment")
if str(app) not in exec_start:
raise ActivationError("EDGE_EFFECTIVE_EXECSTART_INVALID")
for expected in (
f"AIOS_GPT_EDGE_HOST={approval.bind_host}",
f"AIOS_GPT_EDGE_PORT={approval.port}",
"AIOS_GPT_EDGE_PUBLIC_BIND_APPROVED=1",
f"AIOS_GPT_EDGE_PUBLIC_DOMAIN={approval.domain}",
):
if expected not in environment:
raise ActivationError("EDGE_EFFECTIVE_ENVIRONMENT_INVALID")
tls_probe(approval.domain, cert_path, port=approval.port)
receipt = receipt_for(approval, release_root, state="ACTIVE")
atomic_write(state_path, json.dumps(receipt, indent=2, sort_keys=True) + "\n")
return receipt
except Exception:
if modified:
rollback(
dropin_path=dropin_path,
state_path=state_path,
systemd=systemd,
reason="activation_failed",
)
raise
def rollback(
*,
dropin_path: Path,
state_path: Path,
systemd: Systemd,
reason: str,
) -> dict[str, Any]:
if os.geteuid() != 0:
raise ActivationError("ROOT_REQUIRED")
systemd.run("stop", EDGE_UNIT, check=False)
systemd.run("disable", EDGE_UNIT, check=False)
dropin_path.unlink(missing_ok=True)
systemd.run("daemon-reload")
if systemd.active(EDGE_UNIT):
raise ActivationError("EDGE_ROLLBACK_SERVICE_STILL_ACTIVE")
previous: dict[str, Any] = {}
if state_path.is_file():
try:
previous = json.loads(state_path.read_text(encoding="utf-8"))
except Exception:
previous = {}
receipt = {
"schema": STATE_SCHEMA,
"state": "ROLLED_BACK",
"reason": reason[:255],
"candidate_oid": previous.get("candidate_oid"),
"approval_id": previous.get("approval_id"),
"domain": previous.get("domain"),
"updated_at": datetime.now(timezone.utc).isoformat(),
}
atomic_write(state_path, json.dumps(receipt, indent=2, sort_keys=True) + "\n")
return receipt
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=("check", "activate", "rollback"))
parser.add_argument("--candidate")
parser.add_argument("--approval", default="/etc/aios-gitops/gpt-https-edge.approved")
parser.add_argument("--tls-cert", default="/etc/aios-gitops/gpt-https-edge-tls.crt")
parser.add_argument("--tls-key", default="/etc/aios-gitops/gpt-https-edge-tls.key")
parser.add_argument("--release-root")
parser.add_argument(
"--dropin",
default=f"/etc/systemd/system/{EDGE_UNIT}.d/{DROPIN_NAME}",
)
parser.add_argument(
"--state",
default="/var/lib/aios-gpt-https-edge-activation/state.json",
)
parser.add_argument("--reason", default="owner_requested")
args = parser.parse_args()
systemd = Systemd()
if args.command == "rollback":
value = rollback(
dropin_path=Path(args.dropin),
state_path=Path(args.state),
systemd=systemd,
reason=args.reason,
)
print(json.dumps(value, indent=2, sort_keys=True))
return 0
candidate = str(args.candidate or "")
if OID_RE.fullmatch(candidate) is None:
raise SystemExit("CANDIDATE_REQUIRED")
release_root = (
Path(args.release_root)
if args.release_root
else Path(f"/opt/aios/releases/{candidate}-gpt-https-edge-public")
)
cert_path = Path(args.tls_cert)
key_path = Path(args.tls_key)
approval = Approval.load(
Path(args.approval),
candidate_oid=candidate,
cert_path=cert_path,
)
tls = validate_tls_pair(cert_path, key_path, approval.domain)
release = validate_release(release_root)
require_ingress_ready(systemd)
if args.command == "check":
value = {
"status": "READY",
"candidate_oid": candidate,
"approval_id": approval.approval_id,
"approval_sha256": approval.raw_sha256,
"domain": approval.domain,
"bind_host": approval.bind_host,
"port": approval.port,
"edge_sha256": sha256_file(release["edge"]),
"controller_sha256": sha256_file(release["controller"]),
**tls,
"public_network_mutation": False,
}
print(json.dumps(value, indent=2, sort_keys=True))
return 0
value = activate(
approval=approval,
release_root=release_root,
cert_path=cert_path,
key_path=key_path,
dropin_path=Path(args.dropin),
state_path=Path(args.state),
systemd=systemd,
)
print(json.dumps(value, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+198 -4
View File
@@ -4,6 +4,7 @@ import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, urllib.
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from baseline_authority import BaselineAuthority, BaselineAuthorityError
from silent_release_controller import ReleaseCandidate
from typing import Any
try:
@@ -28,11 +29,18 @@ from orchestrator.action_monitor import ActionMonitor
from silent_release_hardening import (
ApprovalKeyRing,
ApprovalRevocationStore,
BuilderAttestation,
BuilderAttestationKeyRing,
BuilderAttestationVerifier,
CapabilityAuthorizationError,
ReleasePlan,
SagaConflict,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TrustedCandidateBuilderUnavailable,
TrustedCandidateBundle,
TrustedReleasePolicy,
)
try:
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
@@ -82,13 +90,33 @@ SILENT_RELEASE_STATE_ROOT = Path(os.getenv(
'AIOS_SILENT_RELEASE_STATE_ROOT',
'/var/lib/aios-gitops/silent-release',
))
SILENT_RELEASE_EXECUTION_ENABLED = str(os.getenv(
'AIOS_SILENT_RELEASE_EXECUTION_ENABLED', 'false'
)).lower() in {'1', 'true', 'yes', 'on'}
# This immutable staging API can prepare only a shadow candidate. An
# environment value must not turn its HTTP process into a release executor.
SILENT_RELEASE_EXECUTION_ENABLED = False
SILENT_RELEASE_PUBLIC_KEYS = Path(os.getenv(
'AIOS_SILENT_RELEASE_PUBLIC_KEYS',
'/etc/aios-gitops/silent-release-owner-public-keys.json',
))
TRUSTED_BUILDER_ENABLED = str(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_ENABLED', 'false'
)).lower() in {'1', 'true', 'yes', 'on'}
TRUSTED_BUILDER_POLICY = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_POLICY', ''
))
TRUSTED_BUILDER_CREDENTIALS_DIRECTORY = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_CREDENTIALS_DIRECTORY',
os.getenv('CREDENTIALS_DIRECTORY', ''),
))
TRUSTED_BUILDER_SOCKET = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_SOCKET',
'/run/aios-trusted-candidate-builder.sock',
))
TRUSTED_BUILDER_PUBLIC_KEYRING = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_PUBLIC_KEYRING', ''
))
TRUSTED_RELEASE_POLICY = Path(os.getenv(
'AIOS_SILENT_RELEASE_TRUSTED_POLICY', ''
))
monitor = ActionMonitor()
BASE_MODELS = [
@@ -166,6 +194,79 @@ def _file_sha256(path: Path) -> str:
digest.update(chunk)
return digest.hexdigest()
def _trusted_release_policy_from_file(path: Path) -> TrustedReleasePolicy:
payload = json.loads(path.read_text(encoding='utf-8'))
required = {
'services', 'release_roots', 'cutover_links', 'dropin_paths',
'authoritative_git_dirs', 'repository_identities',
}
if not isinstance(payload, dict) or set(payload) != required:
raise ValueError('trusted release policy schema is invalid')
values = {}
for name in required:
raw = payload[name]
if not isinstance(raw, list) or not raw or not all(
isinstance(item, str) and item for item in raw
):
raise ValueError('trusted release policy values are invalid')
values[name] = frozenset(raw)
return TrustedReleasePolicy(**values)
class _HostTrustedCandidateBuilderAdapter:
"""Maps only a complete host-owned silent-release bundle response.
The API never manufactures candidate evidence or a builder signature. A
host builder protocol that returns a different response is therefore
unavailable, rather than silently downgraded to caller-owned evidence.
"""
def __init__(self, client):
self._client = client
def build(self, intent):
import secrets
from aios_trusted_candidate_builder import BuilderError
try:
version = str(intent['version'])
candidate_id = str(intent['candidate_id'])
source_commit = str(intent['source_commit'])
except (KeyError, TypeError) as exc:
raise CapabilityAuthorizationError(
'trusted builder requires a release worker request'
) from exc
try:
response = self._client.build(
version,
candidate_id,
source_commit,
nonce=secrets.token_hex(32),
)
except BuilderError as exc:
raise TrustedCandidateBuilderUnavailable(
'trusted builder request failed'
) from exc
raw_bundle = response.get('silent_release_bundle')
if not isinstance(raw_bundle, dict):
raise CapabilityAuthorizationError(
'trusted builder response is not a silent release bundle'
)
try:
return TrustedCandidateBundle(
candidate=ReleaseCandidate.from_dict(raw_bundle['candidate']),
release_plan=ReleasePlan.from_dict(raw_bundle['release_plan']),
checks=raw_bundle['checks'],
attestation=BuilderAttestation.from_dict(
raw_bundle['attestation'], require_fingerprint=True
),
)
except (KeyError, TypeError, ValueError) as exc:
raise CapabilityAuthorizationError(
'trusted builder response is invalid'
) from exc
def _build_silent_release_service() -> SilentReleaseActionService:
verifier=None
if SILENT_RELEASE_PUBLIC_KEYS.is_file():
@@ -177,10 +278,86 @@ def _build_silent_release_service() -> SilentReleaseActionService:
)
policy_path=WS / 'config' / 'gitops' / 'silent-release-policy.json'
policy_sha=_file_sha256(policy_path) if policy_path.is_file() else ''
candidate_builder = None
builder_attestation_verifier = None
trusted_policy = None
builder_readiness_probe = None
if TRUSTED_BUILDER_ENABLED:
# Every path is opt-in. In particular, this API receives its own
# client HMAC credential, never the builder's signing private key.
if not (
TRUSTED_BUILDER_POLICY.is_file()
and TRUSTED_BUILDER_CREDENTIALS_DIRECTORY.is_dir()
and TRUSTED_BUILDER_PUBLIC_KEYRING.is_file()
and TRUSTED_RELEASE_POLICY.is_file()
):
raise CapabilityAuthorizationError(
'trusted builder configuration is incomplete'
)
try:
from aios_trusted_candidate_builder import (
BuilderKeyring,
TrustedCandidateBuilderConfig,
UnixTrustedCandidateBuilderClient,
load_builder_policy,
readiness_probe,
)
builder_policy = load_builder_policy(TRUSTED_BUILDER_POLICY)
builder_config = TrustedCandidateBuilderConfig.from_policy(
builder_policy
)
client_hmac = (
TRUSTED_BUILDER_CREDENTIALS_DIRECTORY / 'builder_hmac'
).read_bytes().strip()
client_keyring = BuilderKeyring(builder_hmac=client_hmac)
client = UnixTrustedCandidateBuilderClient(
builder_config,
client_keyring,
socket_path=TRUSTED_BUILDER_SOCKET,
)
candidate_builder = _HostTrustedCandidateBuilderAdapter(client)
builder_attestation_verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing.from_file(
TRUSTED_BUILDER_PUBLIC_KEYRING
)
)
trusted_policy = _trusted_release_policy_from_file(
TRUSTED_RELEASE_POLICY
)
def builder_readiness_probe():
if not TRUSTED_BUILDER_SOCKET.exists():
return {'status': 'UNAVAILABLE'}
remote = dict(readiness_probe(client))
if remote.get('policy_sha256') != builder_config.policy_sha256:
return {'status': 'UNAVAILABLE'}
builder_attestation_verifier.keyring.require_active_identity(
key_id=str(remote.get('builder_key_id') or ''),
generation=remote.get('builder_key_generation'),
algorithm=str(remote.get('signature_algorithm') or ''),
purpose=str(remote.get('signature_purpose') or ''),
)
return {
**remote,
'hmac_credential_loaded': True,
'peer_verification_configured': True,
'public_key_trusted': True,
'policy_loaded': True,
}
except Exception as exc:
raise CapabilityAuthorizationError(
'trusted builder configuration is invalid'
) from exc
return SilentReleaseActionService(
SILENT_RELEASE_STATE_ROOT,
execution_enabled=SILENT_RELEASE_EXECUTION_ENABLED,
approval_verifier=verifier,
candidate_builder=candidate_builder,
builder_attestation_verifier=builder_attestation_verifier,
trusted_policy=trusted_policy,
builder_readiness_probe=builder_readiness_probe,
allow_legacy_candidate_input=False,
expected_repository=REPOSITORY_IDENTITY,
policy_sha256=policy_sha,
)
@@ -466,8 +643,11 @@ class H(BaseHTTPRequestHandler):
q=urllib.parse.parse_qs(u.query)
try:
if path in ('/health','/api/health'):
builder_readiness = silent_release_service().get_builder_readiness()
return self.sendj({
'status': 'ok',
'process_liveness': 'PASS',
'builder_readiness': builder_readiness,
'service': os.getenv('AIOS_GITOPS_MODEL_API_SERVICE', 'aios-gitops-model-api-v2r2c1-staging.service'),
'workspace': str(WS),
'version': 'V2R2C1',
@@ -706,8 +886,15 @@ class H(BaseHTTPRequestHandler):
if path == '/createReleaseCandidate':
a=self.need('release:candidate')
if not a: return
service = silent_release_service()
readiness = service.get_builder_readiness()
if readiness.get('status') != 'READY':
return self.sendj({
'error': 'TRUSTED_BUILDER_UNAVAILABLE',
'builder_readiness': readiness,
}, 503)
return self.sendj(
silent_release_service().create_release_candidate(data)
service.create_release_candidate(data)
)
if path == '/createReleaseIntent':
a=self.need('release:candidate')
@@ -747,6 +934,13 @@ class H(BaseHTTPRequestHandler):
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
return self.sendj({'error':error},403)
except TrustedCandidateBuilderUnavailable:
monitor.record_failure(
'do_POST', 'TRUSTED_BUILDER_UNAVAILABLE', {'path': path}
)
return self.sendj(
{'error': 'TRUSTED_BUILDER_UNAVAILABLE'}, 503
)
except SagaConflict as e:
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
@@ -0,0 +1,82 @@
"""Immutable release-lineage contracts used by activation and recovery."""
from __future__ import annotations
from dataclasses import asdict, dataclass
from typing import Any, Mapping
@dataclass(frozen=True)
class ReleaseLineage:
task_id: str
candidate_id: str
source_commit: str
version: str
required_capabilities: tuple[str, ...]
def as_dict(self) -> dict[str, Any]:
return asdict(self)
FIXED_V2R2C2 = ReleaseLineage(
task_id="r42-release-candidate-v2r2c2-live-20260819",
candidate_id="r42-release-candidate-v2r2c2-20260819",
source_commit="71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
version="V2R2C2",
required_capabilities=("release:candidate",),
)
class ReleaseLineageError(RuntimeError):
pass
def validate_fixed_v2r2c2_task(task: Mapping[str, Any]) -> dict[str, Any]:
"""Validate identity/source/version without mutating Task state.
This contract intentionally does not bind status/attempt/revision, because
those are lifecycle state and may change through governed retry/recovery.
"""
if str(task.get("task_id") or "") != FIXED_V2R2C2.task_id:
raise ReleaseLineageError("FIXED_TASK_ID_DRIFT")
capabilities = task.get("required_capabilities")
if (
not isinstance(capabilities, list)
or tuple(capabilities) != FIXED_V2R2C2.required_capabilities
):
raise ReleaseLineageError("FIXED_TASK_CAPABILITY_DRIFT")
constraints = task.get("constraints")
if not isinstance(constraints, Mapping):
raise ReleaseLineageError("FIXED_TASK_CONSTRAINTS_MISSING")
release = constraints.get("release_candidate")
if not isinstance(release, Mapping):
raise ReleaseLineageError("FIXED_RELEASE_BINDING_MISSING")
actual = {
"candidate_id": str(release.get("candidate_id") or ""),
"source_commit": str(release.get("source_commit") or ""),
"version": str(release.get("version") or ""),
}
expected = {
"candidate_id": FIXED_V2R2C2.candidate_id,
"source_commit": FIXED_V2R2C2.source_commit,
"version": FIXED_V2R2C2.version,
}
if actual != expected:
raise ReleaseLineageError(
"FIXED_RELEASE_LINEAGE_DRIFT:"
+ repr({"expected": expected, "actual": actual})
)
return {
"schema": "aios.release-lineage.validation.v1",
"status": "VERIFIED",
"lineage": FIXED_V2R2C2.as_dict(),
"task_status": task.get("status"),
"attempt": task.get("attempt"),
"max_attempts": task.get("max_attempts"),
"revision": task.get("revision"),
}
@@ -0,0 +1,714 @@
#!/usr/bin/env python3
"""Persistent release-preparation Online Agent worker.
The worker is intentionally narrower than the generic engineering worker:
it can only consume tasks whose required capability set is exactly
{"release:candidate"} and whose payload asks for immutable release-candidate
creation. It never receives Owner signing, deployment, tag, baseline or Git
write credentials.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import socket
import threading
import time
import urllib.error
import urllib.request
from typing import Any, Mapping
from aios_autonomous_control_plane import (
classify_failure,
repair_for_failure,
runtime_admission,
)
AGENT_ID = os.environ.get(
"AIOS_RELEASE_WORKER_AGENT_ID",
"online-release-worker-cn-network-mkt-20260819",
).strip()
AGENT_VERSION = os.environ.get(
"AIOS_RELEASE_WORKER_AGENT_VERSION",
"2026-08-19.1-release-candidate",
).strip()
TASK_API = os.environ.get(
"AIOS_AGENT_TASK_API_URL",
"http://127.0.0.1:18940",
).rstrip("/")
RELEASE_API = os.environ.get("AIOS_RELEASE_API_URL", "").rstrip("/")
POLL_SECONDS = int(os.environ.get("AIOS_RELEASE_WORKER_POLL_SECONDS", "3"))
LEASE_SECONDS = int(os.environ.get("AIOS_RELEASE_WORKER_LEASE_SECONDS", "60"))
HEARTBEAT_SECONDS = int(
os.environ.get("AIOS_RELEASE_WORKER_HEARTBEAT_SECONDS", "15")
)
STATE_ROOT = Path(
os.environ.get(
"AIOS_RELEASE_WORKER_STATE_ROOT",
"/var/lib/aios-online-agent-release",
)
)
EXPECTED_CAPABILITIES = frozenset({"release:candidate"})
LIVENESS_PATH = Path(
os.environ.get(
"AIOS_WORKER_LIVENESS_PATH",
str(STATE_ROOT / "worker-liveness.json"),
)
)
CANDIDATE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,255}$")
OID_RE = re.compile(r"^[0-9a-f]{40}$")
VERSION_RE = re.compile(r"^V[0-9]+R[0-9]+C[0-9]+$")
TASK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
SECRET_MARKERS = (
"authorization",
"token",
"secret",
"credential",
"password",
"private_key",
"signature",
)
class ReleaseWorkerError(RuntimeError):
def __init__(self, code: str, *, phase: str) -> None:
super().__init__(code)
self.code = code
self.phase = phase
def now() -> str:
return datetime.now(timezone.utc).isoformat()
def canonical(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def sha256(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def sanitized(value: Any) -> Any:
if isinstance(value, Mapping):
result: dict[str, Any] = {}
for key, inner in value.items():
lowered = str(key).lower()
if any(marker in lowered for marker in SECRET_MARKERS):
result[str(key)] = "[REDACTED]"
else:
result[str(key)] = sanitized(inner)
return result
if isinstance(value, list):
return [sanitized(item) for item in value]
return value
def audit(event: str, **fields: Any) -> None:
STATE_ROOT.mkdir(parents=True, exist_ok=True, mode=0o750)
path = STATE_ROOT / "events.jsonl"
record = {
"schema": "aios.release-worker.event.v1",
"event": event,
"created_at": now(),
"agent_id": AGENT_ID,
**sanitized(fields),
}
with path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(record, sort_keys=True) + "\n")
os.chmod(path, 0o640)
def sd_notify(message: str) -> None:
target = os.environ.get("NOTIFY_SOCKET", "")
if not target:
return
if target.startswith("@"):
address = "\0" + target[1:]
else:
address = target
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
try:
sock.connect(address)
sock.sendall(message.encode("utf-8"))
except OSError:
# Liveness JSON remains the diagnostic source; systemd will enforce
# WatchdogSec if notify transport is persistently broken.
pass
finally:
sock.close()
def write_liveness(
state: str,
*,
task_id: str | None = None,
stage: str | None = None,
error: str | None = None,
visible_count: int | None = None,
) -> None:
STATE_ROOT.mkdir(parents=True, exist_ok=True, mode=0o750)
value = {
"schema": "aios.online-agent.worker-liveness.v1",
"agent_id": AGENT_ID,
"agent_type": "release-preparation-worker",
"agent_version": AGENT_VERSION,
"pid": os.getpid(),
"state": state,
"last_seen_epoch": time.time(),
"last_seen_at": now(),
"poll_seconds": POLL_SECONDS,
"heartbeat_seconds": HEARTBEAT_SECONDS,
"api_base": TASK_API,
"capabilities": sorted(EXPECTED_CAPABILITIES),
"task_id": task_id,
"stage": stage,
"error": error,
"visible_count": visible_count,
}
LIVENESS_PATH.parent.mkdir(parents=True, exist_ok=True, mode=0o750)
temporary = LIVENESS_PATH.with_name(
"." + LIVENESS_PATH.name + f".{os.getpid()}.tmp"
)
temporary.write_text(
json.dumps(value, ensure_ascii=False, sort_keys=True) + "\n",
encoding="utf-8",
)
os.chmod(temporary, 0o640)
os.replace(temporary, LIVENESS_PATH)
def load_credential(name: str) -> str:
directory = Path(os.environ.get("CREDENTIALS_DIRECTORY", ""))
path = directory / name
if path.is_symlink() or not path.is_file():
raise ReleaseWorkerError(
"CREDENTIAL_UNAVAILABLE:" + name,
phase="startup",
)
value = path.read_text(encoding="utf-8").strip()
if len(value) < 24 or any(ch.isspace() for ch in value):
raise ReleaseWorkerError(
"CREDENTIAL_INVALID:" + name,
phase="startup",
)
return value
def http_json(
base: str,
method: str,
route: str,
token: str,
body: Mapping[str, Any] | None = None,
*,
timeout: int = 30,
) -> tuple[int, dict[str, Any]]:
data = None if body is None else canonical(body)
headers = {"Accept": "application/json"}
if token:
headers["Authorization"] = "Bearer " + token
if data is not None:
headers["Content-Type"] = "application/json"
request = urllib.request.Request(
base + route,
method=method,
data=data,
headers=headers,
)
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
raw, status = response.read(), response.status
except urllib.error.HTTPError as exc:
raw, status = exc.read(), exc.code
except (urllib.error.URLError, TimeoutError) as exc:
raise ReleaseWorkerError(
"HTTP_TRANSPORT_UNAVAILABLE",
phase="network",
) from exc
try:
value = json.loads(raw.decode())
except Exception as exc:
raise ReleaseWorkerError(
"HTTP_RESPONSE_INVALID",
phase="network",
) from exc
return status, value if isinstance(value, dict) else {}
@dataclass(frozen=True)
class ReleaseRequest:
candidate_id: str
source_commit: str
version: str
projection_id: str | None
@classmethod
def from_task(cls, task: Mapping[str, Any]) -> "ReleaseRequest":
if not TASK_RE.fullmatch(str(task.get("task_id") or "")):
raise ReleaseWorkerError("TASK_ID_INVALID", phase="validation")
caps = task.get("required_capabilities")
if (
not isinstance(caps, list)
or frozenset(caps) != EXPECTED_CAPABILITIES
or len(caps) != 1
):
raise ReleaseWorkerError(
"TASK_CAPABILITY_SCOPE_INVALID",
phase="validation",
)
metadata = task.get("metadata")
if not isinstance(metadata, Mapping):
metadata = {}
constraints = task.get("constraints")
if not isinstance(constraints, Mapping):
constraints = {}
release = constraints.get("release_candidate")
if not isinstance(release, Mapping):
release = metadata.get("release_candidate")
if not isinstance(release, Mapping):
release = task.get("release_candidate")
if not isinstance(release, Mapping):
raise ReleaseWorkerError(
"RELEASE_CANDIDATE_PAYLOAD_MISSING",
phase="validation",
)
allowed = {
"candidate_id",
"source_commit",
"version",
"projection_id",
}
if set(release) - allowed:
raise ReleaseWorkerError(
"RELEASE_CANDIDATE_PAYLOAD_FIELDS_INVALID",
phase="validation",
)
candidate_id = str(release.get("candidate_id") or "")
source_commit = str(release.get("source_commit") or "")
version = str(release.get("version") or "")
projection_id = release.get("projection_id")
if CANDIDATE_RE.fullmatch(candidate_id) is None:
raise ReleaseWorkerError(
"RELEASE_CANDIDATE_ID_INVALID",
phase="validation",
)
if OID_RE.fullmatch(source_commit) is None:
raise ReleaseWorkerError(
"RELEASE_SOURCE_COMMIT_INVALID",
phase="validation",
)
if VERSION_RE.fullmatch(version) is None:
raise ReleaseWorkerError(
"RELEASE_VERSION_INVALID",
phase="validation",
)
if projection_id is not None:
projection_id = str(projection_id)
if not re.fullmatch(r"projection-[0-9a-f]{32}", projection_id):
raise ReleaseWorkerError(
"RELEASE_PROJECTION_ID_INVALID",
phase="validation",
)
return cls(
candidate_id=candidate_id,
source_commit=source_commit,
version=version,
projection_id=projection_id,
)
def body(self) -> dict[str, str]:
return {
"candidate_id": self.candidate_id,
"source_commit": self.source_commit,
"version": self.version,
}
class Heartbeat:
def __init__(
self,
agent_token: str,
task_id: str,
lease_token: str,
) -> None:
self.agent_token = agent_token
self.task_id = task_id
self.lease_token = lease_token
self.stage = "claimed"
self.failure: str | None = None
self.stop = threading.Event()
self.thread = threading.Thread(
target=self._run,
name="release-worker-heartbeat",
daemon=True,
)
def first(self) -> None:
status, value = http_json(
TASK_API,
"POST",
f"/v1/tasks/{self.task_id}/heartbeat",
self.agent_token,
{
"lease_token": self.lease_token,
"lease_seconds": LEASE_SECONDS,
"progress": {
"stage": self.stage,
"worker_version": AGENT_VERSION,
},
},
)
if status != 200:
raise ReleaseWorkerError(
"TASK_HEARTBEAT_FAILED",
phase="heartbeat",
)
audit(
"TASK_HEARTBEAT",
task_id=self.task_id,
stage=self.stage,
api_status=status,
)
write_liveness(
"BUSY",
task_id=self.task_id,
stage=self.stage,
)
sd_notify("WATCHDOG=1")
def _run(self) -> None:
while not self.stop.wait(HEARTBEAT_SECONDS):
try:
status, value = http_json(
TASK_API,
"POST",
f"/v1/tasks/{self.task_id}/heartbeat",
self.agent_token,
{
"lease_token": self.lease_token,
"lease_seconds": LEASE_SECONDS,
"progress": {
"stage": self.stage,
"worker_version": AGENT_VERSION,
},
},
)
if status != 200:
self.failure = str(value.get("error") or status)
return
audit(
"TASK_HEARTBEAT",
task_id=self.task_id,
stage=self.stage,
api_status=status,
)
write_liveness(
"BUSY",
task_id=self.task_id,
stage=self.stage,
)
sd_notify("WATCHDOG=1")
except Exception as exc:
self.failure = type(exc).__name__
return
def ensure(self) -> None:
if self.failure:
raise ReleaseWorkerError(
"TASK_LEASE_LOST",
phase="heartbeat",
)
def close(self) -> None:
self.stop.set()
if self.thread.ident is not None:
self.thread.join(timeout=2)
def release_admission(
task: Mapping[str, Any],
*,
release_token: str,
):
required = task.get("required_capabilities")
if not isinstance(required, list):
required = []
def release_api_health() -> bool:
status, value = http_json(
RELEASE_API,
"GET",
"/health",
"",
timeout=10,
)
return status == 200 and str(value.get("status") or "").lower() in {"ok", "healthy"}
def authenticated_probe() -> bool:
status, _ = http_json(
RELEASE_API,
"POST",
"/createReleaseCandidate",
release_token,
{},
timeout=10,
)
# Authentication + credential projection succeeded; the empty release
# request is intentionally rejected by request validation.
return status == 400
probes = {
"credential_projection": authenticated_probe,
"trusted_builder": release_api_health,
"authenticated_probe": authenticated_probe,
"release_api_health": release_api_health,
"worker_liveness": lambda: True,
}
return runtime_admission(
worker_capabilities=EXPECTED_CAPABILITIES,
required_capabilities=required,
requested_operations=("prepare_release",),
probes=probes,
)
def result_payload(
task_id: str,
release: ReleaseRequest,
builder: Mapping[str, Any],
) -> dict[str, Any]:
builder_candidate = builder.get("candidate_id")
if builder_candidate not in {None, release.candidate_id}:
raise ReleaseWorkerError(
"BUILDER_CANDIDATE_BINDING_INVALID",
phase="release",
)
immutable = {
"candidate_id": release.candidate_id,
"source_commit": release.source_commit,
"version": release.version,
}
return {
"status": "SUCCEEDED",
"classification": "RELEASE_CANDIDATE_CREATED",
"execution_mode": "release_candidate",
"adapter": "release_candidate",
"task_id": task_id,
"agent_id": AGENT_ID,
"release_candidate": immutable,
"release_candidate_request_sha256": sha256(canonical(immutable)),
"builder_response": sanitized(dict(builder)),
"projection_id": release.projection_id,
"writer_lease": "NONE",
"changed_files": [],
"direct_main_write": False,
"production_write": False,
"tag_write": False,
"baseline_write": False,
"runtime_switch": False,
"owner_approval_submitted": False,
"credential_material_exposed": False,
}
def execute_task(
task: Mapping[str, Any],
*,
agent_token: str,
release_token: str,
) -> bool:
task_id = str(task.get("task_id") or "")
release = ReleaseRequest.from_task(task)
admission = release_admission(
task,
release_token=release_token,
)
if not admission.admitted:
audit(
"TASK_ADMISSION_BLOCKED",
task_id=task_id,
admission=admission.as_dict(),
)
write_liveness(
"BLOCKED",
task_id=task_id,
stage="execution_ready",
error=admission.failure_class.value if admission.failure_class else "ADMISSION_FAILED",
)
return False
audit(
"TASK_ADMISSION_PASSED",
task_id=task_id,
admission=admission.as_dict(),
)
status, claimed = http_json(
TASK_API,
"POST",
f"/v1/tasks/{task_id}/claim",
agent_token,
{"lease_seconds": LEASE_SECONDS},
)
if status in {409, 423}:
return False
if status != 200:
raise ReleaseWorkerError(
"TASK_CLAIM_FAILED",
phase="claim",
)
lease_token = str(claimed.get("lease_token") or "")
if len(lease_token) < 16:
raise ReleaseWorkerError(
"TASK_LEASE_TOKEN_INVALID",
phase="claim",
)
attempt = int(claimed.get("attempt") or 0)
audit(
"TASK_CLAIMED",
task_id=task_id,
attempt=attempt,
)
heartbeat = Heartbeat(agent_token, task_id, lease_token)
heartbeat.first()
heartbeat.thread.start()
try:
heartbeat.stage = "release_candidate"
heartbeat.ensure()
status, builder = http_json(
RELEASE_API,
"POST",
"/createReleaseCandidate",
release_token,
release.body(),
timeout=120,
)
if status not in {200, 201, 202}:
raise ReleaseWorkerError(
"RELEASE_CANDIDATE_CREATE_FAILED:"
+ str(builder.get("error") or status),
phase="release",
)
heartbeat.ensure()
result = result_payload(task_id, release, builder)
idempotency_key = (
f"{task_id}-attempt-{attempt}-release-result-v1"
)
status, submitted = http_json(
TASK_API,
"POST",
f"/v1/tasks/{task_id}/result",
agent_token,
{
"lease_token": lease_token,
"idempotency_key": idempotency_key,
"result": result,
},
)
if status not in {200, 201, 202}:
raise ReleaseWorkerError(
"TASK_RESULT_SUBMIT_FAILED",
phase="result",
)
audit(
"RESULT_SUBMITTED",
task_id=task_id,
attempt=attempt,
release_candidate_id=release.candidate_id,
)
return True
finally:
heartbeat.close()
def main() -> int:
if not RELEASE_API:
raise SystemExit("FATAL:AIOS_RELEASE_API_URL_REQUIRED")
agent_token = load_credential("agent_token")
release_token = load_credential("release_candidate_token")
STATE_ROOT.mkdir(parents=True, exist_ok=True, mode=0o750)
audit(
"WORKER_STARTED",
capabilities=sorted(EXPECTED_CAPABILITIES),
release_api=RELEASE_API,
)
write_liveness("STARTING", stage="startup")
sd_notify("READY=1\nSTATUS=release worker ready")
while True:
try:
status, response = http_json(
TASK_API,
"POST",
"/v1/tasks/poll",
agent_token,
{"limit": 100},
)
if status != 200:
raise ReleaseWorkerError(
"TASK_POLL_FAILED",
phase="poll",
)
tasks = response.get("tasks")
if not isinstance(tasks, list):
tasks = []
write_liveness(
"IDLE",
stage="poll",
visible_count=len(tasks),
)
sd_notify("WATCHDOG=1\nSTATUS=release worker poll ok")
handled = False
for raw in tasks:
if not isinstance(raw, Mapping):
continue
caps = raw.get("required_capabilities")
if (
not isinstance(caps, list)
or frozenset(caps) != EXPECTED_CAPABILITIES
):
continue
try:
handled = execute_task(
raw,
agent_token=agent_token,
release_token=release_token,
) or handled
except ReleaseWorkerError as exc:
failure = classify_failure(exc.code, phase=exc.phase)
repair = repair_for_failure(failure)
audit(
"TASK_EXECUTION_FAILED",
task_id=raw.get("task_id"),
phase=exc.phase,
error=exc.code,
failure_class=failure.value,
repair=repair.as_dict(),
)
time.sleep(0 if handled else POLL_SECONDS)
except KeyboardInterrupt:
return 0
except Exception as exc:
audit(
"WORKER_LOOP_ERROR",
error=type(exc).__name__,
)
write_liveness(
"DEGRADED",
stage="loop",
error=type(exc).__name__,
)
time.sleep(POLL_SECONDS)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,794 @@
#!/usr/bin/env python3
"""Fail-closed AF_UNIX builder for server-owned trusted candidate evidence."""
from __future__ import annotations
import argparse
import base64
import hashlib
import hmac
import json
import os
import secrets
import socket
import stat
import struct
import subprocess
import time
from datetime import datetime, timezone
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol
class BuilderError(RuntimeError):
"""A stable, non-sensitive error code returned by the builder boundary."""
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
def _sha256(*parts: str) -> str:
return hashlib.sha256("\0".join(parts).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class PeerIdentity:
pid: int
uid: int
gid: int
class DomainPurposeSigner(Protocol):
"""A signer deliberately limited to the builder's one domain purpose."""
@property
def key_id(self) -> str:
...
@property
def key_generation(self) -> int:
...
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
...
class CredentialPathEd25519Signer:
"""Reads a systemd-mounted credential path; never exports or logs key bytes."""
def __init__(self, credential_path: Path, *, key_id: str, key_generation: int, allowed_purpose: str):
self._credential_path = Path(credential_path)
self._key_id = str(key_id)
self._key_generation = key_generation
self._allowed_purpose = str(allowed_purpose)
if not self._key_id or not isinstance(key_generation, int) or key_generation < 1:
raise BuilderError("SIGNING_IDENTITY_INVALID")
if not self._credential_path.is_file():
raise BuilderError("SIGNING_CREDENTIAL_UNAVAILABLE")
try:
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
self._private_key = load_pem_private_key(self._credential_path.read_bytes(), password=None)
if not isinstance(self._private_key, Ed25519PrivateKey):
raise TypeError("signing key is not Ed25519")
except Exception as exc: # do not expose parse/key material details
raise BuilderError("SIGNING_CREDENTIAL_INVALID") from exc
@property
def key_id(self) -> str:
return self._key_id
@property
def key_generation(self) -> int:
return self._key_generation
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
if purpose != self._allowed_purpose:
raise BuilderError("SIGNING_PURPOSE_DENIED")
return self._private_key.sign(payload)
@dataclass(frozen=True)
class BuilderKeyring:
builder_hmac: bytes
signing_private_key_path: Path | None = None
def __post_init__(self) -> None:
if len(self.builder_hmac) < 32:
raise BuilderError("BUILDER_HMAC_INVALID")
@classmethod
def from_credentials_directory(cls, credentials_directory: Path) -> "BuilderKeyring":
root = Path(credentials_directory)
hmac_path = root / "builder_hmac"
signing_path = root / "signing_private_key"
try:
secret = hmac_path.read_bytes().strip()
except OSError as exc:
raise BuilderError("BUILDER_HMAC_UNAVAILABLE") from exc
if not signing_path.is_file():
raise BuilderError("SIGNING_CREDENTIAL_UNAVAILABLE")
return cls(builder_hmac=secret, signing_private_key_path=signing_path)
@dataclass(frozen=True)
class TrustedCandidateBuilderConfig:
policy_schema: str
repository: str
request_schema: str
response_schema: str
operation: str
readiness_operation: str
maximum_request_age_seconds: int
request_timeout_seconds: int
allowed_peer_uid: int
allowed_peer_cgroups: tuple[str, ...]
allowed_peer_executable_prefix: str
allowed_peer_runtime_prefix: str
allowed_peer_runtime_suffix: str
required_checks: tuple[str, ...]
evidence_layout: Mapping[str, str]
recipes: Mapping[str, Mapping[str, str]]
governed_recipes: Mapping[str, Mapping[str, str]]
policy_sha256: str
@classmethod
def from_policy(cls, policy: Mapping[str, Any]) -> "TrustedCandidateBuilderConfig":
required = {
"schema", "repository", "request_schema", "response_schema", "operation",
"readiness_operation", "maximum_request_age_seconds", "request_timeout_seconds",
"allowed_peer_uid", "allowed_peer_cgroups", "allowed_peer_executable_prefix",
"allowed_peer_runtime_prefix", "allowed_peer_runtime_suffix",
"required_checks", "evidence_layout", "recipes", "governed_recipes",
}
if set(policy) != required or policy.get("schema") != "aios.trusted-candidate-builder-policy.v1":
raise BuilderError("POLICY_SCHEMA_INVALID")
recipes = policy.get("recipes")
if not isinstance(recipes, Mapping) or set(recipes) != {"V2R2C2", "V2R2C3"}:
raise BuilderError("POLICY_RECIPES_INVALID")
c2, c3 = recipes["V2R2C2"], recipes["V2R2C3"]
if not isinstance(c2, Mapping) or set(c2) != {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose"} or c2.get("mode") != "fixed":
raise BuilderError("POLICY_V2R2C2_INVALID")
if not isinstance(c3, Mapping) or set(c3) != {"mode", "derives_from", "version", "purpose"} or c3.get("mode") != "governed-derived-only" or c3.get("derives_from") != "V2R2C2":
raise BuilderError("POLICY_V2R2C3_INVALID")
if (
policy["repository"] != "aios/aios"
or policy["operation"] != "BuildTrustedCandidateV1"
or policy["readiness_operation"] != "ProbeTrustedCandidateBuilderV1"
):
raise BuilderError("POLICY_SCOPE_INVALID")
if not isinstance(policy["maximum_request_age_seconds"], int) or not 1 <= policy["maximum_request_age_seconds"] <= 300:
raise BuilderError("POLICY_FRESHNESS_INVALID")
if not isinstance(policy["request_timeout_seconds"], int) or not 1 <= policy["request_timeout_seconds"] <= 120:
raise BuilderError("POLICY_TIMEOUT_INVALID")
if not isinstance(policy["allowed_peer_uid"], int) or policy["allowed_peer_uid"] != 1000:
raise BuilderError("POLICY_PEER_UID_INVALID")
cgroups, checks = policy["allowed_peer_cgroups"], policy["required_checks"]
if not isinstance(cgroups, list) or not cgroups or not all(isinstance(value, str) and value.startswith("/") for value in cgroups):
raise BuilderError("POLICY_CGROUPS_INVALID")
for field in (
"allowed_peer_executable_prefix",
"allowed_peer_runtime_prefix",
"allowed_peer_runtime_suffix",
):
if not isinstance(policy[field], str) or not policy[field].startswith("/"):
raise BuilderError("POLICY_PROCESS_IDENTITY_INVALID")
if not isinstance(checks, list) or not checks or len(set(checks)) != len(checks) or not all(isinstance(value, str) for value in checks):
raise BuilderError("POLICY_CHECKS_INVALID")
layout = policy["evidence_layout"]
evidence_names = {
"artifact", "manifest", "package_closure", "tests", "canary",
"gitea", "rollback", "policy", "secret_scan",
}
if (
not isinstance(layout, Mapping)
or set(layout) != evidence_names
or not all(
isinstance(value, str)
and value.startswith("{version}/")
and ".." not in Path(value).parts
and not Path(value).is_absolute()
for value in layout.values()
)
):
raise BuilderError("POLICY_EVIDENCE_LAYOUT_INVALID")
return cls(
policy_schema=str(policy["schema"]), repository=str(policy["repository"]),
request_schema=str(policy["request_schema"]), response_schema=str(policy["response_schema"]),
operation=str(policy["operation"]), readiness_operation=str(policy["readiness_operation"]),
maximum_request_age_seconds=policy["maximum_request_age_seconds"],
request_timeout_seconds=policy["request_timeout_seconds"],
allowed_peer_uid=policy["allowed_peer_uid"], allowed_peer_cgroups=tuple(cgroups),
allowed_peer_executable_prefix=policy["allowed_peer_executable_prefix"],
allowed_peer_runtime_prefix=policy["allowed_peer_runtime_prefix"],
allowed_peer_runtime_suffix=policy["allowed_peer_runtime_suffix"],
required_checks=tuple(checks), evidence_layout=dict(layout),
recipes={name: dict(value) for name, value in recipes.items()},
governed_recipes={name: dict(value) for name, value in policy["governed_recipes"].items()} if isinstance(policy["governed_recipes"], Mapping) else _invalid_governed_recipes(),
policy_sha256=hashlib.sha256(_canonical_json(dict(policy))).hexdigest(),
)
def fixed_recipe(self, name: str) -> Mapping[str, str]:
recipe = self.recipes.get(name)
if recipe is None:
raise BuilderError("RECIPE_UNKNOWN")
if recipe["mode"] == "governed-derived-only":
derived = self.governed_recipes.get(name)
required = {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "derived_from_v2r2c2_attestation", "builder_engineering_oid"}
if not isinstance(derived, Mapping) or set(derived) != required or derived.get("mode") != "governed-derived" or derived.get("version") != name or derived.get("purpose") != recipe.get("purpose"):
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
for name in ("base_oid", "source_commit", "builder_engineering_oid"):
value = derived.get(name)
if not isinstance(value, str) or len(value) != 40 or any(char not in "0123456789abcdef" for char in value.lower()):
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
return dict(derived)
return recipe
def resolve_recipe(self, version: str, candidate_id: str, source_commit: str) -> Mapping[str, str]:
recipe = self.fixed_recipe(version)
if {"version": recipe.get("version"), "candidate_id": recipe.get("candidate_id"), "source_commit": recipe.get("source_commit")} != {"version": version, "candidate_id": candidate_id, "source_commit": source_commit}:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
return recipe
def _invalid_governed_recipes() -> Mapping[str, Mapping[str, str]]:
raise BuilderError("POLICY_GOVERNED_RECIPES_INVALID")
def load_builder_policy(path: Path) -> Mapping[str, Any]:
try:
decoded = json.loads(Path(path).read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise BuilderError("POLICY_UNAVAILABLE") from exc
if not isinstance(decoded, Mapping):
raise BuilderError("POLICY_SCHEMA_INVALID")
TrustedCandidateBuilderConfig.from_policy(decoded)
return decoded
class ServerOwnedEvidenceSource:
"""Recomputes a fixed on-host evidence closure without caller-owned paths."""
def __init__(
self,
config: TrustedCandidateBuilderConfig,
authority_git_dir: Path,
evidence_root: Path,
*,
expected_owner_uid: int | None = 0,
git_runner: Callable[..., Any] = subprocess.run,
):
self._config = config
self._authority_git_dir = Path(authority_git_dir)
self._evidence_root = Path(evidence_root)
self._expected_owner_uid = expected_owner_uid
self._git_runner = git_runner
def _regular_path(self, relative: str) -> Path:
root = self._evidence_root.resolve(strict=True)
candidate = self._evidence_root / relative
current = self._evidence_root
for part in Path(relative).parts:
current = current / part
if current.is_symlink():
raise BuilderError("COLLECTOR_SYMLINK_DENIED")
try:
resolved = candidate.resolve(strict=True)
resolved.relative_to(root)
metadata = resolved.stat(follow_symlinks=False)
except (OSError, ValueError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if not stat.S_ISREG(metadata.st_mode):
raise BuilderError("COLLECTOR_SPECIAL_FILE_DENIED")
if os.name != "nt" and metadata.st_mode & 0o022:
raise BuilderError("COLLECTOR_EVIDENCE_MODE_DENIED")
if (
os.name != "nt"
and
self._expected_owner_uid is not None
and metadata.st_uid != self._expected_owner_uid
):
raise BuilderError("COLLECTOR_EVIDENCE_OWNER_DENIED")
return resolved
def _digest(self, path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _json(self, path: Path) -> Mapping[str, Any]:
try:
if path.stat().st_size > 2 * 1024 * 1024:
raise BuilderError("COLLECTOR_EVIDENCE_SIZE_INVALID")
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID") from exc
if not isinstance(value, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
return value
def __call__(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
try:
tree_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "rev-parse", f"{recipe['source_commit']}^{{tree}}"],
check=True, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
ancestry_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "merge-base", "--is-ancestor", recipe["base_oid"], recipe["source_commit"]],
check=False, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
tree_oid = tree_result.stdout.strip().lower()
except (OSError, subprocess.SubprocessError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if getattr(ancestry_result, "returncode", 1) != 0:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
receipt = self._json(self._regular_path(f"{recipe_name}/receipt.json"))
if set(receipt) != {"schema", "version", "candidate", "release_plan"}:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
if (
receipt.get("schema") != "aios.trusted-candidate-builder.evidence.v1"
or receipt.get("version") != recipe_name
):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
candidate = receipt.get("candidate")
release_plan = receipt.get("release_plan")
if not isinstance(candidate, Mapping) or not isinstance(release_plan, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
if (
candidate.get("candidate_id") != recipe["candidate_id"]
or candidate.get("source_commit") != recipe["source_commit"]
or candidate.get("tree_oid") != tree_oid
):
raise BuilderError("COLLECTOR_GIT_MISMATCH")
git_consistency = candidate.get("git_consistency")
if (
not isinstance(git_consistency, Mapping)
or git_consistency.get("consistent") is not True
or git_consistency.get("source_commit") != recipe["source_commit"]
or git_consistency.get("tree_oid") != tree_oid
or git_consistency.get("repository") != self._config.repository
):
raise BuilderError("COLLECTOR_GIT_MISMATCH")
paths = {
name: self._regular_path(template.format(version=recipe_name))
for name, template in self._config.evidence_layout.items()
}
deployment = release_plan.get("deployment")
if not isinstance(deployment, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
digests = {name: self._digest(path) for name, path in paths.items()}
if (
digests["artifact"] != candidate.get("artifact_sha256")
or digests["manifest"] != candidate.get("manifest_sha256")
or digests["package_closure"] != deployment.get("package_closure_sha256")
or digests["policy"] != release_plan.get("policy_sha256")
or digests["policy"] != deployment.get("policy_sha256")
):
raise BuilderError("COLLECTOR_DIGEST_MISMATCH")
tests = self._json(paths["tests"])
canary = self._json(paths["canary"])
gitea = self._json(paths["gitea"])
rollback = self._json(paths["rollback"])
secret_scan = self._json(paths["secret_scan"])
if tests != candidate.get("tests") or tests.get("passed") is not True:
raise BuilderError("COLLECTOR_TESTS_MISMATCH")
if canary != candidate.get("canary") or canary.get("passed") is not True:
raise BuilderError("COLLECTOR_CANARY_MISMATCH")
if (
digests["tests"] != hashlib.sha256(_canonical_json(dict(tests))).hexdigest()
or digests["canary"] != hashlib.sha256(_canonical_json(dict(canary))).hexdigest()
):
raise BuilderError("COLLECTOR_EVIDENCE_CANONICALIZATION_MISMATCH")
if (
gitea != candidate.get("gitea_consistency")
or gitea.get("consistent") is not True
or gitea.get("repository") != self._config.repository
or gitea.get("source_commit") != recipe["source_commit"]
or gitea.get("tree_oid") != tree_oid
or gitea.get("remote_ref_oid") != recipe["source_commit"]
):
raise BuilderError("COLLECTOR_GITEA_MISMATCH")
if (
candidate.get("rollback_ready") is not True
or rollback.get("ready") is not True
or rollback.get("source_commit") != recipe["source_commit"]
or rollback.get("rollback_sha256") != deployment.get("rollback_sha256")
):
raise BuilderError("COLLECTOR_ROLLBACK_MISMATCH")
if (
digests["gitea"] != hashlib.sha256(_canonical_json(dict(gitea))).hexdigest()
or digests["rollback"] != hashlib.sha256(_canonical_json(dict(rollback))).hexdigest()
):
raise BuilderError("COLLECTOR_EVIDENCE_CANONICALIZATION_MISMATCH")
if (
secret_scan.get("passed") is not True
or secret_scan.get("artifact_sha256") != candidate.get("artifact_sha256")
):
raise BuilderError("COLLECTOR_SECRET_SCAN_MISMATCH")
checks = {name: True for name in self._config.required_checks}
return {
"candidate": dict(candidate),
"release_plan": dict(release_plan),
"checks": checks,
"evidence_digests": digests,
}
class DeterministicEvidenceCollector:
"""Validates a server-owned source; request fields never influence collection."""
_BUNDLE_FIELDS = {"candidate", "release_plan", "checks", "evidence_digests"}
def __init__(self, config: TrustedCandidateBuilderConfig, *, evidence_source: Callable[[str, Mapping[str, str]], Mapping[str, Any]] | None = None):
self._config = config
self._evidence_source = evidence_source
def collect(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
if self._evidence_source is None:
raise BuilderError("COLLECTOR_UNCONFIGURED")
bundle = self._evidence_source(recipe_name, recipe)
if not isinstance(bundle, Mapping) or set(bundle) != self._BUNDLE_FIELDS:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
raw_checks = bundle.get("checks")
if (
not isinstance(raw_checks, Mapping)
or set(raw_checks) != set(self._config.required_checks)
or any(raw_checks.get(name) is not True for name in self._config.required_checks)
):
raise BuilderError("COLLECTOR_CHECKS_INCOMPLETE")
evidence_digests = bundle.get("evidence_digests")
if (
not isinstance(evidence_digests, Mapping)
or set(evidence_digests) != set(self._config.evidence_layout)
or any(
not isinstance(value, str)
or len(value) != 64
or any(char not in "0123456789abcdef" for char in value.lower())
for value in evidence_digests.values()
)
):
raise BuilderError("COLLECTOR_EVIDENCE_DIGESTS_INVALID")
try:
from silent_release_controller import ReleaseCandidate
from silent_release_hardening import ReleasePlan, TrustedCandidateBundle
candidate = ReleaseCandidate.from_dict(dict(bundle["candidate"]))
release_plan = ReleasePlan.from_dict(dict(bundle["release_plan"]))
checks = dict(raw_checks)
TrustedCandidateBundle(candidate, release_plan, checks).validate()
except (ImportError, KeyError, TypeError, ValueError, RuntimeError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID") from exc
if candidate.source_commit != recipe["source_commit"] or candidate.candidate_id != recipe["candidate_id"]:
raise BuilderError("COLLECTOR_SOURCE_MISMATCH")
return {
"candidate": candidate.to_dict(),
"release_plan": release_plan.to_dict(),
"checks": checks,
"evidence_digests": dict(evidence_digests),
}
def _extract_peer_identity(connection: socket.socket) -> PeerIdentity:
if not hasattr(socket, "SO_PEERCRED"):
raise BuilderError("SO_PEERCRED_UNAVAILABLE")
raw = connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i"))
if len(raw) != struct.calcsize("3i"):
raise BuilderError("SO_PEERCRED_INVALID")
return PeerIdentity(*struct.unpack("3i", raw))
def _resolve_cgroups(pid: int) -> tuple[str, ...]:
try:
return tuple(
line.rsplit(":", 1)[1].strip()
for line in Path(f"/proc/{pid}/cgroup").read_text(encoding="utf-8").splitlines()
if ":" in line and line.rsplit(":", 1)[1].strip().startswith("/")
)
except OSError as exc:
raise BuilderError("PEER_CGROUP_UNAVAILABLE") from exc
def _resolve_process_identity(pid: int) -> tuple[str, tuple[str, ...]]:
try:
executable = os.path.realpath(f"/proc/{pid}/exe")
argv = tuple(
item.decode("utf-8", "strict")
for item in Path(f"/proc/{pid}/cmdline").read_bytes().split(b"\0")
if item
)
except (OSError, UnicodeDecodeError) as exc:
raise BuilderError("PEER_PROCESS_UNAVAILABLE") from exc
if not executable or not argv:
raise BuilderError("PEER_PROCESS_UNAVAILABLE")
return executable, argv
class TrustedCandidateBuilder:
def __init__(
self, config: TrustedCandidateBuilderConfig, keyring: BuilderKeyring, *,
collector: DeterministicEvidenceCollector, signer: DomainPurposeSigner,
peer_identity_resolver: Callable[[socket.socket], PeerIdentity] = _extract_peer_identity,
cgroup_resolver: Callable[[int], tuple[str, ...] | str] = _resolve_cgroups,
process_identity_resolver: Callable[[int], tuple[str, tuple[str, ...]]] = _resolve_process_identity,
clock: Callable[[], float] = time.time, nonce_ledger_path: Path | None = None,
) -> None:
self.config, self.keyring, self.collector, self.signer = config, keyring, collector, signer
self.peer_identity_resolver, self.cgroup_resolver, self.clock = peer_identity_resolver, cgroup_resolver, clock
self.process_identity_resolver = process_identity_resolver
self._nonce_ledger_path = Path(nonce_ledger_path) if nonce_ledger_path else None
self._nonce_ledger: dict[str, tuple[bytes, dict[str, Any]]] = self._load_nonce_ledger()
def _load_nonce_ledger(self) -> dict[str, tuple[bytes, dict[str, Any]]]:
if self._nonce_ledger_path is None or not self._nonce_ledger_path.exists():
return {}
if self._nonce_ledger_path.is_symlink():
raise BuilderError("NONCE_LEDGER_INVALID")
try:
raw = json.loads(self._nonce_ledger_path.read_text(encoding="utf-8"))
return {nonce: (bytes.fromhex(value["request_sha256"]), dict(value["result"])) for nonce, value in raw.items()}
except (OSError, ValueError, TypeError, KeyError, json.JSONDecodeError) as exc:
raise BuilderError("NONCE_LEDGER_INVALID") from exc
def _persist_nonce_ledger(self) -> None:
if self._nonce_ledger_path is None:
return
try:
self._nonce_ledger_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = self._nonce_ledger_path.with_suffix(".tmp")
payload = {nonce: {"request_sha256": digest.hex(), "result": result} for nonce, (digest, result) in self._nonce_ledger.items()}
with temporary.open("wb") as handle:
handle.write(_canonical_json(payload))
handle.flush()
os.fsync(handle.fileno())
os.chmod(temporary, 0o600)
os.replace(temporary, self._nonce_ledger_path)
if os.name != "nt":
descriptor = os.open(self._nonce_ledger_path.parent, os.O_RDONLY)
try:
os.fsync(descriptor)
finally:
os.close(descriptor)
except OSError as exc:
raise BuilderError("NONCE_LEDGER_PERSIST_FAILED") from exc
def _verify_peer(self, connection: socket.socket) -> None:
peer = self.peer_identity_resolver(connection)
if peer.uid != self.config.allowed_peer_uid or peer.pid <= 0:
raise BuilderError("PEER_IDENTITY_DENIED")
resolved = self.cgroup_resolver(peer.pid)
cgroups = (resolved,) if isinstance(resolved, str) else tuple(resolved)
if not cgroups or not any(item in self.config.allowed_peer_cgroups for item in cgroups):
raise BuilderError("PEER_CGROUP_DENIED")
executable, argv = self.process_identity_resolver(peer.pid)
if not executable.startswith(self.config.allowed_peer_executable_prefix):
raise BuilderError("PEER_PROCESS_DENIED")
runtime_arguments = [
value
for value in argv
if value.startswith(self.config.allowed_peer_runtime_prefix)
and value.endswith(self.config.allowed_peer_runtime_suffix)
]
if len(runtime_arguments) != 1:
raise BuilderError("PEER_PROCESS_DENIED")
def handle_request(self, request: Mapping[str, Any], *, connection: socket.socket) -> dict[str, Any]:
if not isinstance(request, Mapping):
raise BuilderError("REQUEST_SCHEMA_INVALID")
expected_keys = {"schema", "operation", "recipe", "repository", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "issued_at", "nonce", "hmac"}
if (
set(request) != expected_keys
or request.get("schema") != self.config.request_schema
or request.get("operation") not in {
self.config.operation,
self.config.readiness_operation,
}
):
raise BuilderError("REQUEST_SCHEMA_INVALID")
supplied_mac = request.get("hmac")
unsigned = {key: value for key, value in request.items() if key != "hmac"}
if not isinstance(supplied_mac, str) or not hmac.compare_digest(hmac.new(self.keyring.builder_hmac, _canonical_json(unsigned), hashlib.sha256).hexdigest(), supplied_mac):
raise BuilderError("REQUEST_HMAC_INVALID")
issued_at, nonce = request.get("issued_at"), request.get("nonce")
if not isinstance(issued_at, int) or not isinstance(nonce, str) or len(nonce) < 16 or len(nonce) > 256:
raise BuilderError("REQUEST_SCHEMA_INVALID")
self._verify_peer(connection)
request_bytes = _canonical_json(dict(request))
replay = self._nonce_ledger.get(nonce)
if replay is not None:
if hmac.compare_digest(replay[0], hashlib.sha256(request_bytes).digest()):
return dict(replay[1])
raise BuilderError("REPLAY_CONFLICT")
if abs(int(self.clock()) - issued_at) > self.config.maximum_request_age_seconds:
raise BuilderError("REQUEST_STALE")
recipe_name = request.get("recipe")
if not isinstance(recipe_name, str):
raise BuilderError("REQUEST_SCHEMA_INVALID")
recipe = self.config.resolve_recipe(str(request.get("version") or ""), str(request.get("candidate_id") or ""), str(request.get("source_commit") or ""))
if recipe_name != recipe.get("version"):
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
bound = {"repository": self.config.repository, "task_id": recipe["task_id"], "candidate_id": recipe["candidate_id"], "base_oid": recipe["base_oid"], "source_commit": recipe["source_commit"], "version": recipe["version"], "purpose": recipe["purpose"]}
if {key: request.get(key) for key in bound} != bound:
raise BuilderError("REQUEST_SCOPE_INVALID")
bundle = dict(self.collector.collect(recipe_name, recipe))
candidate = bundle["candidate"]
release_plan = bundle["release_plan"]
checks = bundle.get("checks")
evidence_digests = bundle["evidence_digests"]
if not isinstance(checks, Mapping) or set(checks) != set(self.config.required_checks) or not all(value is True for value in checks.values()):
raise BuilderError("COLLECTOR_CHECKS_INCOMPLETE")
if request["operation"] == self.config.readiness_operation:
result = {
"schema": self.config.response_schema,
"status": "READY",
"repository": self.config.repository,
"recipe": recipe_name,
"candidate_id": recipe["candidate_id"],
"source_commit": recipe["source_commit"],
"builder_key_id": self.signer.key_id,
"builder_key_generation": self.signer.key_generation,
"signature_algorithm": "Ed25519",
"signature_purpose": "builder_attestation",
"policy_sha256": self.config.policy_sha256,
"collector_ready": True,
"signer_ready": True,
}
if len(self._nonce_ledger) >= 2048:
self._nonce_ledger.pop(next(iter(self._nonce_ledger)))
self._nonce_ledger[nonce] = (
hashlib.sha256(request_bytes).digest(),
dict(result),
)
self._persist_nonce_ledger()
return result
issued = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
attestation = {
"builder_attestation_id": "builder-attestation-" + _sha256(recipe_name, recipe["task_id"], candidate["artifact_sha256"])[:24],
"builder_key_id": self.signer.key_id,
"builder_key_generation": self.signer.key_generation,
"signature_algorithm": "Ed25519",
"signature_purpose": "builder_attestation",
"source_commit": candidate["source_commit"], "tree_oid": candidate["tree_oid"],
"artifact_sha256": candidate["artifact_sha256"], "manifest_sha256": candidate["manifest_sha256"],
"package_closure_sha256": release_plan["deployment"]["package_closure_sha256"],
"test_evidence_sha256": evidence_digests["tests"],
"canary_evidence_sha256": evidence_digests["canary"],
"gitea_evidence_sha256": evidence_digests["gitea"],
"rollback_evidence_sha256": evidence_digests["rollback"],
"policy_evidence_sha256": evidence_digests["policy"],
"secret_scan_evidence_sha256": evidence_digests["secret_scan"],
"issued_at": issued, "trust_domain": "aios.trusted-builder.attestation.v1",
}
canonical_attestation = _canonical_json(attestation)
fingerprint = hashlib.sha256(canonical_attestation).hexdigest()
signature = base64.b64encode(self.signer.sign_for_purpose("builder_attestation", canonical_attestation)).decode("ascii")
result = {
"schema": self.config.response_schema, "status": "BUILT", "recipe": recipe_name,
"repository": self.config.repository, "task_id": recipe["task_id"], "base_oid": recipe["base_oid"],
"purpose": recipe["purpose"], "checks": dict(checks),
"evidence": {"candidate_id": candidate["candidate_id"], "source_commit": candidate["source_commit"], "tree_oid": candidate["tree_oid"], "artifact_sha256": candidate["artifact_sha256"], "manifest_sha256": candidate["manifest_sha256"], "package_closure_sha256": release_plan["deployment"]["package_closure_sha256"], "test_evidence_sha256": attestation["test_evidence_sha256"], "canary_evidence_sha256": attestation["canary_evidence_sha256"]},
"attestation": {**attestation, "fingerprint": fingerprint, "signature": signature},
}
result["silent_release_bundle"] = {"candidate": candidate, "release_plan": release_plan, "checks": dict(checks), "attestation": result["attestation"]}
if len(self._nonce_ledger) >= 2048:
self._nonce_ledger.pop(next(iter(self._nonce_ledger)))
self._nonce_ledger[nonce] = (hashlib.sha256(request_bytes).digest(), dict(result))
self._persist_nonce_ledger()
return result
def serve_forever(self, listener: socket.socket) -> None:
while True:
connection, _ = listener.accept()
with connection:
try:
raw = connection.makefile("rb").readline(65537)
if not raw or len(raw) > 65536:
raise BuilderError("REQUEST_SIZE_INVALID")
result = self.handle_request(json.loads(raw), connection=connection)
except (BuilderError, UnicodeDecodeError, json.JSONDecodeError):
result = {"schema": self.config.response_schema, "status": "REJECTED", "error_code": "REQUEST_REJECTED"}
connection.sendall(_canonical_json(result) + b"\n")
class UnixTrustedCandidateBuilderClient:
def __init__(self, config: TrustedCandidateBuilderConfig, keyring: BuilderKeyring, *, socket_path: Path | None = None, clock: Callable[[], float] = time.time) -> None:
self.config, self.keyring, self.socket_path, self.clock = config, keyring, socket_path, clock
def create_request(self, version: str, candidate_id: str, source_commit: str, *, nonce: str, operation: str | None = None) -> dict[str, Any]:
selected = self.config.resolve_recipe(version, candidate_id, source_commit)
selected_operation = operation or self.config.operation
if selected_operation not in {self.config.operation, self.config.readiness_operation}:
raise BuilderError("REQUEST_OPERATION_INVALID")
request = {"schema": self.config.request_schema, "operation": selected_operation, "recipe": version, "repository": self.config.repository, "task_id": selected["task_id"], "candidate_id": selected["candidate_id"], "base_oid": selected["base_oid"], "source_commit": selected["source_commit"], "version": selected["version"], "purpose": selected["purpose"], "issued_at": int(self.clock()), "nonce": nonce}
request["hmac"] = hmac.new(self.keyring.builder_hmac, _canonical_json(request), hashlib.sha256).hexdigest()
return request
def _exchange(self, request: Mapping[str, Any]) -> Mapping[str, Any]:
if self.socket_path is None:
raise BuilderError("BUILDER_SOCKET_UNCONFIGURED")
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection:
connection.settimeout(self.config.request_timeout_seconds)
connection.connect(os.fspath(self.socket_path))
connection.sendall(_canonical_json(dict(request)) + b"\n")
raw = connection.makefile("rb").readline(65537)
except (OSError, TimeoutError) as exc:
raise BuilderError("BUILDER_TRANSPORT_UNAVAILABLE") from exc
try:
result = json.loads(raw)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BuilderError("BUILDER_RESPONSE_INVALID") from exc
if not isinstance(result, Mapping) or result.get("schema") != self.config.response_schema:
raise BuilderError("BUILDER_RESPONSE_INVALID")
return result
def build(self, version: str, candidate_id: str, source_commit: str, *, nonce: str) -> Mapping[str, Any]:
result = self._exchange(
self.create_request(version, candidate_id, source_commit, nonce=nonce)
)
if result.get("status") != "BUILT":
raise BuilderError("BUILDER_REQUEST_REJECTED")
return result
def probe(self, *, nonce: str | None = None) -> Mapping[str, Any]:
recipe = self.config.fixed_recipe("V2R2C2")
result = self._exchange(
self.create_request(
"V2R2C2",
recipe["candidate_id"],
recipe["source_commit"],
nonce=nonce or secrets.token_hex(32),
operation=self.config.readiness_operation,
)
)
if result.get("status") != "READY":
raise BuilderError("BUILDER_NOT_READY")
return result
def readiness_probe(client: UnixTrustedCandidateBuilderClient) -> Mapping[str, Any]:
result = dict(client.probe())
required_true = {"collector_ready", "signer_ready"}
if any(result.get(name) is not True for name in required_true):
raise BuilderError("BUILDER_NOT_READY")
return result
def _main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--socket-fd", required=True, type=int)
parser.add_argument("--policy", required=True, type=Path)
arguments = parser.parse_args()
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(arguments.policy))
credentials = os.environ.get("CREDENTIALS_DIRECTORY")
if not credentials:
raise SystemExit("CREDENTIALS_DIRECTORY is required")
keyring = BuilderKeyring.from_credentials_directory(Path(credentials))
signer = CredentialPathEd25519Signer(
keyring.signing_private_key_path,
key_id=os.environ["AIOS_TRUSTED_BUILDER_KEY_ID"],
key_generation=int(os.environ["AIOS_TRUSTED_BUILDER_KEY_GENERATION"]),
allowed_purpose="builder_attestation",
)
listener = socket.fromfd(arguments.socket_fd, socket.AF_UNIX, socket.SOCK_STREAM)
collector = DeterministicEvidenceCollector(
config,
evidence_source=ServerOwnedEvidenceSource(
config,
Path(os.environ["AIOS_TRUSTED_BUILDER_GIT_DIR"]),
Path(os.environ["AIOS_TRUSTED_BUILDER_EVIDENCE_ROOT"]),
),
)
TrustedCandidateBuilder(config, keyring, collector=collector, signer=signer, nonce_ledger_path=Path(os.environ["AIOS_TRUSTED_BUILDER_NONCE_LEDGER"])).serve_forever(listener)
if __name__ == "__main__":
_main()
+590 -32
View File
@@ -9,6 +9,7 @@ from __future__ import annotations
import base64
import hashlib
import hmac
import json
import os
import re
@@ -40,6 +41,8 @@ from silent_release_controller import (
IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$")
NONCE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{15,255}$")
BUILDER_ATTESTATION_TRUST_DOMAIN = "aios.trusted-builder.attestation.v1"
BUILDER_ATTESTATION_PURPOSE = "builder_attestation"
ALLOWED_CAPABILITIES = frozenset(
{
"runtime_switch",
@@ -62,6 +65,10 @@ class CapabilityAuthorizationError(RuntimeError):
"""The signed plan cannot authorize the requested operation."""
class TrustedCandidateBuilderUnavailable(RuntimeError):
"""The purpose-bound builder dependency cannot currently serve a build."""
class StaleApprovalError(CapabilityAuthorizationError):
"""Live release evidence no longer matches the signed approval."""
@@ -222,6 +229,10 @@ JOURNAL_GENESIS_HASH = "0" * 64
def _fsync_directory(path: Path) -> None:
if os.name == "nt":
# Windows does not permit opening a directory with os.open. Durable
# Linux host execution retains the directory fsync below.
return
descriptor = os.open(Path(path), os.O_RDONLY)
try:
os.fsync(descriptor)
@@ -715,6 +726,14 @@ class BuilderAttestation:
canary_evidence_sha256: str
issued_at: str
signature: str
trust_domain: str = BUILDER_ATTESTATION_TRUST_DOMAIN
builder_key_generation: int | None = None
signature_algorithm: str | None = None
signature_purpose: str | None = None
gitea_evidence_sha256: str = ""
rollback_evidence_sha256: str = ""
policy_evidence_sha256: str = ""
secret_scan_evidence_sha256: str = ""
def __post_init__(self) -> None:
_validate_identifier(
@@ -731,7 +750,35 @@ class BuilderAttestation:
"canary_evidence_sha256",
):
_validate_sha(name, getattr(self, name))
for name in (
"gitea_evidence_sha256",
"rollback_evidence_sha256",
"policy_evidence_sha256",
"secret_scan_evidence_sha256",
):
if getattr(self, name):
_validate_sha(name, getattr(self, name))
_parse_utc(self.issued_at)
if self.trust_domain != BUILDER_ATTESTATION_TRUST_DOMAIN:
raise CapabilityAuthorizationError(
"builder attestation trust domain is invalid"
)
metadata = (
self.builder_key_generation,
self.signature_algorithm,
self.signature_purpose,
)
if any(value is not None for value in metadata):
if (
not isinstance(self.builder_key_generation, int)
or isinstance(self.builder_key_generation, bool)
or self.builder_key_generation < 0
or self.signature_algorithm != "Ed25519"
or self.signature_purpose != BUILDER_ATTESTATION_PURPOSE
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata is invalid"
)
@property
def canonical_payload(self) -> dict[str, Any]:
@@ -754,20 +801,290 @@ class BuilderAttestation:
}
@classmethod
def from_dict(cls, payload: Mapping[str, Any]) -> "BuilderAttestation":
def from_dict(
cls,
payload: Mapping[str, Any],
*,
require_fingerprint: bool = False,
) -> "BuilderAttestation":
values = dict(payload)
values.pop("fingerprint", None)
return cls(**values)
supplied_fingerprint = values.pop("fingerprint", None)
# Older local records did not spell out the domain. They are only
# usable when re-signed by the current builder; their original
# signatures cannot validate against this domain-separated payload.
values.setdefault("trust_domain", BUILDER_ATTESTATION_TRUST_DOMAIN)
value = cls(**values)
if supplied_fingerprint is None:
if require_fingerprint:
raise CapabilityAuthorizationError(
"builder attestation digest is required"
)
return value
expected = _validate_sha(
"builder attestation digest", str(supplied_fingerprint)
)
if not hmac.compare_digest(expected, value.fingerprint):
raise CapabilityAuthorizationError(
"builder attestation digest is invalid"
)
return value
@dataclass(frozen=True)
class BuilderAttestationKeyRecord:
"""One purpose-bound public verification key from the host keyring."""
key_id: str
public_key: bytes
algorithm: str
purpose: str
generation: int
not_before: str
expires_at: str
status: str
def __post_init__(self) -> None:
_validate_identifier("builder_key_id", self.key_id)
if self.algorithm != "Ed25519":
raise ValueError("unsupported builder signature algorithm")
if self.purpose != BUILDER_ATTESTATION_PURPOSE:
raise ValueError("builder key purpose is invalid")
if not isinstance(self.generation, int) or isinstance(
self.generation, bool
) or self.generation < 0:
raise ValueError("builder key generation is invalid")
if self.status not in {"ACTIVE", "REVOKED"}:
raise ValueError("builder key status is invalid")
if len(self.public_key) != 32:
raise ValueError("Ed25519 public key must contain 32 bytes")
Ed25519PublicKey.from_public_bytes(self.public_key)
if _parse_utc(self.not_before) >= _parse_utc(self.expires_at):
raise ValueError("builder key validity window is invalid")
@property
def public_key_sha256(self) -> str:
return hashlib.sha256(self.public_key).hexdigest()
def validate_at(self, instant: datetime) -> None:
if self.status != "ACTIVE":
raise CapabilityAuthorizationError(
"builder attestation key is unavailable or revoked"
)
if instant.tzinfo is None:
raise CapabilityAuthorizationError(
"builder attestation verification time is invalid"
)
if not _parse_utc(self.not_before) <= instant <= _parse_utc(
self.expires_at
):
raise CapabilityAuthorizationError(
"builder attestation key is outside its validity window"
)
@classmethod
def from_dict(
cls, payload: Mapping[str, Any]
) -> "BuilderAttestationKeyRecord":
values = dict(payload)
encoded = values.pop("public_key", values.pop("public_key_b64", None))
if not isinstance(encoded, str):
raise ValueError("builder public key is missing")
try:
public_key = base64.b64decode(encoded, validate=True)
except (TypeError, ValueError) as exc:
raise ValueError("builder public key encoding is invalid") from exc
supplied_digest = values.pop(
"public_key_sha256",
values.pop("key_sha256", values.pop("digest", None)),
)
if not isinstance(supplied_digest, str):
raise ValueError("builder public key digest is missing")
digest = _validate_sha("builder public key digest", supplied_digest)
if not hmac.compare_digest(
digest, hashlib.sha256(public_key).hexdigest()
):
raise ValueError("builder public key digest is invalid")
allowed = {
"algorithm",
"purpose",
"key_id",
"generation",
"not_before",
"expires_at",
"status",
}
unknown = set(values).difference(allowed)
if unknown:
raise ValueError("builder key record contains unknown fields")
return cls(public_key=public_key, **values)
class BuilderAttestationKeyRing:
"""Validated, public-only keyring for builder attestations.
The file format deliberately cannot carry Owner approval keys: both the
keyring and every record must declare the builder-attestation purpose.
"""
def __init__(
self,
records: Iterable[BuilderAttestationKeyRecord],
*,
require_attestation_metadata: bool = False,
):
self._records: dict[str, BuilderAttestationKeyRecord] = {}
self._require_attestation_metadata = require_attestation_metadata
for record in records:
if record.key_id in self._records:
raise ValueError("duplicate builder key id")
self._records[record.key_id] = record
@classmethod
def from_public_keys(
cls, public_keys: Mapping[str, bytes]
) -> "BuilderAttestationKeyRing":
"""Compatibility adapter for explicit in-process builder key maps.
This is intentionally not used for host configuration; host keyrings
must use :meth:`from_file` so purpose, digest, validity and status are
checked before the API accepts traffic.
"""
return cls(
BuilderAttestationKeyRecord(
key_id=_validate_identifier("builder_key_id", key_id),
public_key=bytes(public_key),
algorithm="Ed25519",
purpose=BUILDER_ATTESTATION_PURPOSE,
generation=0,
not_before="1970-01-01T00:00:00Z",
expires_at="9999-12-31T23:59:59Z",
status="ACTIVE",
)
for key_id, public_key in public_keys.items()
)
@classmethod
def from_file(cls, path: Path) -> "BuilderAttestationKeyRing":
payload = json.loads(Path(path).read_text(encoding="utf-8"))
if payload.get("algorithm") != "Ed25519":
raise ValueError("unsupported builder keyring algorithm")
if payload.get("purpose") != BUILDER_ATTESTATION_PURPOSE:
raise ValueError("builder keyring purpose is invalid")
raw_records = payload.get("keys")
if not isinstance(raw_records, list) or not raw_records:
raise ValueError("builder keyring keys are invalid")
allowed = {"algorithm", "purpose", "keys", "keyring_sha256"}
if set(payload).difference(allowed):
raise ValueError("builder keyring contains unknown fields")
if "keyring_sha256" not in payload:
raise ValueError("builder keyring digest is missing")
expected = _validate_sha(
"builder keyring digest", str(payload["keyring_sha256"])
)
canonical = _canonical_json(
{
"algorithm": payload["algorithm"],
"purpose": payload["purpose"],
"keys": raw_records,
}
)
if not hmac.compare_digest(
expected, hashlib.sha256(canonical).hexdigest()
):
raise ValueError("builder keyring digest is invalid")
if not all(isinstance(record, Mapping) for record in raw_records):
raise ValueError("builder keyring record is invalid")
return cls(
(
BuilderAttestationKeyRecord.from_dict(record)
for record in raw_records
),
require_attestation_metadata=True,
)
def verify(
self,
key_id: str,
signature: bytes,
message: bytes,
*,
at: datetime,
generation: int | None,
algorithm: str | None,
purpose: str | None,
) -> BuilderAttestationKeyRecord:
record = self._records.get(key_id)
if record is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
record.validate_at(at)
metadata = (generation, algorithm, purpose)
if self._require_attestation_metadata and any(
value is None for value in metadata
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata is required"
)
if any(value is not None for value in metadata) and metadata != (
record.generation,
record.algorithm,
record.purpose,
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata does not match key"
)
try:
Ed25519PublicKey.from_public_bytes(record.public_key).verify(
signature, message
)
except InvalidSignature as exc:
raise CapabilityAuthorizationError(
"builder attestation signature is invalid"
) from exc
return record
def require_active_identity(
self,
*,
key_id: str,
generation: int,
algorithm: str,
purpose: str,
at: datetime | None = None,
) -> BuilderAttestationKeyRecord:
record = self._records.get(key_id)
if record is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
if (
record.generation != generation
or record.algorithm != algorithm
or record.purpose != purpose
):
raise CapabilityAuthorizationError(
"builder attestation key metadata is invalid"
)
record.validate_at(at or datetime.now(timezone.utc))
return record
class BuilderAttestationVerifier:
"""Public-key-only verifier; no builder private material is accepted."""
def __init__(self, public_keys: Mapping[str, bytes]):
self.public_keys = {
_validate_identifier("builder_key_id", key_id): bytes(value)
for key_id, value in public_keys.items()
}
def __init__(
self,
public_keys: BuilderAttestationKeyRing | Mapping[str, bytes],
):
if isinstance(public_keys, BuilderAttestationKeyRing):
self.keyring = public_keys
elif isinstance(public_keys, Mapping):
self.keyring = BuilderAttestationKeyRing.from_public_keys(
public_keys
)
else:
raise TypeError("builder verifier requires a builder keyring")
def verify(
self,
@@ -793,22 +1110,54 @@ class BuilderAttestationVerifier:
raise CapabilityAuthorizationError(
"builder attestation does not bind candidate evidence"
)
key = self.public_keys.get(attestation.builder_key_id)
if key is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
try:
signature = base64.b64decode(
attestation.signature, validate=True
)
Ed25519PublicKey.from_public_bytes(key).verify(
signature, attestation.canonical_bytes()
key_record = self.keyring.verify(
attestation.builder_key_id,
signature,
attestation.canonical_bytes(),
at=_parse_utc(attestation.issued_at),
generation=attestation.builder_key_generation,
algorithm=attestation.signature_algorithm,
purpose=attestation.signature_purpose,
)
except (InvalidSignature, ValueError, TypeError) as exc:
raise CapabilityAuthorizationError(
"builder attestation signature is invalid"
) from exc
if key_record.generation >= 1:
expected_gitea = hashlib.sha256(
_canonical_json(candidate.gitea_consistency)
).hexdigest()
expected_rollback = hashlib.sha256(
_canonical_json(
{
"ready": candidate.rollback_ready,
"rollback_sha256": release_plan.deployment.rollback_sha256,
"source_commit": candidate.source_commit,
}
)
).hexdigest()
production_evidence = (
attestation.gitea_evidence_sha256,
attestation.rollback_evidence_sha256,
attestation.policy_evidence_sha256,
attestation.secret_scan_evidence_sha256,
)
if any(not value for value in production_evidence):
raise CapabilityAuthorizationError(
"builder attestation production evidence is incomplete"
)
if (
attestation.gitea_evidence_sha256 != expected_gitea
or attestation.rollback_evidence_sha256 != expected_rollback
or attestation.policy_evidence_sha256 != release_plan.policy_sha256
):
raise CapabilityAuthorizationError(
"builder attestation production evidence is invalid"
)
return attestation
@@ -3181,9 +3530,12 @@ class SilentReleaseActionService:
builder_attestation_verifier: BuilderAttestationVerifier | None = None,
trusted_policy: TrustedReleasePolicy | None = None,
orchestrator: ReleaseOrchestrator | None = None,
builder_readiness_probe: Callable[[], Mapping[str, Any]] | None = None,
allow_legacy_candidate_input: bool = False,
expected_repository: str = "",
policy_sha256: str = "",
max_builder_attempts: int = 3,
builder_attempt_timeout_seconds: int = 180,
):
self.root = Path(root)
self.execution_enabled = bool(execution_enabled)
@@ -3194,11 +3546,28 @@ class SilentReleaseActionService:
self.builder_attestation_verifier = builder_attestation_verifier
self.trusted_policy = trusted_policy
self.orchestrator = orchestrator
self.builder_readiness_probe = builder_readiness_probe
self.allow_legacy_candidate_input = bool(
allow_legacy_candidate_input
)
self.expected_repository = expected_repository
self.policy_sha256 = policy_sha256
if (
not isinstance(max_builder_attempts, int)
or isinstance(max_builder_attempts, bool)
or not 1 <= max_builder_attempts <= 10
):
raise ValueError("max_builder_attempts must be between 1 and 10")
self.max_builder_attempts = max_builder_attempts
if (
not isinstance(builder_attempt_timeout_seconds, int)
or isinstance(builder_attempt_timeout_seconds, bool)
or not 1 <= builder_attempt_timeout_seconds <= 3600
):
raise ValueError(
"builder_attempt_timeout_seconds must be between 1 and 3600"
)
self.builder_attempt_timeout_seconds = builder_attempt_timeout_seconds
self.candidates = ReleaseCandidateStore(
self.root / "candidates"
)
@@ -3235,10 +3604,26 @@ class SilentReleaseActionService:
+ ", ".join(missing)
)
def get_builder_readiness(self) -> dict[str, Any]:
"""Report dependency readiness without conflating it with API liveness."""
if self.candidate_builder is None:
return {"status": "UNAVAILABLE"}
if self.builder_readiness_probe is None:
# Explicit in-process test/adaptor construction remains usable;
# production wiring provides a probe backed by its credentials.
return {"status": "READY"}
try:
value = dict(self.builder_readiness_probe())
except Exception:
return {"status": "UNAVAILABLE"}
if value.get("status") != "READY":
return {"status": "UNAVAILABLE"}
return _sanitize(value)
def create_release_intent(
self, payload: Mapping[str, Any]
) -> dict[str, Any]:
allowed = {"event_id", "source_commit"}
allowed = {"event_id", "source_commit", "version"}
unknown = set(payload).difference(allowed)
if unknown:
raise CapabilityAuthorizationError(
@@ -3250,12 +3635,17 @@ class SilentReleaseActionService:
source_commit = _validate_oid(
"source_commit", str(payload.get("source_commit") or "")
)
version = str(payload.get("version") or "")
if version:
_validate_identifier("version", version)
intent_id = "intent-" + hashlib.sha256(
f"{event_id}\0{source_commit}".encode("utf-8")
).hexdigest()[:24]
path = _safe_child(self.intents_root, intent_id)
was_existing = path.exists()
record = {
"created_at": utc_now(),
"attempt_count": 0,
"event_id": event_id,
"intent_id": intent_id,
"source_commit": source_commit,
@@ -3265,42 +3655,153 @@ class SilentReleaseActionService:
else "BUILDER_UNAVAILABLE"
),
}
if path.exists():
if version:
record["version"] = version
if was_existing:
existing = json.loads(path.read_text(encoding="utf-8"))
if (
existing.get("event_id") != event_id
or existing.get("source_commit") != source_commit
or str(existing.get("version") or "") != version
):
raise SagaConflict(
"release intent id is bound to different evidence"
)
return {
**_sanitize(existing),
"idempotent": True,
"execution_enabled": self.execution_enabled,
"shadow_mode": not self.execution_enabled,
"side_effects_executed": False,
if existing.get("status") == "BUILDING":
try:
started_at = _parse_utc(
str(existing.get("attempt_started_at") or "")
)
elapsed = (
datetime.now(timezone.utc) - started_at
).total_seconds()
except (TypeError, ValueError):
elapsed = self.builder_attempt_timeout_seconds + 1
if elapsed <= self.builder_attempt_timeout_seconds:
return self._intent_response(existing, idempotent=True)
existing.update(
{
"failure_code": "TRUSTED_BUILDER_CRASH_RECOVERED",
"last_recovery_code": "TRUSTED_BUILDER_CRASH_RECOVERED",
"recovery_count": int(existing.get("recovery_count") or 0) + 1,
"retryable": int(existing.get("attempt_count") or 0)
< self.max_builder_attempts,
"status": "BUILDER_RETRYABLE_FAILURE",
"updated_at": utc_now(),
}
)
_durable_json(path, existing)
if (
existing.get("status") != "BUILDER_RETRYABLE_FAILURE"
or int(existing.get("attempt_count") or 0)
>= self.max_builder_attempts
):
return self._intent_response(existing, idempotent=True)
record = dict(existing)
if self.candidate_builder is None:
record.update(
{
"retryable": False,
"status": "BUILDER_UNAVAILABLE",
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=was_existing)
record.update(
{
"attempt_count": int(record.get("attempt_count") or 0) + 1,
"attempt_started_at": utc_now(),
"retryable": False,
"status": "BUILDING",
"updated_at": utc_now(),
}
)
_durable_json(path, record)
result = dict(record)
if self.candidate_builder is not None:
try:
candidate = self.create_release_candidate(
{
"event_id": event_id,
"intent_id": intent_id,
"source_commit": source_commit,
**({"version": version} if version else {}),
}
)
result.update(
except TrustedCandidateBuilderUnavailable:
record.update(
{
"candidate_id": candidate["candidate_id"],
"status": candidate["saga_state"],
"failure_code": "TRUSTED_BUILDER_UNAVAILABLE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, result)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except CapabilityAuthorizationError:
record.update(
{
"failure_code": "TRUSTED_BUILDER_REJECTED",
"retryable": False,
"status": "BUILDER_TERMINAL_FAILURE",
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except (ConnectionError, OSError, TimeoutError):
record.update(
{
"failure_code": "TRUSTED_BUILDER_UNAVAILABLE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except Exception:
# The durable intent is recoverable without serializing an
# arbitrary exception (which may contain a credential or host
# path). Repeated requests consume the small fixed retry budget.
record.update(
{
"failure_code": "TRUSTED_BUILDER_FAILURE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
result = dict(record)
result.update(
{
"candidate_id": candidate["candidate_id"],
"retryable": False,
"status": candidate["saga_state"],
}
)
_durable_json(path, result)
return self._intent_response(result, idempotent=False)
def _intent_response(
self, record: Mapping[str, Any], *, idempotent: bool
) -> dict[str, Any]:
return {
**_sanitize(result),
"idempotent": False,
**_sanitize(dict(record)),
"idempotent": idempotent,
"execution_enabled": self.execution_enabled,
"shadow_mode": not self.execution_enabled,
"side_effects_executed": False,
@@ -3321,6 +3822,7 @@ class SilentReleaseActionService:
self, payload: Mapping[str, Any]
) -> dict[str, Any]:
if self.candidate_builder is not None:
values = dict(payload)
forbidden_evidence = {
"artifact_sha256",
"canary",
@@ -3333,13 +3835,69 @@ class SilentReleaseActionService:
"tests",
"tree_oid",
}
if forbidden_evidence.intersection(payload):
if forbidden_evidence.intersection(values):
raise CapabilityAuthorizationError(
"candidate evidence must come from the trusted builder"
)
worker_fields = {"candidate_id", "source_commit", "version"}
intent_fields = {"event_id", "intent_id", "source_commit", "version"}
supplied = set(values)
is_worker_request = worker_fields.issubset(supplied)
if is_worker_request:
if supplied != worker_fields:
raise ValueError("release worker request contains unknown fields")
_validate_identifier(
"candidate_id", str(values.get("candidate_id") or "")
)
_validate_identifier(
"version", str(values.get("version") or "")
)
else:
if not supplied.issubset(intent_fields):
raise ValueError("trusted builder intent contains unknown fields")
# Preserve the established internal intent contract
# (source_commit + version) while allowing newer callers to
# add an event/intent identifier. Neither form can supply
# acceptance evidence; the host builder owns all collectors.
if values.get("event_id"):
_validate_identifier(
"event_id", str(values["event_id"])
)
if values.get("intent_id"):
_validate_identifier(
"intent_id", str(values["intent_id"])
)
if values.get("version"):
_validate_identifier(
"version", str(values["version"])
)
_validate_oid(
"source_commit", str(values.get("source_commit") or "")
)
readiness = self.get_builder_readiness()
if readiness.get("status") != "READY":
raise TrustedCandidateBuilderUnavailable(
"trusted builder dependency is unavailable"
)
bundle = self.candidate_builder.build(dict(payload)).validate(
self.builder_attestation_verifier
)
requested_source = str(payload.get("source_commit") or "")
if (
requested_source
and bundle.candidate.source_commit != requested_source
):
raise CapabilityAuthorizationError(
"trusted builder source commit does not match intent"
)
if (
is_worker_request
and bundle.candidate.candidate_id
!= str(values["candidate_id"])
):
raise CapabilityAuthorizationError(
"trusted builder candidate id does not match request"
)
if self.trusted_policy is None:
raise CapabilityAuthorizationError(
"trusted release policy is not configured"
@@ -0,0 +1,57 @@
{
"schema": "aios.trusted-candidate-builder-policy.v1",
"repository": "aios/aios",
"request_schema": "aios.trusted-candidate-builder.request.v1",
"response_schema": "aios.trusted-candidate-builder.response.v1",
"operation": "BuildTrustedCandidateV1",
"readiness_operation": "ProbeTrustedCandidateBuilderV1",
"maximum_request_age_seconds": 60,
"request_timeout_seconds": 30,
"allowed_peer_uid": 1000,
"allowed_peer_cgroups": [
"/system.slice/aios-gitops-model-api.service"
],
"allowed_peer_executable_prefix": "/usr/bin/python3",
"allowed_peer_runtime_prefix": "/opt/aios/gitops-model-api/",
"allowed_peer_runtime_suffix": "/runtime/app/aios_model_api_staging.py",
"required_checks": [
"authoritative_git",
"artifact",
"canary",
"gitea",
"manifest",
"package_closure",
"rollback",
"secret_scan",
"tests"
],
"evidence_layout": {
"artifact": "{version}/artifact.tar.gz",
"manifest": "{version}/manifest.json",
"package_closure": "{version}/checksums.sha256",
"tests": "{version}/tests.json",
"canary": "{version}/canary.json",
"gitea": "{version}/gitea.json",
"rollback": "{version}/rollback.json",
"policy": "{version}/silent-release-policy.json",
"secret_scan": "{version}/secret-scan.json"
},
"recipes": {
"V2R2C2": {
"mode": "fixed",
"task_id": "r42-release-candidate-v2r2c2-live-20260819",
"candidate_id": "r42-release-candidate-v2r2c2-20260819",
"base_oid": "71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
"source_commit": "71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
"version": "V2R2C2",
"purpose": "release:candidate:trusted-build"
},
"V2R2C3": {
"mode": "governed-derived-only",
"derives_from": "V2R2C2",
"version": "V2R2C3",
"purpose": "release:candidate:trusted-build"
}
},
"governed_recipes": {}
}
@@ -0,0 +1,47 @@
[Unit]
Description=AIOS durable Owner authorization broker
After=network.target aios-chatgpt-bridge-v2-staging.service
Wants=aios-chatgpt-bridge-v2-staging.service
[Service]
Type=simple
User=root
Group=aios-executor-exchange
RuntimeDirectory=aios-owner-authorization-broker
RuntimeDirectoryMode=0750
StateDirectory=aios-owner-authorization-broker
StateDirectoryMode=0750
LoadCredential=agent_task_control_token:/etc/aios-gitops/agent-task-control.token
Environment=AIOS_AGENT_TASK_CONTROL_TOKEN_FILE=%d/agent_task_control_token
Environment=AIOS_OWNER_DURABLE_AUTHORIZATION_POLICY=/etc/aios-gitops/owner-durable-authorization.json
Environment=AIOS_OWNER_DURABLE_AUTHORIZATION_REVOCATIONS=/etc/aios-gitops/owner-durable-authorization-revocations.json
Environment=AIOS_OWNER_AUTH_BROKER_ACCOUNT=chatgpt-governed-implementer-singlehost
Environment=AIOS_OWNER_AUTH_BROKER_SOCKET=%t/aios-owner-authorization-broker/broker.sock
ExecStart=/usr/bin/python3 -B /opt/aios/current/active-runtime/app/aios_durable_owner_authorization.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectKernelLogs=yes
ProtectClock=yes
LockPersonality=yes
RestrictSUIDSGID=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
RestrictAddressFamilies=AF_UNIX AF_INET
IPAddressDeny=any
IPAddressAllow=127.0.0.0/8
ReadOnlyPaths=/etc/aios-gitops/owner-durable-authorization.json
ReadOnlyPaths=/etc/aios-gitops/owner-durable-authorization-revocations.json
InaccessiblePaths=/data/aios-gitops /home
UMask=0007
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,50 @@
[Unit]
Description=AIOS release-candidate Online Agent worker
After=network.target aios-agent-task-api.service
Wants=aios-agent-task-api.service
[Service]
Type=notify
NotifyAccess=main
WatchdogSec=90
User=root
Group=root
StateDirectory=aios-online-agent-release
StateDirectoryMode=0750
LoadCredentialEncrypted=agent_token:/etc/credstore.encrypted/aios-release-online-agent-worker/agent_token
LoadCredentialEncrypted=release_candidate_token:/etc/credstore.encrypted/aios-release-online-agent-worker/release_candidate_token
Environment=AIOS_RELEASE_WORKER_AGENT_ID=online-release-worker-cn-network-mkt-20260819
Environment=AIOS_RELEASE_WORKER_AGENT_VERSION=2026-08-19.1-release-candidate
Environment=AIOS_AGENT_TASK_API_URL=http://127.0.0.1:18940
Environment=AIOS_RELEASE_WORKER_STATE_ROOT=/var/lib/aios-online-agent-release
Environment=AIOS_WORKER_STATE_ROOT=/var/lib/aios-online-agent-release
Environment=AIOS_RELEASE_WORKER_POLL_SECONDS=3
Environment=AIOS_RELEASE_WORKER_LEASE_SECONDS=60
Environment=AIOS_RELEASE_WORKER_HEARTBEAT_SECONDS=15
Environment=AIOS_RELEASE_API_URL=http://127.0.0.1:18890
ExecStart=/usr/bin/python3 -B /opt/aios/current/active-runtime/app/aios_release_online_agent_worker.py
Restart=always
RestartSec=2
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectKernelLogs=yes
ProtectClock=yes
LockPersonality=yes
RestrictSUIDSGID=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
RestrictAddressFamilies=AF_UNIX AF_INET
IPAddressDeny=any
IPAddressAllow=127.0.0.0/8
InaccessiblePaths=/data/aios-gitops /home
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,44 @@
[Unit]
Description=AIOS trusted candidate builder
Requires=aios-trusted-candidate-builder.socket
After=aios-trusted-candidate-builder.socket
[Service]
Type=simple
User=root
Group=root
UMask=0077
LoadCredentialEncrypted=signing_private_key:/etc/credstore.encrypted/aios-trusted-candidate-builder/signing_private_key
LoadCredentialEncrypted=builder_hmac:/etc/credstore.encrypted/aios-trusted-candidate-builder/builder_hmac
Environment=AIOS_TRUSTED_BUILDER_POLICY=/opt/aios/trusted-candidate-builder/current/config/gitops/trusted-candidate-builder-policy.json
Environment=AIOS_TRUSTED_BUILDER_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_TRUSTED_BUILDER_EVIDENCE_ROOT=/var/lib/aios-trusted-candidate-builder/evidence
Environment=AIOS_TRUSTED_BUILDER_NONCE_LEDGER=/var/lib/aios-trusted-candidate-builder/nonce-ledger.json
Environment=AIOS_TRUSTED_BUILDER_KEY_ID=builder-attestation-ed25519-g1
Environment=AIOS_TRUSTED_BUILDER_KEY_GENERATION=1
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -B /opt/aios/trusted-candidate-builder/current/app/aios_trusted_candidate_builder.py --socket-fd 3 --policy ${AIOS_TRUSTED_BUILDER_POLICY}
Restart=on-failure
RestartSec=2
StateDirectory=aios-trusted-candidate-builder
StateDirectoryMode=0700
NoNewPrivileges=true
PrivateTmp=true
PrivateNetwork=true
ProtectSystem=strict
ProtectHome=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
RestrictAddressFamilies=AF_UNIX
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
CapabilityBoundingSet=
ReadOnlyPaths=/opt/aios/trusted-candidate-builder /data/aios-gitops/repo.git /var/lib/aios-trusted-candidate-builder/evidence
ReadWritePaths=/var/lib/aios-trusted-candidate-builder
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,13 @@
[Unit]
Description=AIOS trusted candidate builder AF_UNIX socket
[Socket]
ListenStream=/run/aios-trusted-candidate-builder.sock
SocketUser=root
SocketGroup=gitops
SocketMode=0660
RemoveOnStop=yes
Accept=no
[Install]
WantedBy=sockets.target
@@ -0,0 +1,96 @@
# Canonical all-modified-files packager and secret scanning
## Problem
The historical all-modified-files evidence packager lived only as a copied handoff script.
Its private-key scanner matched the fragment:
```text
BEGIN PRIVATE KEY
```
instead of a complete PEM header. A regression test containing that literal therefore caused a
false secret finding even though the candidate contained no credential.
A task had to use a temporary evidence-only compatibility packager to complete its bundle. That is
acceptable as an emergency workaround but not as a durable unattended path.
## Repository source of truth
The canonical implementation is now:
```text
active-runtime/app/aios_all_modified_files_packager.py
```
It preserves the historical output/bundle schemas:
```text
aios.all-modified-files.bundle.v1
aios.all-modified-files.bundle-summary.v1
aios.modified-documents.index.v1
```
and the host-visible status lines:
```text
ALL_MODIFIED_FILES_BUNDLE=PASS
ALL_MODIFIED_FILES_BUNDLE_PATH=...
ALL_MODIFIED_FILES_BUNDLE_SHA256=...
```
Future governed tasks should use this repository version instead of copying an unversioned scanner.
## Secret scanning invariant
Private-key detection requires a complete PEM header:
```text
-----BEGIN [RSA|EC|DSA|OPENSSH ]PRIVATE KEY-----
```
The following source/test/document text is not secret material and must remain packageable:
```text
BEGIN PRIVATE KEY
PRIVATE KEY
PEM private-key header
```
Bearer and key/value credential detection remains fail-closed:
```text
Authorization: Bearer <20+ token chars>
password=<12+ non-space chars>
passwd=<12+ non-space chars>
secret=<12+ non-space chars>
api_key=<12+ non-space chars>
api-key=<12+ non-space chars>
```
Tests construct the real PEM header dynamically so the scanner's own test source does not contain a
literal credential-looking header.
## Bundle integrity
The packager continues to enforce:
- exact base→candidate ancestry;
- before/after file capture;
- rename/copy/delete handling;
- path traversal denial;
- execution evidence symlink denial;
- secret scan over repository before/after and execution evidence;
- document index;
- SHA-256 closure;
- ZIP without symlinks.
No secret finding is downgraded to a warning.
## Migration
The old handoff script may remain for historical reproducibility, but new handoffs should call the
repository packager from the verified candidate lineage.
A temporary compatibility scanner is no longer needed once this candidate is in the lineage.
@@ -0,0 +1,210 @@
# AIOS Autonomous Control Plane — Admission + Invariant + Repair
## Why this exists
Recent Agent registration, Task claim, release-worker, credential and retry failures shared one
failure mode:
> The control plane accepted an intent before the runtime had proved it could execute the intent.
This design does not add another fleet of daemons. It adds one shared library contract that Task API,
workers and governed host recovery consume.
## Three layers
```text
Intent
↓
Runtime Admission
↓
System Invariants
↓
Repair Policy
↓
Execution
```
### Runtime Admission
Admission occurs before Task claim.
A capability such as `release:candidate` is not considered executable merely because its name
matches. The capability contract also binds:
- supported contract version;
- allowed permission;
- forbidden operations;
- runtime dependencies;
- health requirements.
For `release:candidate`:
```text
permission:
prepare_release
forbidden:
production_deploy
submit_owner_approval
tag_publish
baseline_registry_finalize
runtime_switch
dependencies:
credential_projection
trusted_builder
health:
authenticated_probe
release_api_health
worker_liveness
```
Only a fully passing decision reaches:
```text
EXECUTION_READY
```
A failed decision remains:
```text
EXECUTION_READY_FAILED
```
and the worker does not call `/claim`.
Therefore an infrastructure defect discovered by admission does not consume a business attempt.
### System Invariants
The same admission checks are exposed as a machine-readable invariant snapshot.
Examples:
```text
capability_contract_registry
task_capabilities_registered
worker_capabilities_registered
capability_match
forbidden_operations_absent
credential_projection
trusted_builder
authenticated_probe
release_api_health
worker_liveness
```
A runtime can expose:
```text
HEALTHY
DEGRADED
```
without relying on a human reconstructing state from logs.
### Repair Policy
Failure classification maps to bounded recovery:
```text
DEPENDENCY_UNAVAILABLE → level 0 / auto local
RESOURCE_UNAVAILABLE → level 0 / auto local
WORKER_CRASH → level 0 / auto local
LEASE_LOST → level 0 / auto local
CAPABILITY_CONTRACT_ERROR → level 1 / same-lineage repair task
CREDENTIAL_PROJECTION_ERROR → level 1 / same-lineage repair task
UNKNOWN → level 1 / diagnostic repair task
POLICY_DENIED → level 2 / Owner authorization
BUSINESS_PERMANENT → level 3 / terminal/manual decision
```
Only business-permanent failure consumes the business retry budget in this first contract.
The current Task API does not yet have a first-class `EXECUTION_READY_FAILED` durable state. Until it
does, the Release Worker enforces the most important invariant by refusing to claim a Task whose
admission fails.
## Capability Contract Registry
`aios_capability_contract.py` remains backward-compatible with existing aliases but now validates
every alias target against a versioned semantic contract registry.
Unknown capabilities and wildcard capabilities remain fail-closed.
Agent registration and Task creation should next be changed to call `registered_capability()` so a
capability cannot be accepted at registration and rejected only later by the matcher.
## Credential rule
Admission probes credential usability, not merely credential existence.
For release candidate creation the authenticated probe intentionally sends an invalid empty
`/createReleaseCandidate` request.
Expected:
```text
HTTP 400
```
This proves:
```text
credential readable
→ authentication succeeded
→ scope accepted
→ request validation rejected empty business input
```
A permission failure/401/403/5xx is `EXECUTION_READY_FAILED`.
## Lineage
Repair actions are descriptive, not permission grants. Any repair Task must retain the existing
root/parent/attempt/authority chain and use governed same-lineage recovery.
No repair action may silently create a replacement business Task.
## Authority separation
Capability answers:
```text
can this runtime perform the operation?
```
Authority answers:
```text
is this operation currently authorized?
```
`release:candidate` never implies Owner approval, deployment, tag publication, baseline finalization
or runtime switch.
## Deployment sequence
Phase 1 (this candidate):
- Capability Contract Registry;
- shared Autonomous Control Plane library;
- Release Worker pre-claim admission;
- failure classification + repair evidence.
Phase 2:
- Task API uses the same library before returning Tasks from poll and again during claim CAS;
- Task schema gains durable `ADMITTED/EXECUTION_READY/EXECUTION_READY_FAILED` evidence without
counting pre-side-effect infrastructure failures against business attempts.
Phase 3:
- Agent registration validates against the same Capability Contract Registry;
- external connector writes only durable Intent/Command Inbox records and leaves execution to the
local control plane.
This sequencing avoids a second controller stack while converging every decision onto one contract.
@@ -0,0 +1,228 @@
# Durable Owner Authorization and capability projection
## Problem
AIOS already supports unattended worker execution after a Task is executable, but Owner intent was
not durable at the capability layer. The same Owner repeatedly had to authorize or repair:
- `governed-task:project`;
- `release:candidate`.
This caused valid one-time Owner intent to be lost between conversations and forced repeated human
interaction.
## Design
Durable authorization is represented by a root-owned mode `0600` policy outside Git. The policy is
persistent until explicitly revoked.
The first persistable capabilities are deliberately narrow:
```text
governed-task:project
release:candidate
```
They cover preparation work only.
The following capabilities can never be inherited from this durable grant:
```text
submitOwnerApproval
startDeployment
tag_publish
baseline_registry_finalize
runtime_switch
main_write
production_deploy
force_push
credential_admin
```
Those remain separate high-risk release/Owner-signature gates.
## Policy
Example schema:
```json
{
"schema": "aios.owner-durable-authorization.v1",
"grant_id": "owner-permanent-preparation-20260819",
"owner_identity": "owner@example",
"account": "chatgpt-governed-implementer-singlehost",
"capabilities": [
"governed-task:project",
"release:candidate"
],
"issued_at": "2026-08-19T05:00:00Z",
"not_before": "2026-08-19T05:00:00Z",
"persistent_until_revoked": true,
"constraints": {
"repository": "aios/aios",
"allowed_target_prefix": "agent/",
"release_version_prefix": "V2R2C",
"max_task_risk": "engineering"
}
}
```
The repository never stores the live Owner policy or release credential.
## Revocation
A root-owned mode `0600` revocation file lists `revoked_grant_ids`.
Revocation is checked on every operation, not only at service startup. A running broker therefore
fails closed immediately after revocation without needing a new conversation.
## Broker boundary
The broker exposes only an AF_UNIX socket:
```text
/run/aios-owner-authorization-broker/broker.sock
```
The socket is mode `0660` and owned by the service's `aios-executor-exchange` group. This avoids introducing another long-lived bearer secret solely for local broker access.
It exposes only:
```text
GET /v1/capabilities
POST /v1/project-governed-task
POST /v1/authorize-release-candidate
```
`project-governed-task` calls only the canonical Bridge.
`authorize-release-candidate` persists an Owner authorization decision only. It deliberately does
**not** invent a local trusted-builder endpoint. The external release connector remains responsible
for projecting the account's real `release:candidate` scope. Until that external projection exists,
the decision state is `AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION`.
The broker holds only the existing host control credential needed for canonical governed-task
projection. It never holds an Owner signing key or a release/deployment credential. The caller also supplies the expected account identifier over the Unix transport; the OS socket permission is the local access boundary.
## Canonical Online Task projection
A durable `governed-task:project` grant may create a complete canonical Online Task rather than a
bare Task. It requires:
```text
require_online_task_projection=true
generation=1
branch_mode=CREATE_NEW
max_attempts<=3
risk<=engineering
initial action=source_capture
```
Projected task capabilities are limited to:
```text
git.read
git.test
governed.write.branch
governed.write.file
```
A write-capable task still begins as `source_capture`. Existing placeholder-deferral prevents the
generic worker from claiming it until a deterministic plan is registered. Repository mutation
therefore still requires the normal deterministic plan, WriterLease and governed commit path.
This fixes the previous split-brain behavior where a bare Task could exist without the canonical
governed execution projection.
## External capability projection outbox
A `release:candidate` authorization now creates a durable machine-readable outbox record:
```text
schema=aios.external-capability-projection.request.v1
state=PENDING_EXTERNAL_CONNECTOR
capability=release:candidate
```
The projection identifier is deterministic from:
```text
grant_id
account
capability
request_sha256
```
The outbox stores no credentials or Owner signing material. It retains only the already-authorized
release-candidate request, policy/request hashes, account, grant id and timestamps.
The broker exposes read-only Unix-socket endpoints:
```text
GET /v1/projections
GET /v1/projections/{projection_id}
```
These endpoints re-check the current Owner policy on every read. If the durable grant has been
revoked, enumeration fails closed with `OWNER_GRANT_REVOKED`. The historical outbox record is
retained for audit and is not silently deleted.
The repository intentionally does **not** provide a connector acknowledgement/write endpoint yet.
No in-repository code can truthfully assert that the external ChatGPT connector account has gained
`release:candidate`. Until the platform consumes the outbox and performs its own identity-scope
projection, the only valid state is:
```text
AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION
PENDING_EXTERNAL_CONNECTOR
```
A future external projector must preserve:
```text
release:candidate
!= submitOwnerApproval
!= startDeployment
!= tag_publish
!= baseline_registry_finalize
!= runtime_switch
```
## Idempotency and audit
Every broker operation requires an idempotency key. A receipt SQLite database stores only:
- idempotency key;
- capability;
- request SHA-256;
- response SHA-256;
- sanitized upstream response;
- timestamp.
An exact retry returns the same receipt. Reusing an idempotency key with different content fails.
## Unattended invariant
Once the Owner policy and narrow service credentials are provisioned:
```text
Owner durable grant
→ broker decision
→ canonical governed task projection / release candidate preparation
→ worker / trusted builder
```
No repeated Owner interaction is required for those preparation capabilities.
Production execution remains:
```text
release candidate
→ immutable approval envelope
→ Owner signature
→ deployment
```
The durable grant never substitutes for the Owner signature.
@@ -0,0 +1,114 @@
# Owner-approved GPT HTTPS edge activation and rollback
## Scope
This controller is the final host-side control mechanism before the disabled-by-default GPT HTTPS
edge may bind publicly.
Repository implementation and public activation remain separate states:
```text
IMPLEMENTED
≠
OWNER_APPROVED
≠
ACTIVATED
≠
RUNTIME_VERIFIED
```
The controller never creates owner approval, TLS assets, DNS, firewall rules or ingress bearer
credentials.
## Approval binding
The owner approval file is root-owned mode `0600` and has schema:
```json
{
"schema": "aios.gpt-https-edge.public-approval.v1",
"approval_id": "owner-public-edge-...",
"candidate_oid": "<exact governed edge candidate>",
"domain": "tasks.example.com",
"public_bind": true,
"bind_host": "0.0.0.0",
"port": 18443,
"tls_cert_sha256": "<exact certificate sha256>",
"issued_at": "2026-08-19T00:00:00Z",
"not_before": "2026-08-19T00:00:00Z",
"expires_at": "2026-08-20T00:00:00Z",
"owner": "<owner identity>"
}
```
Approval lifetime is at most seven days. Candidate/domain/bind/port/certificate are immutable within
one approval.
Changing any of those requires a new owner approval.
## Check
```text
aios_gpt_https_edge_activation.py check --candidate <GE>
```
Checks:
- approval ownership/mode/schema/time window;
- exact candidate binding;
- exact certificate hash;
- certificate/private-key pair;
- certificate hostname;
- certificate remaining lifetime;
- loopback GPT ingress service health.
It performs no public network mutation.
## Activate
`activate` additionally:
- writes one exact systemd public-bind drop-in atomically;
- enables/restarts only the HTTPS edge unit;
- verifies effective ExecStart points at the immutable candidate release;
- verifies effective public-bind/domain environment;
- performs a TLS handshake to `127.0.0.1:18443` using the approved public hostname;
- writes a durable activation receipt.
Any failure after drop-in mutation automatically invokes rollback.
Exact replay of an already-active matching approval returns the durable receipt without restarting
or creating duplicate state.
## Rollback
`rollback`:
- stops and disables only the HTTPS edge;
- removes only the controller-owned public-bind drop-in;
- daemon-reloads systemd;
- verifies edge inactive;
- writes a durable rollback receipt.
It does not delete:
- owner approval;
- TLS certificate/private key;
- ingress bearer token;
- internal loopback ingress;
- Task/Job state;
- Git branches.
Rollback is therefore replay-safe and does not require reconstructing secrets.
## Not covered
This controller intentionally does not own:
- DNS records;
- host firewall/NAT/security-group changes;
- certificate issuance/renewal;
- OpenAI Custom GPT domain verification;
- public Internet acceptance from outside the host.
Those remain separate owner/environment gates.
@@ -0,0 +1,87 @@
# Release candidate capability contract remediation
## Problem
The release-preparation worker can be correctly registered with the raw capability:
```text
release:candidate
```
while the canonical Task API matcher still rejects the worker.
Before this repair, the capability matrix is:
```text
worker raw = ["release:candidate"]
worker effective= []
task required = ["exact:release:candidate"]
eligible = false
```
The Task therefore remains `QUEUED`, `attempt=0`, and cannot be claimed even though both the Task and
worker use the same literal capability.
## Root cause
`aios_capability_contract.py` is deliberately fail-closed:
- task-side unknown names become `exact:<name>`;
- worker-side unknown names are discarded;
- wildcard worker capabilities are ignored.
`release:candidate` was absent from both canonical alias tables.
## Repair
Add exactly one semantic capability to both vocabularies:
```text
release:candidate → release:candidate
```
Do not add:
```text
release:*
submitOwnerApproval
startDeployment
tag_publish
baseline_registry_finalize
runtime_switch
```
Unknown names and wildcards must continue to fail closed.
## Runtime recovery
After the governed remediation candidate is officially verified:
1. install the candidate capability-contract module as a higher-precedence Task API overlay;
2. restart only `aios-agent-task-api.service`;
3. require `/health` success;
4. require the fixed existing release Task to remain the same Task/idempotency;
5. let the already-active dedicated release worker poll and claim it automatically;
6. require heartbeat, trusted-builder result and official verification;
7. do not manually claim or create a replacement live Task.
The fixed V2R2C2 live Task may complete as the acceptance candidate it was originally bound to.
Because this remediation is a later source commit, the first release candidate that includes the
matcher repair should use the next immutable version (for example `V2R2C3`) rather than rewriting an
already-created V2R2C2 candidate.
## Governance boundary
This repair affects only Task eligibility. It grants no credential and performs no release action by
itself.
The dedicated release worker still requires its independent `release:candidate` credential, and the
following remain separate:
```text
Owner signed approval
deployment
tag publication
baseline finalization
runtime switch
```
@@ -0,0 +1,82 @@
# Fixed V2R2C2 release lineage contract
## Why this contract exists
AIOS has multiple engineering commits in the same ancestry, and they serve different purposes.
The fixed V2R2C2 live Task was created by the dedicated Release Worker activation. Its immutable
release source is therefore the Release Worker implementation candidate:
```text
71e736b05fab13efc06af7a4fe2dc022c1cf9a85
```
Later engineering commits are descendants but are not retroactive V2R2C2 release sources:
```text
17deb91ca7a187dc81766945b1174e854f86bd25
capability-contract remediation
b4b4416c93fedbe451d655d49e28f915b8fd38e6
Autonomous Control Plane engineering candidate
```
Conflating "current engineering base" with "immutable release source" caused ACP activation to
fail closed with `FIXED_RELEASE_SOURCE_DRIFT`.
## Canonical fixed lineage
```text
Task:
r42-release-candidate-v2r2c2-live-20260819
candidate_id:
r42-release-candidate-v2r2c2-20260819
source_commit:
71e736b05fab13efc06af7a4fe2dc022c1cf9a85
version:
V2R2C2
required_capabilities:
["release:candidate"]
```
This contract is immutable for V2R2C2.
## What may change
The following are lifecycle state, not release identity:
```text
status
attempt
max_attempts
revision
claimed_by
lease
verification
```
They may change through official retry/claim/result/verification without changing the release
lineage.
## What must never be "repaired"
Do not rewrite the fixed V2R2C2 Task source to a later engineering commit.
A later engineering source that must itself be released receives a later immutable release version
(for example V2R2C3), not a mutation of V2R2C2.
## Activation invariant
ACP activation must:
1. verify its own governed engineering candidate separately;
2. read the live fixed V2R2C2 Task;
3. validate that Task through `validate_fixed_v2r2c2_task()`;
4. never compare the fixed release source to the ACP engineering base/candidate;
5. never mutate the fixed Task merely to satisfy activation.
This makes engineering lineage and release lineage explicit and independently verifiable.
@@ -0,0 +1,142 @@
# Release Preparation Online Agent
## Purpose
This worker closes the release-preparation execution gap without widening the ChatGPT connector
identity.
The dedicated Agent identity is:
```text
online-release-worker-cn-network-mkt-20260819
```
Its only advertised task capability is:
```text
release:candidate
```
The worker may create an immutable release candidate, but it never submits Owner approval and never
starts deployment, publishes a tag, finalizes a baseline, switches runtime or writes Git.
## Credentials
The worker uses two independently scoped credentials:
```text
agent_token
release_candidate_token
```
`agent_token` is the Online Agent registration credential. It must be encrypted into systemd's
credential store and must never be written to Git, Task metadata, evidence or logs.
`release_candidate_token` is provisioned during Owner-authorized runtime activation. Only its SHA-256 record is stored in the release API token registry; the plaintext is immediately encrypted into systemd credstore and the temporary plaintext is removed. Its only allowed scope is:
```text
release:candidate
```
It is explicitly not an Owner signing key and must not grant:
```text
submitOwnerApproval
startDeployment
tag_publish
baseline_registry_finalize
runtime_switch
```
The unit reads both credentials through systemd `LoadCredentialEncrypted`; neither plaintext token is kept in the unit, repository, Task metadata, evidence, or logs.
## Task contract
The worker will only claim Tasks whose exact required capability set is:
```json
["release:candidate"]
```
The Task must provide a bounded `constraints.release_candidate` payload (metadata is accepted only for compatibility):
```json
{
"candidate_id": "...",
"source_commit": "<40 hex>",
"version": "V2R2C2",
"projection_id": "projection-..."
}
```
No caller-supplied URL, method, headers, command, shell, environment, credential path, deployment
operation or arbitrary release route is accepted.
The release API base is host configuration, not Task input. The only release mutation invoked by the
worker is:
```text
POST /createReleaseCandidate
```
## Lifecycle
```text
poll
→ claim
→ heartbeat
→ createReleaseCandidate
→ result submit
→ official verifier
```
The result declares:
```text
writer_lease=NONE
changed_files=[]
direct_main_write=false
production_write=false
tag_write=false
baseline_write=false
runtime_switch=false
owner_approval_submitted=false
```
## Remaining high-risk gate
Successful release-candidate creation does not authorize Production.
The next stage remains:
```text
release candidate
→ immutable approval envelope
→ Owner signature
→ startDeployment
```
That separation is mandatory even when release preparation is fully unattended.
## Unattended availability
The release worker has its own systemd watchdog rather than modifying the shared worker-availability
guard contract.
Every successful Task poll and Task heartbeat emits:
```text
WATCHDOG=1
```
and the service uses:
```text
Type=notify
WatchdogSec=90
Restart=always
```
A process that is technically alive but no longer polling/heartbeating therefore gets restarted by
systemd. The worker also writes `worker-liveness.json` for operator diagnostics.
+83 -20
View File
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import gzip
import hashlib
import json
import os
from pathlib import Path
import stat
import tarfile
import tempfile
@@ -17,8 +19,18 @@ REQUIRED_APP = (
'gitea_tag_publication.py',
'silent_release_controller.py',
'silent_release_hardening.py',
'aios_trusted_candidate_builder.py',
'aios_gitops_job_controller.py',
)
REQUIRED_RUNTIME = (
'orchestrator/__init__.py',
'orchestrator/action_monitor.py',
)
REQUIRED_BUILDER_ASSETS = (
Path('config/gitops/trusted-candidate-builder-policy.json'),
Path('deploy/systemd/aios-trusted-candidate-builder.socket'),
Path('deploy/systemd/aios-trusted-candidate-builder.service'),
)
OPTIONAL_APP = (
'aios_chatgpt_actions_api.py',
'aios_continuation_executor.py',
@@ -44,6 +56,38 @@ def private_marker(path: Path) -> bool:
)
return any(re.search(pattern, text) for pattern in patterns)
def checked_regular_file(path: Path, label: str) -> Path:
forbidden_parts = {'.git', 'credential', 'credentials', 'secret', 'secrets'}
if forbidden_parts.intersection(part.lower() for part in Path(label).parts):
raise SystemExit(f'credential path rejected: {label}')
if path.is_symlink():
raise SystemExit(f'symlink rejected: {label}')
try:
mode = path.stat(follow_symlinks=False).st_mode
except FileNotFoundError:
raise SystemExit(f'required dependency missing: {label}') from None
if not stat.S_ISREG(mode):
raise SystemExit(f'non-regular file rejected: {label}')
return path
def normalized_tar_info(path: Path, arcname: str) -> tarfile.TarInfo:
info = tarfile.TarInfo(arcname)
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = 'root'
info.gname = 'root'
if path.is_dir():
info.type = tarfile.DIRTYPE
info.mode = 0o755
info.size = 0
else:
info.type = tarfile.REGTYPE
source_mode = path.stat(follow_symlinks=False).st_mode
info.mode = 0o755 if source_mode & stat.S_IXUSR else 0o644
info.size = path.stat(follow_symlinks=False).st_size
return info
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument('--source-root', required=True, type=Path)
@@ -63,27 +107,48 @@ def main() -> int:
paths = []
for name in REQUIRED_APP:
path = app / name
if not path.is_file():
raise SystemExit(f'required app dependency missing: {name}')
checked_regular_file(path, f'active-runtime/app/{name}')
paths.append((Path('app') / name, path))
for name in OPTIONAL_APP:
path = app / name
if path.is_symlink():
raise SystemExit(f'symlink rejected: active-runtime/app/{name}')
if path.is_file():
checked_regular_file(path, f'active-runtime/app/{name}')
paths.append((Path('app') / name, path))
for rel in ('orchestrator/__init__.py', 'orchestrator/action_monitor.py'):
for rel in REQUIRED_RUNTIME:
path = runtime / rel
if not path.is_file():
raise SystemExit(f'required runtime dependency missing: {rel}')
checked_regular_file(path, f'runtime/agent-os/{rel}')
paths.append((Path('runtime-agent-os') / rel, path))
for rel in REQUIRED_BUILDER_ASSETS:
path = source / rel
checked_regular_file(path, str(rel).replace('\\', '/'))
paths.append((rel, path))
openapi = docs / 'openapi-chatgpt-actions.yaml'
if openapi.is_symlink():
raise SystemExit('symlink rejected: docs/gitops/openapi-chatgpt-actions.yaml')
if openapi.is_file():
checked_regular_file(openapi, 'docs/gitops/openapi-chatgpt-actions.yaml')
paths.append((Path('docs') / 'gitops' / 'openapi-chatgpt-actions.yaml', openapi))
for folder in ('config/model_registry', 'config/model_priority', 'patches', 'versions'):
root = source / folder
if root.is_dir():
for path in sorted(root.rglob('*')):
if path.is_symlink():
raise SystemExit(f'symlink rejected: {path.relative_to(source)}')
if not path.is_dir() and not path.is_file():
raise SystemExit(f'special file rejected: {path.relative_to(source)}')
if path.is_file() and path.suffix.lower() in {'.json', '.yaml', '.yml', '.md'}:
checked_regular_file(path, str(path.relative_to(source)).replace('\\', '/'))
paths.append((Path(folder) / path.relative_to(root), path))
seen: set[str] = set()
for rel, _ in paths:
normalized = str(rel).replace('\\', '/')
if normalized.startswith('/') or '..' in Path(normalized).parts:
raise SystemExit(f'path traversal rejected: {normalized}')
if normalized in seen:
raise SystemExit(f'duplicate archive member rejected: {normalized}')
seen.add(normalized)
for rel, path in paths:
if private_marker(path):
raise SystemExit(f'private marker rejected: {rel}')
@@ -110,27 +175,25 @@ def main() -> int:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(path.read_bytes())
(root / 'manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + '\n', encoding='utf-8')
with tarfile.open(archive, 'w:gz', format=tarfile.PAX_FORMAT) as tf:
for path in sorted(root.rglob('*')):
rel = path.relative_to(root.parent)
info = tf.gettarinfo(str(path), arcname=str(rel).replace('\\', '/'))
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = 'root'
info.gname = 'root'
if path.is_file():
with path.open('rb') as fh:
tf.addfile(info, fh)
else:
tf.addfile(info)
with archive.open('wb') as raw:
with gzip.GzipFile(filename='', mode='wb', fileobj=raw, mtime=0) as compressed:
with tarfile.open(fileobj=compressed, mode='w', format=tarfile.PAX_FORMAT) as tf:
for path in sorted(root.rglob('*')):
rel = path.relative_to(root.parent)
arcname = str(rel).replace('\\', '/')
info = normalized_tar_info(path, arcname)
if path.is_file():
with path.open('rb') as fh:
tf.addfile(info, fh)
else:
tf.addfile(info)
digest = sha256(archive)
receipt = {
'schema': 'aios.v2r2c1.model-api-staging-build-receipt.v1',
'target_version': 'V2R2C1',
'source_oid': oid,
'repository_identity': 'aios/aios',
'artifact_path': str(archive),
'artifact_path': archive.name,
'artifact_sha256': digest,
'deployment_state': 'STAGING',
'manifest_schema': SCHEMA,
+252
View File
@@ -0,0 +1,252 @@
from __future__ import annotations
from datetime import datetime, timezone
import json
from pathlib import Path
import subprocess
import sys
import zipfile
import pytest
ROOT=Path(__file__).resolve().parents[1]
APP=ROOT/"active-runtime/app"
sys.path.insert(0,str(APP))
import aios_all_modified_files_packager as packager
def init_repo(tmp_path: Path) -> tuple[Path, Path, str]:
bare=tmp_path/"repo.git"
work=tmp_path/"work"
subprocess.run(["git","init","--bare",str(bare)],check=True,capture_output=True)
subprocess.run(["git","clone",str(bare),str(work)],check=True,capture_output=True)
subprocess.run(["git","-C",str(work),"config","user.email","test@example.invalid"],check=True)
subprocess.run(["git","-C",str(work),"config","user.name","AIOS Test"],check=True)
(work/"README.md").write_text("base\n",encoding="utf-8")
subprocess.run(["git","-C",str(work),"add","README.md"],check=True)
subprocess.run(["git","-C",str(work),"commit","-m","base"],check=True,capture_output=True)
subprocess.run(["git","-C",str(work),"push","origin","HEAD:refs/heads/main"],check=True,capture_output=True)
base=subprocess.check_output(["git","-C",str(work),"rev-parse","HEAD"],text=True).strip()
return bare,work,base
def commit(work: Path, message: str) -> str:
subprocess.run(["git","-C",str(work),"add","-A"],check=True)
subprocess.run(["git","-C",str(work),"commit","-m",message],check=True,capture_output=True)
candidate=subprocess.check_output(["git","-C",str(work),"rev-parse","HEAD"],text=True).strip()
subprocess.run(["git","-C",str(work),"push","origin","HEAD:refs/heads/main"],check=True,capture_output=True)
return candidate
def real_pem_header() -> bytes:
return b"-----" + b"BEGIN " + b"PRIVATE KEY" + b"-----"
def bearer_value() -> bytes:
return b"Authorization" + b": Bearer " + (b"a"*40)
def key_value_secret() -> bytes:
return b"api_key" + b"=" + (b"x"*24)
def test_plain_private_key_words_are_not_secret_findings():
for value in (
b"BEGIN PRIVATE KEY",
b"PRIVATE KEY",
b"PEM private-key header",
b"assert 'BEGIN PRIVATE KEY' in message",
):
assert packager.secret_finding(value) is None
def test_complete_pem_private_key_header_is_rejected():
assert packager.secret_finding(real_pem_header())=="PEM_PRIVATE_KEY"
assert packager.secret_finding(
b"-----BEGIN OPENSSH " + b"PRIVATE KEY-----"
)=="PEM_PRIVATE_KEY"
def test_bearer_and_key_value_secret_detection_remains_fail_closed():
assert packager.secret_finding(bearer_value())=="AUTHORIZATION_BEARER"
assert packager.secret_finding(key_value_secret())=="KEY_VALUE_SECRET"
def test_short_or_descriptive_secret_words_do_not_false_positive():
assert packager.secret_finding(b"secret=short") is None
assert packager.secret_finding(b"password field is validated") is None
assert packager.secret_finding(b"api_key example placeholder") is None
def test_end_to_end_bundle_accepts_literal_begin_private_key_text(tmp_path):
bare,work,base=init_repo(tmp_path)
(work/"tests").mkdir()
(work/"tests"/"test_text.py").write_text(
'ERROR = "BEGIN PRIVATE KEY is not a complete PEM header"\n',
encoding="utf-8",
)
(work/"docs").mkdir()
(work/"docs"/"note.md").write_text("Scanner regression.\n",encoding="utf-8")
candidate=commit(work,"candidate")
extra=tmp_path/"execution"
extra.mkdir()
(extra/"evidence.json").write_text('{"status":"PASS"}\n',encoding="utf-8")
out=tmp_path/"out"
summary=packager.build_bundle(
git_dir=bare,
base=base,
candidate=candidate,
task_id="r42-packager-test",
extra_dir=extra,
output_root=out,
now=datetime(2026,8,19,tzinfo=timezone.utc),
)
assert summary["changed_file_count"]==2
assert summary["execution_file_count"]==1
assert summary["repository_document_count"]==1
assert summary["execution_document_count"]==0
bundle_path=Path(summary["bundle_path"])
assert bundle_path.is_file()
assert packager.sha256(bundle_path.read_bytes())==summary["bundle_sha256"]
with zipfile.ZipFile(bundle_path) as archive:
names=set(archive.namelist())
assert "repo/after/tests/test_text.py" in names
assert "repo/after/docs/note.md" in names
assert "execution/evidence.json" in names
assert "documents-index.json" in names
assert "all-modified-files-manifest.json" in names
assert "checksums.sha256" in names
manifest=json.loads(archive.read("all-modified-files-manifest.json"))
assert manifest["schema"]=="aios.all-modified-files.bundle.v1"
assert manifest["candidate_oid"]==candidate
packager.verify_checksums(out/"bundle")
def test_end_to_end_bundle_rejects_real_pem_header(tmp_path):
bare,work,base=init_repo(tmp_path)
(work/"credential.txt").write_bytes(
real_pem_header()+b"\nnot-a-real-key-body\n"
)
candidate=commit(work,"credential-like candidate")
with pytest.raises(
packager.PackagerError,
match="PEM_PRIVATE_KEY:after:credential.txt",
):
packager.build_bundle(
git_dir=bare,
base=base,
candidate=candidate,
task_id="r42-packager-pem-reject",
output_root=tmp_path/"out",
)
def test_execution_evidence_bearer_is_rejected(tmp_path):
bare,work,base=init_repo(tmp_path)
(work/"normal.txt").write_text("normal\n",encoding="utf-8")
candidate=commit(work,"normal")
extra=tmp_path/"execution"
extra.mkdir()
(extra/"unsafe.txt").write_bytes(bearer_value()+b"\n")
with pytest.raises(
packager.PackagerError,
match="AUTHORIZATION_BEARER:execution:unsafe.txt",
):
packager.build_bundle(
git_dir=bare,
base=base,
candidate=candidate,
task_id="r42-packager-bearer-reject",
extra_dir=extra,
output_root=tmp_path/"out",
)
def test_execution_symlink_is_rejected(tmp_path):
bare,work,base=init_repo(tmp_path)
(work/"normal.txt").write_text("normal\n",encoding="utf-8")
candidate=commit(work,"normal")
extra=tmp_path/"execution"
extra.mkdir()
target=extra/"target.txt"
target.write_text("safe\n",encoding="utf-8")
(extra/"link.txt").symlink_to(target)
with pytest.raises(
packager.PackagerError,
match="EXTRA_FILE_SYMLINK_REJECTED",
):
packager.build_bundle(
git_dir=bare,
base=base,
candidate=candidate,
task_id="r42-packager-symlink-reject",
extra_dir=extra,
output_root=tmp_path/"out",
)
def test_non_ancestor_lineage_is_rejected(tmp_path):
bare,work,base=init_repo(tmp_path)
(work/"one.txt").write_text("one\n",encoding="utf-8")
first=commit(work,"first")
subprocess.run(["git","-C",str(work),"checkout","--detach",base],check=True,capture_output=True)
(work/"other.txt").write_text("other\n",encoding="utf-8")
subprocess.run(["git","-C",str(work),"add","other.txt"],check=True)
subprocess.run(["git","-C",str(work),"commit","-m","other"],check=True,capture_output=True)
other=subprocess.check_output(["git","-C",str(work),"rev-parse","HEAD"],text=True).strip()
subprocess.run(
["git","-C",str(work),"push","origin","HEAD:refs/heads/other"],
check=True,
capture_output=True,
)
with pytest.raises(packager.PackagerError,match="LINEAGE_INVALID"):
packager.validate_lineage(bare,first,other)
def test_bundle_checksum_closure_detects_tamper(tmp_path):
bare,work,base=init_repo(tmp_path)
(work/"normal.txt").write_text("normal\n",encoding="utf-8")
candidate=commit(work,"normal")
out=tmp_path/"out"
packager.build_bundle(
git_dir=bare,
base=base,
candidate=candidate,
task_id="r42-packager-checksum",
output_root=out,
)
(out/"bundle"/"repo"/"after"/"normal.txt").write_text(
"tampered\n",
encoding="utf-8",
)
with pytest.raises(
packager.PackagerError,
match="CHECKSUM_CLOSURE_INVALID",
):
packager.verify_checksums(out/"bundle")
def test_source_does_not_contain_complete_pem_header_literal():
source=(ROOT/"active-runtime/app/aios_all_modified_files_packager.py").read_bytes()
tests=(ROOT/"tests/test_all_modified_files_packager.py").read_bytes()
complete=real_pem_header()
assert complete not in source
assert complete not in tests
def test_document_path_contract():
assert packager.is_document_path("docs/spec.md")
assert packager.is_document_path("README")
assert packager.is_document_path("CHANGELOG.md")
assert not packager.is_document_path("active-runtime/app/main.py")
+157
View File
@@ -0,0 +1,157 @@
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT=Path(__file__).resolve().parents[1]
APP=ROOT/"active-runtime/app"
sys.path.insert(0,str(APP))
import aios_autonomous_control_plane as acp
import aios_capability_contract as contract
import aios_release_online_agent_worker as worker
def _ready_probes():
return {
"credential_projection": lambda: True,
"trusted_builder": lambda: True,
"authenticated_probe": lambda: True,
"release_api_health": lambda: True,
"worker_liveness": lambda: True,
}
def _task():
return {
"task_id":"r42-autonomous-release-admission-test",
"required_capabilities":["release:candidate"],
"constraints":{
"release_candidate":{
"candidate_id":"r42-release-candidate-v2r2c9-test",
"source_commit":"1"*40,
"version":"V2R2C9",
"projection_id":"projection-"+"a"*32,
}
},
}
def test_capability_contract_registry_is_closed_and_release_is_versioned():
status=contract.validate_capability_contract_registry()
assert status["valid"] is True
release=contract.capability_contract("release:candidate")
assert release is not None
assert release.min_version==1
assert release.max_version==1
assert release.permissions==("prepare_release",)
assert "credential_projection" in release.dependencies
assert "trusted_builder" in release.dependencies
assert "authenticated_probe" in release.health_requirements
assert "production_deploy" in release.forbidden
assert "runtime_switch" in release.forbidden
def test_unknown_agent_capability_cannot_register_or_match():
assert contract.registered_capability("release:future",worker=True) is False
assert contract.registered_capability("release:*",worker=True) is False
assert contract.has_capabilities(["release:future"],["release:future"]) is False
def test_runtime_admission_requires_all_release_execution_readiness():
decision=acp.runtime_admission(
worker_capabilities=["release:candidate"],
required_capabilities=["release:candidate"],
requested_operations=["prepare_release"],
probes=_ready_probes(),
)
assert decision.admitted is True
assert decision.state=="EXECUTION_READY"
def test_runtime_admission_blocks_credential_failure_before_execution():
probes=_ready_probes()
probes["credential_projection"]=lambda:False
probes["authenticated_probe"]=lambda:False
decision=acp.runtime_admission(
worker_capabilities=["release:candidate"],
required_capabilities=["release:candidate"],
requested_operations=["prepare_release"],
probes=probes,
)
assert decision.admitted is False
assert decision.state=="EXECUTION_READY_FAILED"
assert decision.failure_class==acp.FailureClass.CREDENTIAL_PROJECTION_ERROR
assert decision.repair_level==acp.RepairLevel.AUTO_REPAIR_TASK
def test_forbidden_release_operation_requires_owner_boundary():
decision=acp.runtime_admission(
worker_capabilities=["release:candidate"],
required_capabilities=["release:candidate"],
requested_operations=["production_deploy"],
probes=_ready_probes(),
)
assert decision.admitted is False
assert decision.failure_class==acp.FailureClass.POLICY_DENIED
assert decision.repair_level==acp.RepairLevel.OWNER_APPROVAL
@pytest.mark.parametrize(
"error,phase,expected,level,consumes",
[
("Permission denied: gitops.tokens","release",acp.FailureClass.CREDENTIAL_PROJECTION_ERROR,acp.RepairLevel.AUTO_REPAIR_TASK,False),
("HTTP_TRANSPORT_UNAVAILABLE","network",acp.FailureClass.DEPENDENCY_UNAVAILABLE,acp.RepairLevel.AUTO_LOCAL,False),
("TASK_CAPABILITY_SCOPE_INVALID","validation",acp.FailureClass.CAPABILITY_CONTRACT_ERROR,acp.RepairLevel.AUTO_REPAIR_TASK,False),
("lease lost","heartbeat",acp.FailureClass.LEASE_LOST,acp.RepairLevel.AUTO_LOCAL,False),
("business permanent invalid release","release",acp.FailureClass.BUSINESS_PERMANENT,acp.RepairLevel.MANUAL,True),
],
)
def test_failure_classification_drives_repair_policy(error,phase,expected,level,consumes):
failure=acp.classify_failure(error,phase=phase)
assert failure==expected
repair=acp.repair_for_failure(failure)
assert repair.level==level
assert repair.consumes_business_attempt is consumes
def test_release_worker_does_not_claim_when_admission_is_not_ready(monkeypatch):
calls=[]
def fake_http(base,method,route,token,body=None,timeout=30):
calls.append((base,method,route,body))
if route=="/health":
return 200,{"status":"ok"}
if route=="/createReleaseCandidate" and body=={}:
return 500,{"error":"Permission denied: gitops.tokens"}
if route.endswith("/claim"):
raise AssertionError("claim must not happen before EXECUTION_READY")
raise AssertionError(route)
events=[]
monkeypatch.setattr(worker,"http_json",fake_http)
monkeypatch.setattr(worker,"audit",lambda event,**fields:events.append((event,fields)))
monkeypatch.setattr(worker,"write_liveness",lambda *args,**kwargs:None)
assert worker.execute_task(
_task(),
agent_token="a"*32,
release_token="r"*32,
) is False
assert not any(route.endswith("/claim") for _,_,route,_ in calls)
blocked=[item for item in events if item[0]=="TASK_ADMISSION_BLOCKED"]
assert len(blocked)==1
assert blocked[0][1]["admission"]["state"]=="EXECUTION_READY_FAILED"
def test_system_invariant_snapshot_is_degraded_when_dependency_is_missing():
probes=_ready_probes()
probes["trusted_builder"]=lambda:False
snapshot=acp.system_invariant_snapshot(
worker_capabilities=["release:candidate"],
required_capabilities=["release:candidate"],
probes=probes,
)
assert snapshot["status"]=="DEGRADED"
assert snapshot["execution_ready"] is False
+627
View File
@@ -0,0 +1,627 @@
from __future__ import annotations
from datetime import datetime, timezone
import json
from pathlib import Path
import sqlite3
import sys
import pytest
ROOT=Path(__file__).resolve().parents[1]
APP=ROOT/"active-runtime/app"
sys.path.insert(0,str(APP))
import aios_durable_owner_authorization as auth
NOW=datetime(2026,8,19,6,0,0,tzinfo=timezone.utc)
ACCOUNT="chatgpt-governed-implementer-singlehost"
def write_root_file(path: Path, value: object) -> Path:
path.parent.mkdir(parents=True,exist_ok=True)
path.write_text(json.dumps(value,sort_keys=True)+"\n",encoding="utf-8")
path.chmod(0o600)
return path
def policy_value(**overrides):
value={
"schema":auth.POLICY_SCHEMA,
"grant_id":"owner-permanent-preparation-20260819",
"owner_identity":"owner-singlehost",
"account":ACCOUNT,
"capabilities":[
"governed-task:project",
"release:candidate",
],
"issued_at":"2026-08-19T05:00:00Z",
"not_before":"2026-08-19T05:00:00Z",
"persistent_until_revoked":True,
"constraints":{
"repository":"aios/aios",
"allowed_target_prefix":"agent/",
"release_version_prefix":"V2R2C",
"max_task_risk":"engineering",
},
}
value.update(overrides)
return value
def load_policy(tmp_path,monkeypatch,**overrides):
path=write_root_file(tmp_path/"policy.json",policy_value(**overrides))
revocations=write_root_file(
tmp_path/"revocations.json",
{"revoked_grant_ids":[]},
)
monkeypatch.setattr(auth,"validate_root_file",lambda path,**kwargs:path)
return auth.DurablePolicy.load(
path,
revocations,
now=NOW,
),path,revocations
def governed_request(**overrides):
value={
"task_id":"r42-owner-auth-projection-test-20260819",
"repository":"aios/aios",
"base_oid":"8"*40,
"expected_branch_oid":"8"*40,
"target_branch":"agent/r42-owner-auth-projection-test-20260819",
"generation":1,
"risk":"engineering",
"description":"project existing governed task",
"idempotency_key":"binding-owner-auth-projection-v1",
"branch_mode":"CREATE_NEW",
"priority":100,
"max_attempts":3,
"require_online_task_projection":True,
"online_task":{
"goal":"Analyze source read-only.",
"instructions":"Use source capture first.",
"success_criteria":[
{"id":"source","description":"Return source snapshot."}
],
"constraints":{"repository_mutation":"forbidden"},
"required_capabilities":["git.read","git.test"],
"priority":100,
"max_attempts":3,
},
"execution":{
"action":"source_capture",
"adapter":"source_capture",
"capability_scope":["git.read","git.test"],
"source_signals":["durable authorization projection"],
},
}
value.update(overrides)
return value
def release_request(**overrides):
value={
"candidate_id":"r42-pf1-release-candidate-20260819",
"source_commit":"8"*40,
"version":"V2R2C2",
}
value.update(overrides)
return value
class FakeUpstream:
def __init__(self):
self.project_calls=[]
def project_governed_task(self,body):
self.project_calls.append(dict(body))
return 200,{
"task_id":body["task_id"],
"job_id":"job-"+"a"*32,
"state":"BOUND",
}
def broker(tmp_path,monkeypatch):
policy,path,revocations=load_policy(tmp_path,monkeypatch)
store=auth.ReceiptStore(tmp_path/"receipts.sqlite3")
upstream=FakeUpstream()
instance=auth.Broker(
policy_path=path,
revocation_path=revocations,
store=store,
upstream=upstream,
expected_account=ACCOUNT,
)
monkeypatch.setattr(
auth.DurablePolicy,
"load",
classmethod(lambda cls,*args,**kwargs:policy),
)
return instance,upstream,store,policy
def test_allowed_persistent_capabilities_are_only_preparation_scopes():
assert auth.ALLOWED_PERSISTENT_CAPABILITIES=={
"governed-task:project",
"release:candidate",
}
assert auth.ALLOWED_PERSISTENT_CAPABILITIES.isdisjoint(
auth.FORBIDDEN_PERSISTENT_CAPABILITIES
)
@pytest.mark.parametrize(
"capability",
sorted(auth.FORBIDDEN_PERSISTENT_CAPABILITIES),
)
def test_policy_rejects_high_risk_persistent_capability(
tmp_path,monkeypatch,capability
):
bad=policy_value(
capabilities=["governed-task:project",capability]
)
path=write_root_file(tmp_path/"policy.json",bad)
revocations=write_root_file(
tmp_path/"revocations.json",
{"revoked_grant_ids":[]},
)
monkeypatch.setattr(auth,"validate_root_file",lambda path,**kwargs:path)
with pytest.raises(
auth.AuthorizationError,
match="POLICY_CAPABILITY_NOT_PERSISTABLE",
):
auth.DurablePolicy.load(path,revocations,now=NOW)
def test_policy_is_persistent_until_revoked(tmp_path,monkeypatch):
policy,_,_=load_policy(tmp_path,monkeypatch)
assert policy.persistent_until_revoked is True
assert policy.account==ACCOUNT
assert policy.capabilities=={
"governed-task:project",
"release:candidate",
}
def test_revocation_fails_closed(tmp_path,monkeypatch):
path=write_root_file(tmp_path/"policy.json",policy_value())
revocations=write_root_file(
tmp_path/"revocations.json",
{"revoked_grant_ids":["owner-permanent-preparation-20260819"]},
)
monkeypatch.setattr(auth,"validate_root_file",lambda path,**kwargs:path)
with pytest.raises(auth.AuthorizationError,match="OWNER_GRANT_REVOKED"):
auth.DurablePolicy.load(path,revocations,now=NOW)
def test_future_policy_is_not_active(tmp_path,monkeypatch):
with pytest.raises(auth.AuthorizationError,match="POLICY_NOT_ACTIVE"):
load_policy(
tmp_path,
monkeypatch,
issued_at="2026-08-20T05:00:00Z",
not_before="2026-08-20T05:00:00Z",
)
def test_governed_projection_request_is_tightly_constrained(
tmp_path,monkeypatch
):
policy,_,_=load_policy(tmp_path,monkeypatch)
decision=policy.authorize(
"governed-task:project",
governed_request(),
)
assert decision["decision"]=="ALLOW"
assert decision["persistent_until_revoked"] is True
bad_caps=governed_request()
bad_caps["online_task"]={
**bad_caps["online_task"],
"required_capabilities":["release:candidate"],
}
bad_action=governed_request()
bad_action["execution"]={
**bad_action["execution"],
"action":"deterministic",
"adapter":"deterministic",
}
bad_scope=governed_request()
bad_scope["execution"]={
**bad_scope["execution"],
"capability_scope":["git.read"],
}
for bad in (
governed_request(repository="other/repo"),
governed_request(target_branch="main"),
governed_request(generation=2),
governed_request(risk="high"),
governed_request(expected_branch_oid="9"*40),
governed_request(branch_mode="CONTINUE_EXISTING"),
governed_request(priority=101),
governed_request(max_attempts=4),
governed_request(require_online_task_projection=False),
bad_caps,
bad_action,
bad_scope,
):
with pytest.raises(auth.AuthorizationError):
policy.authorize("governed-task:project",bad)
def test_write_capable_projection_is_allowed_only_as_source_capture_placeholder(
tmp_path,monkeypatch
):
policy,_,_=load_policy(tmp_path,monkeypatch)
request=governed_request()
request["online_task"]={
**request["online_task"],
"required_capabilities":[
"git.read",
"git.test",
"governed.write.branch",
"governed.write.file",
],
}
request["execution"]={
**request["execution"],
"capability_scope":[
"git.read",
"git.test",
"governed.write.branch",
"governed.write.file",
],
}
decision=policy.authorize("governed-task:project",request)
assert decision["decision"]=="ALLOW"
assert request["execution"]["action"]=="source_capture"
def test_release_candidate_request_is_tightly_constrained(
tmp_path,monkeypatch
):
policy,_,_=load_policy(tmp_path,monkeypatch)
decision=policy.authorize("release:candidate",release_request())
assert decision["decision"]=="ALLOW"
with pytest.raises(
auth.AuthorizationError,
match="RELEASE_VERSION_NOT_AUTHORIZED",
):
policy.authorize(
"release:candidate",
release_request(version="V3R1C1"),
)
with pytest.raises(
auth.AuthorizationError,
match="RELEASE_CANDIDATE_FIELDS_INVALID",
):
policy.authorize(
"release:candidate",
{**release_request(),"execute":True},
)
def test_governed_task_projection_executes_canonical_upstream(
tmp_path,monkeypatch
):
instance,upstream,store,_=broker(tmp_path,monkeypatch)
request=governed_request()
response=instance.execute(
"governed-task:project",
request,
idempotency_key="owner-auth-projection-operation-0001",
)
assert len(upstream.project_calls)==1
assert upstream.project_calls[0]==request
assert response["operation_state"]=="EXECUTED"
assert response["external_capability_projection_required"] is False
assert response["separate_owner_release_approval_still_required"] is True
def test_release_candidate_authorization_does_not_invent_builder_upstream(
tmp_path,monkeypatch
):
instance,upstream,store,_=broker(tmp_path,monkeypatch)
response=instance.execute(
"release:candidate",
release_request(),
idempotency_key="owner-auth-release-candidate-operation-0001",
)
assert upstream.project_calls==[]
assert response["operation_state"]==(
"AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION"
)
assert response["upstream_status"] is None
assert response["upstream_response"] is None
assert response["external_capability_projection_required"] is True
assert response["projection_id"].startswith("projection-")
assert response["projection_state"]=="PENDING_EXTERNAL_CONNECTOR"
assert response["separate_owner_release_approval_still_required"] is True
def test_release_authorization_creates_durable_projection_outbox(
tmp_path,monkeypatch
):
instance,_,store,policy=broker(tmp_path,monkeypatch)
request=release_request()
response=instance.execute(
"release:candidate",
request,
idempotency_key="owner-auth-release-outbox-0001",
)
projection=store.get_projection(response["projection_id"])
assert projection is not None
assert projection["schema"]==auth.PROJECTION_SCHEMA
assert projection["state"]=="PENDING_EXTERNAL_CONNECTOR"
assert projection["grant_id"]==policy.grant_id
assert projection["account"]==ACCOUNT
assert projection["capability"]=="release:candidate"
assert projection["request"]==request
assert projection["separate_owner_release_approval_still_required"] is True
listed=instance.projections()
assert listed["schema"]==auth.PROJECTION_LIST_SCHEMA
assert listed["count"]==1
assert listed["items"][0]["projection_id"]==response["projection_id"]
assert listed["external_connector_ack_supported"] is False
def test_existing_pre_outbox_release_receipt_is_backfilled_on_exact_retry(
tmp_path,monkeypatch
):
instance,_,store,policy=broker(tmp_path,monkeypatch)
request=release_request()
decision=policy.authorize("release:candidate",request)
key="owner-auth-release-pre-outbox-0001"
legacy_response={
"schema":auth.RECEIPT_SCHEMA,
"grant_id":policy.grant_id,
"owner_identity":policy.owner_identity,
"account":policy.account,
"capability":"release:candidate",
"policy_sha256":policy.policy_sha256,
"request_sha256":decision["request_sha256"],
"idempotency_key":key,
"operation_state":"AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION",
"upstream_status":None,
"upstream_response":None,
"external_capability_projection_required":True,
"separate_owner_release_approval_still_required":True,
}
store.put(
key,
"release:candidate",
decision["request_sha256"],
legacy_response,
)
response=instance.execute(
"release:candidate",
request,
idempotency_key=key,
)
assert response["projection_id"].startswith("projection-")
assert response["projection_state"]=="PENDING_EXTERNAL_CONNECTOR"
assert store.get_projection(response["projection_id"]) is not None
persisted=store.get(key)["response"]
assert persisted["projection_id"]==response["projection_id"]
def test_projection_id_is_deterministic_and_exact_retry_does_not_duplicate(
tmp_path,monkeypatch
):
instance,_,store,_=broker(tmp_path,monkeypatch)
key="owner-auth-release-outbox-0002"
first=instance.execute("release:candidate",release_request(),idempotency_key=key)
second=instance.execute("release:candidate",release_request(),idempotency_key=key)
assert second==first
con=sqlite3.connect(store.path)
rows=con.execute("select projection_id from projection_outbox").fetchall()
assert rows==[(first["projection_id"],)]
def test_projection_enqueue_recovers_crash_between_outbox_and_receipt(
tmp_path,monkeypatch
):
instance,_,store,policy=broker(tmp_path,monkeypatch)
request=release_request()
decision=policy.authorize("release:candidate",request)
key="owner-auth-release-outbox-crash-0001"
first=store.enqueue_projection(
idempotency_key=key,
grant_id=policy.grant_id,
account=policy.account,
capability="release:candidate",
policy_sha256=policy.policy_sha256,
request_sha256=decision["request_sha256"],
request=request,
)
# Simulate a later retry after the process crashed before receipt creation.
second=store.enqueue_projection(
idempotency_key=key,
grant_id=policy.grant_id,
account=policy.account,
capability="release:candidate",
policy_sha256=policy.policy_sha256,
request_sha256=decision["request_sha256"],
request=request,
)
assert second==first
def test_projection_idempotency_conflict_is_fail_closed(
tmp_path,monkeypatch
):
instance,_,store,policy=broker(tmp_path,monkeypatch)
first=release_request()
first_decision=policy.authorize("release:candidate",first)
key="owner-auth-release-outbox-conflict-0001"
store.enqueue_projection(
idempotency_key=key,
grant_id=policy.grant_id,
account=policy.account,
capability="release:candidate",
policy_sha256=policy.policy_sha256,
request_sha256=first_decision["request_sha256"],
request=first,
)
second=release_request(version="V2R2C3")
second_decision=policy.authorize("release:candidate",second)
with pytest.raises(auth.AuthorizationError,match="PROJECTION_IDEMPOTENCY_CONFLICT"):
store.enqueue_projection(
idempotency_key=key,
grant_id=policy.grant_id,
account=policy.account,
capability="release:candidate",
policy_sha256=policy.policy_sha256,
request_sha256=second_decision["request_sha256"],
request=second,
)
def test_revocation_blocks_projection_enumeration(tmp_path,monkeypatch):
instance,_,store,policy=broker(tmp_path,monkeypatch)
response=instance.execute(
"release:candidate",
release_request(),
idempotency_key="owner-auth-release-outbox-revoke-0001",
)
# Restore real policy loading and write a revocation for this grant.
monkeypatch.undo()
revocations=instance.revocation_path
revocations.write_text(
json.dumps({"revoked_grant_ids":[policy.grant_id]})+"\n",
encoding="utf-8",
)
revocations.chmod(0o600)
monkeypatch.setattr(auth,"validate_root_file",lambda path,**kwargs:path)
with pytest.raises(auth.AuthorizationError,match="OWNER_GRANT_REVOKED"):
instance.projections()
# Evidence is retained; revocation does not erase history.
assert store.get_projection(response["projection_id"]) is not None
def test_projection_outbox_contains_no_credentials(tmp_path,monkeypatch):
instance,_,store,_=broker(tmp_path,monkeypatch)
instance.execute(
"release:candidate",
release_request(),
idempotency_key="owner-auth-release-outbox-schema-0001",
)
con=sqlite3.connect(store.path)
columns=[row[1] for row in con.execute("pragma table_info(projection_outbox)")]
joined=" ".join(columns).lower()
for forbidden in ("token","credential","private_key","secret"):
assert forbidden not in joined
payload=con.execute("select payload_json from projection_outbox").fetchone()[0]
assert "Bearer " not in payload
assert "submitOwnerApproval" not in payload
assert "startDeployment" not in payload
def test_exact_retry_returns_same_receipt_without_second_projection(
tmp_path,monkeypatch
):
instance,upstream,_,_=broker(tmp_path,monkeypatch)
request=governed_request()
first=instance.execute(
"governed-task:project",
request,
idempotency_key="owner-auth-projection-operation-0002",
)
second=instance.execute(
"governed-task:project",
request,
idempotency_key="owner-auth-projection-operation-0002",
)
assert second==first
assert len(upstream.project_calls)==1
def test_idempotency_conflict_fails_closed(tmp_path,monkeypatch):
instance,_,_,_=broker(tmp_path,monkeypatch)
instance.execute(
"release:candidate",
release_request(),
idempotency_key="owner-auth-release-operation-0002",
)
with pytest.raises(auth.AuthorizationError,match="IDEMPOTENCY_CONFLICT"):
instance.execute(
"release:candidate",
release_request(version="V2R2C3"),
idempotency_key="owner-auth-release-operation-0002",
)
def test_receipt_store_contains_no_owner_policy_or_credentials(
tmp_path,monkeypatch
):
instance,_,store,_=broker(tmp_path,monkeypatch)
instance.execute(
"release:candidate",
release_request(),
idempotency_key="owner-auth-release-operation-0003",
)
con=sqlite3.connect(store.path)
columns=[row[1] for row in con.execute("pragma table_info(receipts)")]
joined=" ".join(columns).lower()
assert "token" not in joined
assert "credential" not in joined
assert "policy_json" not in columns
def test_systemd_broker_uses_unix_socket_and_only_host_control_credential():
unit=(
ROOT/"deploy/systemd/aios-owner-durable-authorization-broker.service"
).read_text()
assert "LoadCredential=agent_task_control_token:" in unit
assert "release_candidate_token" not in unit
assert "18947" not in unit
assert "AIOS_RELEASE_CANDIDATE_API_URL" not in unit
assert "AIOS_OWNER_AUTH_BROKER_SOCKET=%t/aios-owner-authorization-broker/broker.sock" in unit
assert "Group=aios-executor-exchange" in unit
assert "RestrictAddressFamilies=AF_UNIX AF_INET" in unit
assert "IPAddressDeny=any" in unit
assert "IPAddressAllow=127.0.0.0/8" in unit
assert "InaccessiblePaths=/data/aios-gitops /home" in unit
def test_source_contains_no_release_builder_endpoint_or_high_risk_execution():
source=(
ROOT/"active-runtime/app/aios_durable_owner_authorization.py"
).read_text()
assert "/createReleaseCandidate" not in source
assert "18947" not in source
assert "ThreadingHTTPServer" not in source
assert "ThreadingUnixStreamServer" in source
assert "startDeployment" in source
assert "tag_publish" in source
assert "baseline_registry_finalize" in source
assert "runtime_switch" in source
assert "AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION" in source
assert "PENDING_EXTERNAL_CONNECTOR" in source
assert "projection_outbox" in source
assert "external_connector_ack_supported" in source
assert "SAFE_PROJECTED_TASK_CAPABILITIES" in source
assert "INITIAL_EXECUTION_ACTION_NOT_AUTHORIZED" in source
def test_docs_avoid_new_broker_bearer_secret():
doc=(
ROOT/"docs/superpowers/specs/2026-08-19-durable-owner-authorization.md"
).read_text()
assert "AF_UNIX" in doc
assert "avoids introducing another long-lived bearer secret" in doc
def test_docs_preserve_separate_owner_release_approval():
doc=(
ROOT/"docs/superpowers/specs/2026-08-19-durable-owner-authorization.md"
).read_text()
assert "persistent until explicitly revoked" in doc.lower()
assert "Owner signature" in doc
assert "AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION" in doc
assert "PENDING_EXTERNAL_CONNECTOR" in doc
+362
View File
@@ -0,0 +1,362 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
import hashlib
import json
from pathlib import Path
import sys
import subprocess
import pytest
ROOT=Path(__file__).resolve().parents[1]
APP=ROOT/"active-runtime/app"
sys.path.insert(0,str(APP))
import aios_gpt_https_edge_activation as activation
CANDIDATE="69a51d50c7f6cd9045346d41e338acebe4b6f1e8"
NOW=datetime(2026,8,19,2,0,0,tzinfo=timezone.utc)
def approval_value(cert_sha: str, **overrides):
value={
"schema":"aios.gpt-https-edge.public-approval.v1",
"approval_id":"owner-edge-approval-0001",
"candidate_oid":CANDIDATE,
"domain":"tasks.example.com",
"public_bind":True,
"bind_host":"0.0.0.0",
"port":18443,
"tls_cert_sha256":cert_sha,
"issued_at":"2026-08-19T01:00:00Z",
"not_before":"2026-08-19T01:00:00Z",
"expires_at":"2026-08-20T01:00:00Z",
"owner":"owner@example",
}
value.update(overrides)
return value
def write_owner_file(path: Path, data: bytes, mode=0o600):
path.write_bytes(data)
path.chmod(mode)
return path
def load_approval(tmp_path, monkeypatch, **overrides):
tmp_path.mkdir(parents=True,exist_ok=True)
cert=write_owner_file(tmp_path/"cert.pem",b"certificate-material-"*4,0o600)
value=approval_value(hashlib.sha256(cert.read_bytes()).hexdigest(),**overrides)
approval=write_owner_file(
tmp_path/"approval.json",
(json.dumps(value,sort_keys=True)+"\n").encode(),
0o600,
)
monkeypatch.setattr(
activation,
"validate_root_file",
lambda path, **kwargs: path,
)
return activation.Approval.load(
approval,
candidate_oid=CANDIDATE,
cert_path=cert,
now=NOW,
),approval,cert
class FakeSystemd:
def __init__(self):
self.active_units={activation.INGRESS_UNIT}
self.enabled_units=set()
self.calls=[]
self.exec_start=""
self.environment=""
self.fail_restart=False
def run(self,*args,check=True):
self.calls.append(args)
class Result:
returncode=0
stdout=""
if args[:2]==("restart",activation.EDGE_UNIT):
if self.fail_restart:
raise subprocess.CalledProcessError(1,args)
self.active_units.add(activation.EDGE_UNIT)
elif args[:2]==("stop",activation.EDGE_UNIT):
self.active_units.discard(activation.EDGE_UNIT)
elif args[:2]==("enable",activation.EDGE_UNIT):
self.enabled_units.add(activation.EDGE_UNIT)
elif args[:2]==("disable",activation.EDGE_UNIT):
self.enabled_units.discard(activation.EDGE_UNIT)
return Result()
def active(self,unit):
return unit in self.active_units
def enabled(self,unit):
return unit in self.enabled_units
def show(self,unit,prop):
if prop=="ExecStart":
return self.exec_start
if prop=="Environment":
return self.environment
return ""
def test_approval_binds_candidate_domain_bind_port_and_cert(tmp_path,monkeypatch):
approval,_,cert=load_approval(tmp_path,monkeypatch)
assert approval.candidate_oid==CANDIDATE
assert approval.domain=="tasks.example.com"
assert approval.bind_host=="0.0.0.0"
assert approval.port==18443
assert approval.tls_cert_sha256==hashlib.sha256(cert.read_bytes()).hexdigest()
assert approval.expires_at>approval.not_before
def test_approval_rejects_candidate_mismatch(tmp_path,monkeypatch):
cert=write_owner_file(tmp_path/"cert.pem",b"certificate-material-"*4)
value=approval_value(hashlib.sha256(cert.read_bytes()).hexdigest())
approval=write_owner_file(tmp_path/"approval.json",json.dumps(value).encode())
monkeypatch.setattr(activation,"validate_root_file",lambda path,**kwargs:path)
with pytest.raises(activation.ActivationError,match="CANDIDATE_MISMATCH"):
activation.Approval.load(
approval,
candidate_oid="0"*40,
cert_path=cert,
now=NOW,
)
def test_approval_rejects_cert_hash_mismatch(tmp_path,monkeypatch):
cert=write_owner_file(tmp_path/"cert.pem",b"certificate-material-"*4)
value=approval_value("a"*64)
approval=write_owner_file(tmp_path/"approval.json",json.dumps(value).encode())
monkeypatch.setattr(activation,"validate_root_file",lambda path,**kwargs:path)
with pytest.raises(activation.ActivationError,match="CERT_SHA_MISMATCH"):
activation.Approval.load(
approval,
candidate_oid=CANDIDATE,
cert_path=cert,
now=NOW,
)
def test_approval_rejects_expired_not_yet_valid_and_long_lived(tmp_path,monkeypatch):
with pytest.raises(activation.ActivationError,match="APPROVAL_EXPIRED"):
load_approval(
tmp_path/"expired",
monkeypatch,
issued_at="2026-08-17T01:00:00Z",
not_before="2026-08-17T01:00:00Z",
expires_at="2026-08-18T01:00:00Z",
)
# Use distinct temp roots because helper writes fixed names.
with pytest.raises(activation.ActivationError,match="NOT_YET_VALID"):
load_approval(
tmp_path/"future",
monkeypatch,
issued_at="2026-08-19T03:00:00Z",
not_before="2026-08-19T03:00:00Z",
expires_at="2026-08-20T03:00:00Z",
)
with pytest.raises(activation.ActivationError,match="VALIDITY_TOO_LONG"):
load_approval(
tmp_path/"long",
monkeypatch,
issued_at="2026-08-18T01:00:00Z",
not_before="2026-08-18T01:00:00Z",
expires_at="2026-08-26T01:00:00Z",
)
def test_validate_release_requires_edge_and_controller_and_denies_symlink(tmp_path):
release=tmp_path/"release"
app=release/"active-runtime/app"
app.mkdir(parents=True)
edge_file=app/"aios_gpt_https_edge.py"
controller=app/"aios_gpt_https_edge_activation.py"
edge_file.write_text("edge\n")
controller.write_text("controller\n")
value=activation.validate_release(release)
assert value["edge"]==edge_file
assert value["controller"]==controller
controller.unlink()
with pytest.raises(activation.ActivationError,match="IMMUTABLE_RELEASE_INVALID"):
activation.validate_release(release)
def test_render_dropin_is_exact_and_contains_no_secret(tmp_path,monkeypatch):
approval,_,_=load_approval(tmp_path,monkeypatch)
release=Path("/opt/aios/releases")/(CANDIDATE+"-gpt-https-edge-public")
text=activation.render_dropin(approval,release)
assert "AIOS_GPT_EDGE_HOST=0.0.0.0" in text
assert "AIOS_GPT_EDGE_PORT=18443" in text
assert "AIOS_GPT_EDGE_PUBLIC_BIND_APPROVED=1" in text
assert "AIOS_GPT_EDGE_PUBLIC_DOMAIN=tasks.example.com" in text
assert CANDIDATE in text
for forbidden in ("Bearer ","agent_task_control","tls_key","gpt_ingress_token"):
assert forbidden not in text
def test_receipt_contains_binding_but_no_owner_secret_material(tmp_path,monkeypatch):
approval,_,_=load_approval(tmp_path,monkeypatch)
receipt=activation.receipt_for(
approval,
Path("/opt/aios/releases/test"),
state="ACTIVE",
)
assert receipt["approval_id"]==approval.approval_id
assert receipt["candidate_oid"]==CANDIDATE
assert receipt["domain"]=="tasks.example.com"
serialized=json.dumps(receipt)
assert "Bearer " not in serialized
assert "private" not in serialized.lower()
def test_activate_is_idempotent_for_same_active_approval(tmp_path,monkeypatch):
approval,_,cert=load_approval(tmp_path,monkeypatch)
release=tmp_path/"release"
app=release/"active-runtime/app/aios_gpt_https_edge.py"
controller=release/"active-runtime/app/aios_gpt_https_edge_activation.py"
app.parent.mkdir(parents=True)
app.write_text("print('x')\n")
controller.write_text("print('controller')\n")
key=write_owner_file(tmp_path/"key.pem",b"private-key-material-"*4,0o600)
dropin=tmp_path/"edge.d"/activation.DROPIN_NAME
state=tmp_path/"state.json"
systemd=FakeSystemd()
monkeypatch.setattr(activation.os,"geteuid",lambda:0)
monkeypatch.setattr(activation,"require_ingress_ready",lambda _:None)
monkeypatch.setattr(activation,"validate_tls_pair",lambda *a,**k:{"cert_sha256":approval.tls_cert_sha256})
monkeypatch.setattr(activation,"tls_probe",lambda *a,**k:None)
systemd.exec_start=str(app)
systemd.environment=(
"AIOS_GPT_EDGE_HOST=0.0.0.0 "
"AIOS_GPT_EDGE_PORT=18443 "
"AIOS_GPT_EDGE_PUBLIC_BIND_APPROVED=1 "
"AIOS_GPT_EDGE_PUBLIC_DOMAIN=tasks.example.com"
)
first=activation.activate(
approval=approval,
release_root=release,
cert_path=cert,
key_path=key,
dropin_path=dropin,
state_path=state,
systemd=systemd,
)
calls_after_first=len(systemd.calls)
second=activation.activate(
approval=approval,
release_root=release,
cert_path=cert,
key_path=key,
dropin_path=dropin,
state_path=state,
systemd=systemd,
)
assert first["state"]=="ACTIVE"
assert second==json.loads(state.read_text())
assert len(systemd.calls)==calls_after_first
def test_activate_failure_after_mutation_rolls_back(tmp_path,monkeypatch):
approval,_,cert=load_approval(tmp_path,monkeypatch)
release=tmp_path/"release"
app=release/"active-runtime/app/aios_gpt_https_edge.py"
controller=release/"active-runtime/app/aios_gpt_https_edge_activation.py"
app.parent.mkdir(parents=True)
app.write_text("print('x')\n")
controller.write_text("print('controller')\n")
key=write_owner_file(tmp_path/"key.pem",b"private-key-material-"*4,0o600)
dropin=tmp_path/"edge.d"/activation.DROPIN_NAME
state=tmp_path/"state.json"
systemd=FakeSystemd()
systemd.fail_restart=True
monkeypatch.setattr(activation.os,"geteuid",lambda:0)
monkeypatch.setattr(activation,"require_ingress_ready",lambda _:None)
monkeypatch.setattr(activation,"validate_tls_pair",lambda *a,**k:{"cert_sha256":approval.tls_cert_sha256})
monkeypatch.setattr(activation,"tls_probe",lambda *a,**k:None)
with pytest.raises(subprocess.CalledProcessError):
activation.activate(
approval=approval,
release_root=release,
cert_path=cert,
key_path=key,
dropin_path=dropin,
state_path=state,
systemd=systemd,
)
assert not dropin.exists()
assert not systemd.active(activation.EDGE_UNIT)
assert json.loads(state.read_text())["state"]=="ROLLED_BACK"
def test_rollback_is_replay_safe_and_leaves_owner_assets_alone(tmp_path,monkeypatch):
approval_file=tmp_path/"approval.json"
cert=tmp_path/"cert.pem"
key=tmp_path/"key.pem"
for path in (approval_file,cert,key):
path.write_text("owner-asset"*4)
dropin=tmp_path/"edge.d"/activation.DROPIN_NAME
dropin.parent.mkdir(parents=True)
dropin.write_text("[Service]\n")
state=tmp_path/"state.json"
state.write_text(json.dumps({
"schema":activation.STATE_SCHEMA,
"state":"ACTIVE",
"candidate_oid":CANDIDATE,
"approval_id":"owner-edge-approval-0001",
"domain":"tasks.example.com",
}))
systemd=FakeSystemd()
systemd.active_units.add(activation.EDGE_UNIT)
systemd.enabled_units.add(activation.EDGE_UNIT)
monkeypatch.setattr(activation.os,"geteuid",lambda:0)
first=activation.rollback(
dropin_path=dropin,
state_path=state,
systemd=systemd,
reason="owner_requested",
)
second=activation.rollback(
dropin_path=dropin,
state_path=state,
systemd=systemd,
reason="owner_requested",
)
assert first["state"]=="ROLLED_BACK"
assert second["state"]=="ROLLED_BACK"
assert not dropin.exists()
assert not systemd.active(activation.EDGE_UNIT)
assert not systemd.enabled(activation.EDGE_UNIT)
for path in (approval_file,cert,key):
assert path.exists()
def test_activation_source_never_creates_owner_approval_or_tls_secret():
source=(ROOT/"active-runtime/app/aios_gpt_https_edge_activation.py").read_text()
assert "secrets.token" not in source
assert "openssl" not in source.lower()
assert "BEGIN PRIVATE KEY" not in source
assert "gpt-https-edge.approved" in source
assert "gpt-https-edge-tls.key" in source
def test_docs_separate_implementation_activation_and_runtime_verification():
doc=(ROOT/"docs/superpowers/specs/2026-08-19-gpt-https-edge-activation.md").read_text()
assert "IMPLEMENTED" in doc
assert "OWNER_APPROVED" in doc
assert "ACTIVATED" in doc
assert "RUNTIME_VERIFIED" in doc
assert "does not own" in doc.lower()
+117
View File
@@ -1,4 +1,13 @@
import hashlib
import importlib.util
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tarfile
import pytest
ROOT = Path(__file__).resolve().parents[1]
@@ -33,3 +42,111 @@ def test_model_api_builder_manifest_contract():
assert "'deployment_state': 'STAGING'" in builder
assert "'legacy_global_writes_enabled': False" in builder
assert 'private marker rejected' in builder
def _load_builder_module():
path = ROOT / 'scripts' / 'build_v2r2c1_model_api_staging.py'
spec = importlib.util.spec_from_file_location('model_api_packager', path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _run_builder(source: Path, output: Path, oid: str = 'a' * 40):
return subprocess.run(
[
sys.executable,
str(ROOT / 'scripts' / 'build_v2r2c1_model_api_staging.py'),
'--source-root',
str(source),
'--output-root',
str(output),
'--source-oid',
oid,
],
capture_output=True,
text=True,
)
def _minimal_source(tmp_path: Path) -> Path:
module = _load_builder_module()
source = tmp_path / 'source'
for name in module.REQUIRED_APP:
target = source / 'active-runtime' / 'app' / name
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / 'active-runtime' / 'app' / name, target)
for rel in module.REQUIRED_RUNTIME:
target = source / 'runtime' / 'agent-os' / rel
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / 'runtime' / 'agent-os' / rel, target)
for rel in module.REQUIRED_BUILDER_ASSETS:
target = source / rel
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / rel, target)
return source
def test_model_api_package_contains_builder_runtime_and_is_deterministic(tmp_path):
first = tmp_path / 'first'
second = tmp_path / 'second'
one = _run_builder(ROOT, first)
two = _run_builder(ROOT, second)
assert one.returncode == 0, one.stderr
assert two.returncode == 0, two.stderr
receipt = json.loads(one.stdout)
peer_receipt = json.loads(two.stdout)
assert one.stdout == two.stdout
archive = first / receipt['artifact_path']
peer_archive = second / peer_receipt['artifact_path']
assert hashlib.sha256(archive.read_bytes()).hexdigest() == hashlib.sha256(
peer_archive.read_bytes()
).hexdigest()
with tarfile.open(archive, 'r:gz') as bundle:
members = bundle.getmembers()
names = [member.name for member in members]
assert len(names) == len(set(names))
assert 'runtime/app/aios_trusted_candidate_builder.py' in names
assert 'runtime/config/gitops/trusted-candidate-builder-policy.json' in names
assert 'runtime/deploy/systemd/aios-trusted-candidate-builder.socket' in names
assert 'runtime/deploy/systemd/aios-trusted-candidate-builder.service' in names
assert all(member.isfile() or member.isdir() for member in members)
assert all('.git' not in Path(name).parts for name in names)
assert all(
member.mode == (0o755 if member.isdir() else 0o644)
for member in members
)
def test_model_api_packager_rejects_symlinked_required_asset(tmp_path):
source = _minimal_source(tmp_path)
policy = source / 'config' / 'gitops' / 'trusted-candidate-builder-policy.json'
policy.unlink()
try:
policy.symlink_to(
source / 'active-runtime' / 'app' / 'silent_release_hardening.py'
)
except OSError:
pytest.skip('creating symlinks is not permitted by this host')
result = _run_builder(source, tmp_path / 'output')
assert result.returncode != 0
assert 'symlink' in (result.stderr + result.stdout).lower()
def test_private_marker_rejects_real_pem_but_allows_prose(tmp_path):
module = _load_builder_module()
prose = tmp_path / 'prose.txt'
prose.write_text('Documentation says BEGIN PRIVATE KEY without a PEM fence.\n')
assert module.private_marker(prose) is False
private = tmp_path / 'private.pem'
pem_begin = '-----' + 'BEGIN PRIVATE KEY' + '-----\n'
pem_end = '-----' + 'END PRIVATE KEY' + '-----\n'
private.write_text(
pem_begin + 'c2Vuc2l0aXZlLWJ1aWxkZXIta2V5LW1hdGVyaWFs\n'
+ pem_end
)
assert module.private_marker(private) is True
@@ -0,0 +1,484 @@
from __future__ import annotations
import base64
import dataclasses
import hashlib
import importlib
import importlib.util
import json
import socket
import sys
import threading
import urllib.error
import urllib.request
from datetime import datetime, timedelta, timezone
from pathlib import Path
from types import SimpleNamespace
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
APP = Path(__file__).resolve().parents[1] / "active-runtime" / "app"
AGENT_OS = Path(__file__).resolve().parents[1] / "runtime" / "agent-os"
for import_root in (APP, AGENT_OS):
if str(import_root) not in sys.path:
sys.path.insert(0, str(import_root))
from silent_release_hardening import ( # noqa: E402
BuilderAttestation,
BuilderAttestationKeyRing,
BuilderAttestationVerifier,
CapabilityAuthorizationError,
SilentReleaseActionService,
TrustedCandidateBuilderUnavailable,
)
import silent_release_hardening as hardening # noqa: E402
COMMIT = "a" * 40
TREE = "b" * 40
SHA = "c" * 64
GITEA = {"consistent": True}
def _af_inet_available() -> bool:
try:
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except OSError:
return False
probe.close()
return True
def _canonical_digest(value: dict) -> str:
return hashlib.sha256(
json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
def _instant(delta: timedelta = timedelta()) -> str:
return (datetime.now(timezone.utc) + delta).isoformat().replace(
"+00:00", "Z"
)
def _keyring_payload(private: Ed25519PrivateKey, *, purpose: str) -> dict:
public = private.public_key().public_bytes(
serialization.Encoding.Raw, serialization.PublicFormat.Raw
)
record = {
"algorithm": "Ed25519",
"purpose": purpose,
"key_id": "builder-key-001",
"generation": 1,
"not_before": _instant(timedelta(minutes=-1)),
"expires_at": _instant(timedelta(days=1)),
"status": "ACTIVE",
"public_key": base64.b64encode(public).decode("ascii"),
"public_key_sha256": hashlib.sha256(public).hexdigest(),
}
payload = {
"algorithm": "Ed25519",
"purpose": "builder_attestation",
"keys": [record],
}
payload["keyring_sha256"] = hashlib.sha256(
json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
return payload
def _refresh_keyring_digest(payload: dict) -> dict:
canonical = {
"algorithm": payload["algorithm"],
"purpose": payload["purpose"],
"keys": payload["keys"],
}
payload["keyring_sha256"] = hashlib.sha256(
json.dumps(canonical, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
return payload
def _attestation(private: Ed25519PrivateKey) -> BuilderAttestation:
unsigned = BuilderAttestation(
builder_attestation_id="builder-attestation-001",
builder_key_id="builder-key-001",
source_commit=COMMIT,
tree_oid=TREE,
artifact_sha256=SHA,
manifest_sha256=SHA,
package_closure_sha256=SHA,
test_evidence_sha256=SHA,
canary_evidence_sha256=SHA,
issued_at=_instant(),
signature="",
builder_key_generation=1,
signature_algorithm="Ed25519",
signature_purpose="builder_attestation",
gitea_evidence_sha256=_canonical_digest(GITEA),
rollback_evidence_sha256=_canonical_digest({
"ready": True,
"rollback_sha256": SHA,
"source_commit": COMMIT,
}),
policy_evidence_sha256=SHA,
secret_scan_evidence_sha256=SHA,
)
return BuilderAttestation(
**{
**unsigned.canonical_payload,
"signature": base64.b64encode(
private.sign(unsigned.canonical_bytes())
).decode("ascii"),
}
)
def test_builder_verifier_rejects_owner_key_record_cross_purpose(tmp_path):
private = Ed25519PrivateKey.generate()
payload = _keyring_payload(private, purpose="owner_approval")
path = tmp_path / "builder-keyring.json"
path.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(ValueError, match="purpose"):
BuilderAttestationKeyRing.from_file(path)
def test_builder_verifier_checks_domain_key_window_digest_and_signature(tmp_path):
private = Ed25519PrivateKey.generate()
path = tmp_path / "builder-keyring.json"
path.write_text(
json.dumps(_keyring_payload(private, purpose="builder_attestation")),
encoding="utf-8",
)
verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing.from_file(path)
)
candidate = SimpleNamespace(
source_commit=COMMIT,
tree_oid=TREE,
artifact_sha256=SHA,
manifest_sha256=SHA,
gitea_consistency=GITEA,
rollback_ready=True,
)
plan = SimpleNamespace(
deployment=SimpleNamespace(
package_closure_sha256=SHA,
rollback_sha256=SHA,
),
policy_sha256=SHA,
)
assert verifier.verify(_attestation(private), candidate, plan).builder_key_id == "builder-key-001"
@pytest.mark.parametrize("state", ["REVOKED", "EXPIRED", "FUTURE"])
def test_builder_verifier_rejects_unavailable_key_lifecycle_states(tmp_path, state):
private = Ed25519PrivateKey.generate()
payload = _keyring_payload(private, purpose="builder_attestation")
record = payload["keys"][0]
if state == "REVOKED":
record["status"] = "REVOKED"
elif state == "EXPIRED":
record["not_before"] = _instant(timedelta(days=-2))
record["expires_at"] = _instant(timedelta(days=-1))
else:
record["not_before"] = _instant(timedelta(days=1))
record["expires_at"] = _instant(timedelta(days=2))
path = tmp_path / "builder-keyring.json"
path.write_text(json.dumps(_refresh_keyring_digest(payload)), encoding="utf-8")
verifier = BuilderAttestationVerifier(BuilderAttestationKeyRing.from_file(path))
candidate = SimpleNamespace(
source_commit=COMMIT, tree_oid=TREE,
artifact_sha256=SHA, manifest_sha256=SHA,
gitea_consistency=GITEA, rollback_ready=True,
)
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
with pytest.raises(CapabilityAuthorizationError, match="validity|revoked|unavailable"):
verifier.verify(_attestation(private), candidate, plan)
def test_builder_verifier_rejects_unknown_key_invalid_signature_and_keyring_digest(tmp_path):
private = Ed25519PrivateKey.generate()
payload = _keyring_payload(private, purpose="builder_attestation")
path = tmp_path / "builder-keyring.json"
path.write_text(json.dumps(payload), encoding="utf-8")
verifier = BuilderAttestationVerifier(BuilderAttestationKeyRing.from_file(path))
candidate = SimpleNamespace(
source_commit=COMMIT, tree_oid=TREE,
artifact_sha256=SHA, manifest_sha256=SHA,
gitea_consistency=GITEA, rollback_ready=True,
)
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
with pytest.raises(CapabilityAuthorizationError, match="not trusted"):
verifier.verify(
dataclasses.replace(_attestation(private), builder_key_id="unknown-builder-key"),
candidate,
plan,
)
with pytest.raises(CapabilityAuthorizationError, match="signature"):
verifier.verify(
dataclasses.replace(_attestation(private), signature=base64.b64encode(b"x" * 64).decode()),
candidate,
plan,
)
payload["keyring_sha256"] = "0" * 64
path.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(ValueError, match="keyring digest"):
BuilderAttestationKeyRing.from_file(path)
def test_builder_attestation_rejects_tampered_digest_and_signing_metadata():
private = Ed25519PrivateKey.generate()
raw = _attestation(private).to_dict()
raw["fingerprint"] = "0" * 64
with pytest.raises(CapabilityAuthorizationError, match="digest is invalid"):
BuilderAttestation.from_dict(raw, require_fingerprint=True)
@pytest.mark.parametrize(
"field,value",
[
("builder_key_generation", 2),
("signature_algorithm", "RSA"),
("signature_purpose", "owner_approval"),
],
)
def test_builder_keyring_rejects_attestation_metadata_mismatch(tmp_path, field, value):
private = Ed25519PrivateKey.generate()
path = tmp_path / "builder-keyring.json"
path.write_text(
json.dumps(_keyring_payload(private, purpose="builder_attestation")),
encoding="utf-8",
)
verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing.from_file(path)
)
candidate = SimpleNamespace(
source_commit=COMMIT, tree_oid=TREE,
artifact_sha256=SHA, manifest_sha256=SHA,
gitea_consistency=GITEA, rollback_ready=True,
)
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
with pytest.raises(CapabilityAuthorizationError, match="metadata"):
verifier.verify(
dataclasses.replace(_attestation(private), **{field: value}),
candidate,
plan,
)
raw = _attestation(private).to_dict()
raw["builder_key_generation"] = 2
with pytest.raises(CapabilityAuthorizationError, match="digest is invalid"):
BuilderAttestation.from_dict(raw, require_fingerprint=True)
def test_builder_unavailable_intent_is_bounded_retryable_and_recovers_without_secret(tmp_path, monkeypatch):
class UnavailableBuilder:
def build(self, intent):
raise TrustedCandidateBuilderUnavailable("socket token=should-not-leak")
monkeypatch.setattr(hardening, "_fsync_directory", lambda _path: None)
service = SilentReleaseActionService(
tmp_path,
candidate_builder=UnavailableBuilder(),
max_builder_attempts=2,
)
payload = {"event_id": "event-builder-001", "source_commit": COMMIT}
first = service.create_release_intent(payload)
second = service.create_release_intent(payload)
assert first["status"] == "BUILDER_RETRYABLE_FAILURE"
assert first["retryable"] is True
assert first["attempt_count"] == 1
assert "should-not-leak" not in json.dumps(first)
assert second["status"] == "BUILDER_TERMINAL_FAILURE"
assert second["retryable"] is False
assert second["attempt_count"] == 2
def test_stale_building_intent_recovers_after_restart_without_double_running_active_attempt(tmp_path, monkeypatch):
class CountingUnavailableBuilder:
calls = 0
def build(self, intent):
self.calls += 1
raise TrustedCandidateBuilderUnavailable("nonsecret")
monkeypatch.setattr(hardening, "_fsync_directory", lambda _path: None)
builder = CountingUnavailableBuilder()
service = SilentReleaseActionService(
tmp_path,
candidate_builder=builder,
max_builder_attempts=3,
builder_attempt_timeout_seconds=1,
)
payload = {"event_id": "event-builder-crash-001", "source_commit": COMMIT}
intent_id = "intent-" + hashlib.sha256(
f"{payload['event_id']}\0{COMMIT}".encode()
).hexdigest()[:24]
intent_path = tmp_path / "intents" / f"{intent_id}.json"
intent_path.parent.mkdir(parents=True)
active_record = {
"attempt_count": 1,
"attempt_started_at": _instant(),
"created_at": _instant(timedelta(minutes=-1)),
"event_id": payload["event_id"],
"intent_id": intent_id,
"source_commit": COMMIT,
"status": "BUILDING",
}
intent_path.write_text(json.dumps(active_record), encoding="utf-8")
active = service.create_release_intent(payload)
assert active["status"] == "BUILDING"
assert active["idempotent"] is True
assert builder.calls == 0
active_record["attempt_started_at"] = _instant(timedelta(minutes=-5))
intent_path.write_text(json.dumps(active_record), encoding="utf-8")
recovered = service.create_release_intent(payload)
assert recovered["status"] == "BUILDER_RETRYABLE_FAILURE"
assert recovered["attempt_count"] == 2
assert recovered["failure_code"] == "TRUSTED_BUILDER_UNAVAILABLE"
assert recovered["last_recovery_code"] == "TRUSTED_BUILDER_CRASH_RECOVERED"
assert recovered["recovery_count"] == 1
assert builder.calls == 1
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
@pytest.mark.skipif(
not _af_inet_available(),
reason="isolated executor permits AF_UNIX only; live HTTP probe runs at activation",
)
def test_model_api_keeps_process_liveness_separate_from_builder_readiness(monkeypatch):
api = importlib.import_module("aios_model_api_staging")
class FakeService:
ready = False
def get_builder_readiness(self):
return {"status": "READY" if self.ready else "UNAVAILABLE"}
def create_release_candidate(self, payload):
if not payload:
raise ValueError("event_id is required")
return {"status": "READY_FOR_APPROVAL"}
service = FakeService()
monkeypatch.setattr(api, "silent_release_service", lambda: service)
monkeypatch.setattr(
api,
"authenticate",
lambda token: (
{"account": "test", "role": "owner", "scopes": ["release:candidate"]}
if token == "test-token"
else None
),
)
server = api.ThreadingHTTPServer(("127.0.0.1", 0), api.H)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
base = f"http://127.0.0.1:{server.server_port}"
def request(path, body=None):
data = None if body is None else json.dumps(body).encode("utf-8")
value = urllib.request.Request(
base + path,
data=data,
headers={"Authorization": "Bearer test-token"},
method="POST" if body is not None else "GET",
)
try:
with urllib.request.urlopen(value, timeout=5) as response:
return response.status, json.load(response)
except urllib.error.HTTPError as error:
return error.code, json.load(error)
try:
health_code, health = request("/health")
unavailable_code, unavailable = request("/createReleaseCandidate", {})
service.ready = True
invalid_code, invalid = request("/createReleaseCandidate", {})
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
assert health_code == 200
assert health["process_liveness"] == "PASS"
assert health["builder_readiness"]["status"] == "UNAVAILABLE"
assert unavailable_code == 503
assert unavailable["error"] == "TRUSTED_BUILDER_UNAVAILABLE"
assert invalid_code == 400
assert invalid["error"] == "event_id is required"
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
def test_host_adapter_accepts_a_real_builder_bundle_response():
spec = importlib.util.spec_from_file_location(
"trusted_builder_public_seam",
APP.parents[1] / "tests" / "test_trusted_candidate_builder.py",
)
assert spec and spec.loader
backend_seam = importlib.util.module_from_spec(spec)
spec.loader.exec_module(backend_seam)
server, client, _ = backend_seam.make_seam()
candidate_id = "r42-release-candidate-v2r2c2-20260819"
source_commit = "71e736b05fab13efc06af7a4fe2dc022c1cf9a85"
response = server.handle_request(
client.create_request(
"V2R2C2", candidate_id, source_commit, nonce="z" * 32
),
connection=object(),
)
class ResponseClient:
def build(self, version, supplied_candidate_id, supplied_source, *, nonce):
assert version == "V2R2C2"
assert supplied_candidate_id == candidate_id
assert supplied_source == source_commit
assert len(nonce) >= 32
return response
api = importlib.import_module("aios_model_api_staging")
bundle = api._HostTrustedCandidateBuilderAdapter(ResponseClient()).build(
{
"candidate_id": candidate_id,
"source_commit": source_commit,
"version": "V2R2C2",
}
)
assert bundle.validate().candidate.source_commit == response["base_oid"]
assert bundle.attestation.builder_key_id == response["attestation"]["builder_key_id"]
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
def test_host_adapter_forwards_v2r2c3_to_the_policy_and_fails_closed_when_unavailable():
from aios_trusted_candidate_builder import BuilderError
api = importlib.import_module("aios_model_api_staging")
calls = []
class DerivedOnlyClient:
def build(self, version, candidate_id, source_commit, *, nonce):
calls.append((version, candidate_id, source_commit, nonce))
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
with pytest.raises(TrustedCandidateBuilderUnavailable):
api._HostTrustedCandidateBuilderAdapter(DerivedOnlyClient()).build(
{
"candidate_id": "r42-release-candidate-v2r2c3-20260820",
"source_commit": COMMIT,
"version": "V2R2C3",
}
)
assert calls[0][:3] == (
"V2R2C3",
"r42-release-candidate-v2r2c3-20260820",
COMMIT,
)
@@ -0,0 +1,83 @@
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/"active-runtime/app"))
from aios_capability_contract import ( # noqa: E402
has_capabilities,
normalize_required_capabilities,
normalize_worker_capabilities,
normalized_capability_matrix,
required_capabilities_subset,
)
def test_release_candidate_is_known_to_task_and_worker_vocabularies():
capability="release:candidate"
assert normalize_required_capabilities([capability])==frozenset({capability})
assert normalize_worker_capabilities([capability])==frozenset({capability})
assert has_capabilities([capability],[capability]) is True
assert normalized_capability_matrix([capability],[capability])=={
"worker_effective":["release:candidate"],
"task_required":["release:candidate"],
"eligible":True,
}
def test_release_candidate_capability_does_not_imply_high_risk_release_operations():
worker=["release:candidate"]
for forbidden in (
"submitOwnerApproval",
"startDeployment",
"tag_publish",
"baseline_registry_finalize",
"runtime_switch",
"deploy.production",
):
assert has_capabilities(worker,[forbidden]) is False
def test_release_wildcards_and_nearby_unknown_names_remain_fail_closed():
assert has_capabilities(["release:candidate"],["release:*"]) is False
assert has_capabilities(["release:candidate"],["release:candidate.extra"]) is False
assert has_capabilities(["release:*"],["release:candidate"]) is False
def test_release_candidate_delegation_subset_is_exact():
assert required_capabilities_subset(
["release:candidate"],
["release:candidate"],
) is True
assert required_capabilities_subset(
["release:candidate"],
["startDeployment"],
) is False
@pytest.mark.parametrize(
"worker,required",
[
(["agent.runtime.hermes"],["agent.runtime.hermes"]),
(["agent.runtime.openclaw"],["agent.runtime.openclaw"]),
(["git.read"],["repository_read"]),
(["git.test"],["test_execution"]),
(
["governed.write.branch","governed.write.file"],
["governed.git.write"],
),
],
)
def test_existing_known_capability_semantics_are_non_regressed(worker,required):
assert has_capabilities(worker,required) is True
def test_release_candidate_worker_does_not_match_empty_or_unrelated_task_by_escalation():
assert has_capabilities(["release:candidate"],[]) is True
assert has_capabilities(["release:candidate"],["git.read"]) is False
assert has_capabilities(["release:candidate"],["agent.runtime.hermes"]) is False
+95
View File
@@ -0,0 +1,95 @@
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/"active-runtime/app"))
from aios_release_lineage_contract import ( # noqa: E402
FIXED_V2R2C2,
ReleaseLineageError,
validate_fixed_v2r2c2_task,
)
def fixed_task(source=FIXED_V2R2C2.source_commit, *, status="FAILED", attempt=1, max_attempts=1, revision=4):
return {
"task_id":FIXED_V2R2C2.task_id,
"status":status,
"attempt":attempt,
"max_attempts":max_attempts,
"revision":revision,
"required_capabilities":["release:candidate"],
"constraints":{
"release_candidate":{
"candidate_id":FIXED_V2R2C2.candidate_id,
"source_commit":source,
"version":FIXED_V2R2C2.version,
"projection_id":"projection-"+"a"*32,
}
},
}
def test_fixed_v2r2c2_lineage_is_the_original_release_worker_candidate():
assert FIXED_V2R2C2.source_commit=="71e736b05fab13efc06af7a4fe2dc022c1cf9a85"
result=validate_fixed_v2r2c2_task(fixed_task())
assert result["status"]=="VERIFIED"
assert result["lineage"]["version"]=="V2R2C2"
def test_later_capability_remediation_is_not_retroactively_v2r2c2_source():
with pytest.raises(ReleaseLineageError,match="FIXED_RELEASE_LINEAGE_DRIFT"):
validate_fixed_v2r2c2_task(
fixed_task("17deb91ca7a187dc81766945b1174e854f86bd25")
)
def test_acp_engineering_candidate_is_not_retroactively_v2r2c2_source():
with pytest.raises(ReleaseLineageError,match="FIXED_RELEASE_LINEAGE_DRIFT"):
validate_fixed_v2r2c2_task(
fixed_task("b4b4416c93fedbe451d655d49e28f915b8fd38e6")
)
@pytest.mark.parametrize(
"status,attempt,max_attempts,revision",
[
("FAILED",1,1,4),
("QUEUED",1,2,5),
("CLAIMED",2,2,6),
("VERIFYING",2,2,9),
("SUCCEEDED",2,2,10),
],
)
def test_lineage_contract_does_not_confuse_lifecycle_state_with_release_identity(
status,attempt,max_attempts,revision
):
result=validate_fixed_v2r2c2_task(
fixed_task(
status=status,
attempt=attempt,
max_attempts=max_attempts,
revision=revision,
)
)
assert result["status"]=="VERIFIED"
assert result["task_status"]==status
assert result["attempt"]==attempt
assert result["max_attempts"]==max_attempts
assert result["revision"]==revision
def test_capability_and_task_identity_are_bound():
value=fixed_task()
value["required_capabilities"]=[]
with pytest.raises(ReleaseLineageError,match="FIXED_TASK_CAPABILITY_DRIFT"):
validate_fixed_v2r2c2_task(value)
value=fixed_task()
value["task_id"]="replacement-v2r2c2"
with pytest.raises(ReleaseLineageError,match="FIXED_TASK_ID_DRIFT"):
validate_fixed_v2r2c2_task(value)
+226
View File
@@ -0,0 +1,226 @@
from __future__ import annotations
import json
from pathlib import Path
import sys
import pytest
ROOT=Path(__file__).resolve().parents[1]
APP=ROOT/"active-runtime/app"
sys.path.insert(0,str(APP))
import aios_release_online_agent_worker as worker
def task(**overrides):
value={
"task_id":"r42-release-candidate-live-acceptance-20260819",
"required_capabilities":["release:candidate"],
"constraints":{
"release_candidate":{
"candidate_id":"r42-release-candidate-v2r2c2-20260819",
"source_commit":"5"*40,
"version":"V2R2C2",
"projection_id":"projection-"+"a"*32,
}
}
}
value.update(overrides)
return value
def test_release_request_accepts_only_exact_release_capability():
req=worker.ReleaseRequest.from_task(task())
assert req.candidate_id=="r42-release-candidate-v2r2c2-20260819"
assert req.source_commit=="5"*40
assert req.version=="V2R2C2"
assert req.projection_id=="projection-"+"a"*32
with pytest.raises(worker.ReleaseWorkerError,match="TASK_CAPABILITY_SCOPE_INVALID"):
worker.ReleaseRequest.from_task(task(required_capabilities=["release:candidate","git.read"]))
@pytest.mark.parametrize(
"payload",
[
{
"candidate_id":"r42-release-candidate-v2r2c2-20260819",
"source_commit":"short",
"version":"V2R2C2",
"projection_id":"projection-"+"a"*32,
},
{
"candidate_id":"bad",
"source_commit":"5"*40,
"version":"V2R2C2",
"projection_id":"projection-"+"a"*32,
},
{
"candidate_id":"r42-release-candidate-v2r2c2-20260819",
"source_commit":"5"*40,
"version":"bad",
"projection_id":"projection-"+"a"*32,
},
{
"candidate_id":"r42-release-candidate-v2r2c2-20260819",
"source_commit":"5"*40,
"version":"V2R2C2",
"projection_id":"bad",
},
{
"candidate_id":"r42-release-candidate-v2r2c2-20260819",
"source_commit":"5"*40,
"version":"V2R2C2",
"projection_id":"projection-"+"a"*32,
"execute":True,
},
],
)
def test_release_request_rejects_invalid_or_extra_fields(payload):
value=task()
value["constraints"]={"release_candidate":payload}
with pytest.raises(worker.ReleaseWorkerError):
worker.ReleaseRequest.from_task(value)
def test_result_payload_preserves_high_risk_denials():
release=worker.ReleaseRequest.from_task(task())
result=worker.result_payload(
"r42-release-candidate-live-acceptance-20260819",
release,
{"candidate_id":release.candidate_id,"status":"BUILT"},
)
assert result["status"]=="SUCCEEDED"
assert result["classification"]=="RELEASE_CANDIDATE_CREATED"
assert result["writer_lease"]=="NONE"
assert result["changed_files"]==[]
assert result["direct_main_write"] is False
assert result["production_write"] is False
assert result["tag_write"] is False
assert result["baseline_write"] is False
assert result["runtime_switch"] is False
assert result["owner_approval_submitted"] is False
assert result["credential_material_exposed"] is False
def test_result_payload_rejects_builder_candidate_mismatch():
release=worker.ReleaseRequest.from_task(task())
with pytest.raises(worker.ReleaseWorkerError,match="BUILDER_CANDIDATE_BINDING_INVALID"):
worker.result_payload(
"r42-release-candidate-live-acceptance-20260819",
release,
{"candidate_id":"other-release-candidate"},
)
def test_worker_calls_only_fixed_release_candidate_route(monkeypatch):
release=worker.ReleaseRequest.from_task(task())
calls=[]
def fake_http(base,method,route,token,body=None,timeout=30):
calls.append((base,method,route,token,body))
if route=="/health":
return 200,{"status":"ok"}
if route=="/createReleaseCandidate" and body=={}:
return 400,{"error":"request_invalid"}
if route.endswith("/claim"):
return 200,{"lease_token":"l"*32,"attempt":1}
if route.endswith("/heartbeat"):
return 200,{}
if route=="/createReleaseCandidate":
return 200,{
"candidate_id":release.candidate_id,
"artifact_sha256":"f"*64,
}
if route.endswith("/result"):
return 200,{"status":"VERIFYING"}
raise AssertionError(route)
monkeypatch.setattr(worker,"http_json",fake_http)
monkeypatch.setattr(worker,"audit",lambda *args,**kwargs:None)
monkeypatch.setattr(worker,"write_liveness",lambda *args,**kwargs:None)
monkeypatch.setattr(worker.Heartbeat,"first",lambda self:None)
monkeypatch.setattr(worker.Heartbeat,"close",lambda self:None)
monkeypatch.setattr(worker.Heartbeat,"ensure",lambda self:None)
monkeypatch.setattr(worker.threading.Thread,"start",lambda self:None)
assert worker.execute_task(
task(),
agent_token="a"*32,
release_token="r"*32,
) is True
release_calls=[
item for item in calls
if item[0]==worker.RELEASE_API
and item[2]=="/createReleaseCandidate"
and item[4]!={}
]
assert len(release_calls)==1
assert release_calls[0][1]=="POST"
assert release_calls[0][2]=="/createReleaseCandidate"
assert release_calls[0][4]=={
"candidate_id":release.candidate_id,
"source_commit":release.source_commit,
"version":release.version,
}
def test_write_liveness_records_release_capability(tmp_path,monkeypatch):
path=tmp_path/"worker-liveness.json"
monkeypatch.setattr(worker,"STATE_ROOT",tmp_path)
monkeypatch.setattr(worker,"LIVENESS_PATH",path)
worker.write_liveness(
"IDLE",
stage="poll",
visible_count=2,
)
value=json.loads(path.read_text())
assert value["schema"]=="aios.online-agent.worker-liveness.v1"
assert value["agent_type"]=="release-preparation-worker"
assert value["capabilities"]==["release:candidate"]
assert value["state"]=="IDLE"
assert value["visible_count"]==2
def test_sd_notify_is_noop_without_systemd_socket(monkeypatch):
monkeypatch.delenv("NOTIFY_SOCKET",raising=False)
worker.sd_notify("WATCHDOG=1")
def test_systemd_unit_uses_two_encrypted_credentials_and_loopback_release_api():
unit=(ROOT/"deploy/systemd/aios-release-online-agent-worker.service").read_text()
assert "LoadCredentialEncrypted=agent_token:" in unit
assert "LoadCredentialEncrypted=release_candidate_token:" in unit
assert "AIOS_RELEASE_API_URL=http://127.0.0.1:18890" in unit
assert "AIOS_RELEASE_WORKER_AGENT_ID=online-release-worker-cn-network-mkt-20260819" in unit
assert "Type=notify" in unit
assert "WatchdogSec=90" in unit
assert "Restart=always" in unit
assert "IPAddressDeny=any" in unit
assert "IPAddressAllow=127.0.0.0/8" in unit
assert "InaccessiblePaths=/data/aios-gitops /home" in unit
assert "submitOwnerApproval" not in unit
assert "startDeployment" not in unit
def test_source_contains_no_high_risk_release_routes():
source=(ROOT/"active-runtime/app/aios_release_online_agent_worker.py").read_text()
assert '"/createReleaseCandidate"' in source
for forbidden in (
"/submitOwnerApproval",
"/startDeployment",
"/requestOrCreateTag",
"/buildProductionArtifact",
"/executeApprovedProductionRelease",
):
assert forbidden not in source
def test_docs_keep_release_candidate_distinct_from_production():
doc=(ROOT/"docs/superpowers/specs/2026-08-19-release-preparation-online-agent.md").read_text()
assert "release:candidate" in doc
assert "Owner signature" in doc
assert "never\nstarts deployment" in doc or "never starts deployment" in doc
assert "LoadCredentialEncrypted" in doc
+592
View File
@@ -0,0 +1,592 @@
from __future__ import annotations
import dataclasses
import hashlib
import hmac
import json
import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
import aios_trusted_candidate_builder as builder_runtime # noqa: E402
from aios_trusted_candidate_builder import ( # noqa: E402
BuilderError,
BuilderKeyring,
DeterministicEvidenceCollector,
PeerIdentity,
ServerOwnedEvidenceSource,
TrustedCandidateBuilder,
TrustedCandidateBuilderConfig,
UnixTrustedCandidateBuilderClient,
load_builder_policy,
)
from silent_release_controller import ReleaseCandidate, ReleaseState # noqa: E402
from silent_release_hardening import ( # noqa: E402
BuilderAttestation,
BuilderAttestationKeyRecord,
BuilderAttestationKeyRing,
BuilderAttestationVerifier,
DeploymentPlan,
FinalizationPlan,
ReleasePlan,
TagPlan,
TrustedCandidateBundle,
)
POLICY_PATH = ROOT / "config" / "gitops" / "trusted-candidate-builder-policy.json"
NOW = 1_725_000_000
class RecordingDomainSigner:
key_id = "test-builder-key"
key_generation = 1
def __init__(self) -> None:
self.calls: list[tuple[str, bytes]] = []
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
self.calls.append((purpose, payload))
return hashlib.sha256(b"test-signature\0" + payload).digest()
def make_seam(*, peer: PeerIdentity | None = None, cgroup: str = "/system.slice/aios-gitops-model-api.service", process=("/usr/bin/python3.12", ("/usr/bin/python3", "/opt/aios/gitops-model-api/test/runtime/app/aios_model_api_staging.py")), server_clock=lambda: NOW, client_clock=lambda: NOW, collector=None, nonce_ledger_path=None, signer_override=None) -> tuple[TrustedCandidateBuilder, UnixTrustedCandidateBuilderClient, RecordingDomainSigner]:
policy = load_builder_policy(POLICY_PATH)
config = TrustedCandidateBuilderConfig.from_policy(policy)
keyring = BuilderKeyring(builder_hmac=b"h" * 32)
signer = signer_override or RecordingDomainSigner()
server = TrustedCandidateBuilder(
config,
keyring,
collector=collector or DeterministicEvidenceCollector(config, evidence_source=passing_bundle),
signer=signer,
peer_identity_resolver=lambda _connection: peer or PeerIdentity(pid=77, uid=1000, gid=1000),
cgroup_resolver=lambda _pid: cgroup,
process_identity_resolver=lambda _pid: process,
clock=server_clock,
nonce_ledger_path=nonce_ledger_path,
)
return server, UnixTrustedCandidateBuilderClient(config, keyring, clock=client_clock), signer
def passing_bundle(recipe_name, recipe):
base_oid = recipe["base_oid"]
digest = lambda label: hashlib.sha256(f"{label}\0{base_oid}".encode()).hexdigest()
candidate = ReleaseCandidate(
candidate_id=recipe["candidate_id"], source_commit=base_oid,
tree_oid=digest("tree")[:40], artifact_sha256=digest("artifact"),
manifest_sha256=digest("manifest"), tests={"passed": True, "receipt": digest("tests")},
canary={"passed": True, "receipt": digest("canary")}, rollback_ready=True,
git_consistency={"consistent": True}, gitea_consistency={"consistent": True},
state=ReleaseState.READY_FOR_APPROVAL,
)
policy_sha = digest("policy")
plan = ReleasePlan(
deployment=DeploymentPlan(
service="aios-silent-release-controller", release_path="/srv/release/r1",
release_root="/srv/release", cutover_link="/srv/release/current",
dropin_path="/srv/release/r1/deploy/systemd/controller.service",
dropin_sha256=digest("dropin"), systemd_unit_sha256=digest("unit"),
rollback_path="/srv/release/rollback-r1", rollback_sha256=digest("rollback"),
source_commit=base_oid, tree_oid=candidate.tree_oid,
artifact_sha256=candidate.artifact_sha256, manifest_sha256=candidate.manifest_sha256,
package_closure_sha256=digest("closure"), authoritative_git_dir="/data/aios-gitops/repo.git",
repository_identity="aios/aios", baseline_before_sha256=digest("baseline"),
registry_before_sha256=digest("registry"), production_tag_refset_before_sha256=digest("tagset"),
health_contract_version="trusted-builder/v1", timeout_policy={"seconds": 10},
retry_policy={"attempts": 1}, policy_sha256=policy_sha, health_contract={"checked": True},
),
tag=TagPlan(
version="trusted-v2r2c2-001", tag_type="annotated", expected_tag_oid=digest("tag")[:40],
expected_target_oid=base_oid, evidence_closure_oid=digest("closure-oid")[:40],
approval_plan_id="owner-plan-trusted-v2r2c2", repository_identity="aios/aios",
gitea_owner="aios", gitea_repository="aios", current_remote_tag_oid=None,
remote_absent_evidence_sha256=digest("tag-absence"), verification_contract="trusted-builder/v1",
),
finalization=FinalizationPlan(
version="trusted-v2r2c2-001", expected_tag_oid=digest("tag")[:40], expected_target_oid=base_oid,
expected_previous_baseline_sha256=digest("baseline"), registry_expected_record={"base": base_oid},
baseline_expected_record={"base": base_oid}, final_consistency_contract="trusted-builder/v1",
), policy_sha256=policy_sha,
)
checks = {name: True for name in [
"authoritative_git", "artifact", "canary", "gitea", "manifest",
"package_closure", "rollback", "secret_scan", "tests",
]}
TrustedCandidateBundle(candidate, plan, checks).validate()
canonical_digest = lambda value: hashlib.sha256(
json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
evidence_digests = {
"artifact": candidate.artifact_sha256,
"manifest": candidate.manifest_sha256,
"package_closure": plan.deployment.package_closure_sha256,
"tests": canonical_digest(candidate.tests),
"canary": canonical_digest(candidate.canary),
"gitea": canonical_digest(candidate.gitea_consistency),
"rollback": canonical_digest({
"ready": True,
"rollback_sha256": plan.deployment.rollback_sha256,
"source_commit": candidate.source_commit,
}),
"policy": plan.policy_sha256,
"secret_scan": digest("secret-scan"),
}
return {
"candidate": candidate.to_dict(),
"release_plan": plan.to_dict(),
"checks": checks,
"evidence_digests": evidence_digests,
}
def rehmac(request: dict) -> None:
unsigned = {key: value for key, value in request.items() if key != "hmac"}
request["hmac"] = hmac.new(
b"h" * 32,
json.dumps(unsigned, sort_keys=True, separators=(",", ":")).encode(),
hashlib.sha256,
).hexdigest()
def test_public_build_seam_accepts_only_the_fixed_v2r2c2_recipe_and_returns_server_owned_evidence() -> None:
server, client, signer = make_seam()
request = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="a" * 32)
result = server.handle_request(request, connection=object())
assert result["schema"] == "aios.trusted-candidate-builder.response.v1"
assert result["status"] == "BUILT"
assert result["recipe"] == "V2R2C2"
assert result["repository"] == "aios/aios"
assert result["task_id"] == "r42-release-candidate-v2r2c2-live-20260819"
assert result["silent_release_bundle"]["candidate"]["candidate_id"] == "r42-release-candidate-v2r2c2-20260819"
assert result["base_oid"] == "71e736b05fab13efc06af7a4fe2dc022c1cf9a85"
assert set(result["checks"]) == set(server.config.required_checks)
assert all(result["checks"].values())
assert result["attestation"]["trust_domain"] == "aios.trusted-builder.attestation.v1"
assert signer.calls and signer.calls[0][0] == "builder_attestation"
assert result["attestation"]["fingerprint"] == hashlib.sha256(signer.calls[0][1]).hexdigest()
assert BuilderAttestation.from_dict(result["attestation"]).canonical_bytes() == signer.calls[0][1]
silent_bundle = result["silent_release_bundle"]
round_trip = TrustedCandidateBundle(
ReleaseCandidate.from_dict(silent_bundle["candidate"]),
ReleasePlan.from_dict(silent_bundle["release_plan"]),
silent_bundle["checks"],
)
assert round_trip.validate().candidate.candidate_id == result["evidence"]["candidate_id"]
@pytest.mark.parametrize(
"mutate, error",
[
(lambda request: request.update({"base_oid": "f" * 40}), "REQUEST_HMAC_INVALID"),
(lambda request: request.update({"artifact_sha256": "0" * 64}), "REQUEST_SCHEMA_INVALID"),
],
)
def test_public_build_seam_rejects_tampering_and_caller_owned_evidence(mutate, error) -> None:
server, client, _ = make_seam()
request = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="b" * 32)
mutate(request)
with pytest.raises(BuilderError, match=error):
server.handle_request(request, connection=object())
@pytest.mark.parametrize("field,value", [
("repository", "other/repository"),
("task_id", "other-task"),
("base_oid", "f" * 40),
("purpose", "owner_approval"),
])
def test_public_build_seam_rejects_reauthenticated_scope_rebinding(field, value) -> None:
server, client, _ = make_seam()
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="s" * 32,
)
request[field] = value
rehmac(request)
with pytest.raises(BuilderError, match="REQUEST_SCOPE_INVALID"):
server.handle_request(request, connection=object())
def test_public_build_seam_rejects_wrong_hmac() -> None:
server, client, _ = make_seam()
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="w" * 32,
)
request["hmac"] = "0" * 64
with pytest.raises(BuilderError, match="REQUEST_HMAC_INVALID"):
server.handle_request(request, connection=object())
def test_public_build_seam_returns_the_original_result_for_exact_nonce_replay_and_rejects_conflicts() -> None:
server, client, _ = make_seam()
request = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="c" * 32)
first = server.handle_request(request, connection=object())
assert server.handle_request(request, connection=object()) == first
conflict = client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="c" * 32)
conflict["issued_at"] += 1
unsigned = {key: value for key, value in conflict.items() if key != "hmac"}
conflict["hmac"] = hmac.new(b"h" * 32, json.dumps(unsigned, sort_keys=True, separators=(",", ":")).encode(), hashlib.sha256).hexdigest()
with pytest.raises(BuilderError, match="REPLAY_CONFLICT"):
server.handle_request(conflict, connection=object())
def test_exact_replay_survives_restart_and_the_original_freshness_window(tmp_path) -> None:
ledger = tmp_path / "nonce-ledger.json"
server, client, _ = make_seam(nonce_ledger_path=ledger)
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="r" * 32,
)
first = server.handle_request(request, connection=object())
restarted, _, _ = make_seam(
server_clock=lambda: NOW + 3600,
nonce_ledger_path=ledger,
)
assert restarted.handle_request(request, connection=object()) == first
def test_readiness_probe_checks_collector_and_signer_without_signing() -> None:
server, client, signer = make_seam()
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="q" * 32,
operation=server.config.readiness_operation,
)
result = server.handle_request(request, connection=object())
assert result["status"] == "READY"
assert result["collector_ready"] is True
assert result["signer_ready"] is True
assert result["builder_key_id"] == signer.key_id
assert result["policy_sha256"] == server.config.policy_sha256
assert "signature" not in result
assert "silent_release_bundle" not in result
assert signer.calls == []
def test_readiness_fails_closed_when_signer_is_unavailable() -> None:
class UnavailableSigner:
@property
def key_id(self):
raise BuilderError("SIGNER_UNAVAILABLE")
@property
def key_generation(self):
return 1
def sign_for_purpose(self, purpose, payload):
raise AssertionError("readiness must not attempt signing")
server, client, _ = make_seam(signer_override=UnavailableSigner())
request = client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="u" * 32,
operation=server.config.readiness_operation,
)
with pytest.raises(BuilderError, match="SIGNER_UNAVAILABLE"):
server.handle_request(request, connection=object())
def test_unix_client_applies_bounded_timeout_and_fails_closed(monkeypatch) -> None:
observed = {}
class TimeoutSocket:
def __enter__(self):
return self
def __exit__(self, *_args):
return False
def settimeout(self, seconds):
observed["timeout"] = seconds
def connect(self, _path):
raise TimeoutError("bounded test timeout")
monkeypatch.setattr(builder_runtime.socket, "socket", lambda *_args: TimeoutSocket())
monkeypatch.setattr(builder_runtime.socket, "AF_UNIX", 1, raising=False)
monkeypatch.setattr(builder_runtime.socket, "SOCK_STREAM", 1, raising=False)
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH))
client = UnixTrustedCandidateBuilderClient(
config,
BuilderKeyring(builder_hmac=b"h" * 32),
socket_path=Path("/run/aios-trusted-candidate-builder.sock"),
clock=lambda: NOW,
)
with pytest.raises(BuilderError, match="BUILDER_TRANSPORT_UNAVAILABLE"):
client.build(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="t" * 32,
)
assert observed["timeout"] == config.request_timeout_seconds
def test_backend_attestation_round_trip_verifies_with_production_keyring() -> None:
private = Ed25519PrivateKey.generate()
class Ed25519Signer:
key_id = "builder-key-001"
key_generation = 1
def sign_for_purpose(self, purpose, payload):
assert purpose == "builder_attestation"
return private.sign(payload)
server, client, _ = make_seam(signer_override=Ed25519Signer())
response = server.handle_request(
client.create_request(
"V2R2C2",
"r42-release-candidate-v2r2c2-20260819",
"71e736b05fab13efc06af7a4fe2dc022c1cf9a85",
nonce="v" * 32,
),
connection=object(),
)
public_key = private.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
verifier = BuilderAttestationVerifier(
BuilderAttestationKeyRing(
[BuilderAttestationKeyRecord(
key_id="builder-key-001",
public_key=public_key,
algorithm="Ed25519",
purpose="builder_attestation",
generation=1,
not_before="2020-01-01T00:00:00Z",
expires_at="2099-01-01T00:00:00Z",
status="ACTIVE",
)],
require_attestation_metadata=True,
)
)
raw = response["silent_release_bundle"]
bundle = TrustedCandidateBundle(
ReleaseCandidate.from_dict(raw["candidate"]),
ReleasePlan.from_dict(raw["release_plan"]),
raw["checks"],
BuilderAttestation.from_dict(raw["attestation"], require_fingerprint=True),
)
assert bundle.validate(verifier).candidate.candidate_id == response["evidence"]["candidate_id"]
def test_public_build_seam_rejects_direct_unbound_v2r2c3_and_authentication_boundaries() -> None:
server, client, _ = make_seam()
with pytest.raises(BuilderError, match="GOVERNED_V2R2C3_DERIVATION_REQUIRED"):
client.create_request("V2R2C3", "unknown", "f" * 40, nonce="d" * 32)
denied_peer, denied_client, _ = make_seam(peer=PeerIdentity(pid=77, uid=0, gid=0))
with pytest.raises(BuilderError, match="PEER_IDENTITY_DENIED"):
denied_peer.handle_request(denied_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="e" * 32), connection=object())
denied_cgroup, denied_client, _ = make_seam(cgroup="/system.slice/untrusted.service")
with pytest.raises(BuilderError, match="PEER_CGROUP_DENIED"):
denied_cgroup.handle_request(denied_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="f" * 32), connection=object())
denied_process, denied_client, _ = make_seam(
process=("/usr/bin/python3.12", ("/usr/bin/python3", "/tmp/aios_model_api_staging.py"))
)
with pytest.raises(BuilderError, match="PEER_PROCESS_DENIED"):
denied_process.handle_request(denied_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="p" * 32), connection=object())
stale_server, stale_client, _ = make_seam(client_clock=lambda: NOW - 61)
with pytest.raises(BuilderError, match="REQUEST_STALE"):
stale_server.handle_request(stale_client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="g" * 32), connection=object())
def test_public_build_seam_fails_closed_when_server_owned_collector_is_incomplete() -> None:
def incomplete_evidence(recipe_name, recipe):
value = passing_bundle(recipe_name, recipe)
value["checks"]["canary"] = False
return value
policy = load_builder_policy(POLICY_PATH)
config = TrustedCandidateBuilderConfig.from_policy(policy)
server, client, _ = make_seam(
collector=DeterministicEvidenceCollector(config, evidence_source=incomplete_evidence)
)
with pytest.raises(BuilderError, match="COLLECTOR_CHECKS_INCOMPLETE"):
server.handle_request(client.create_request("V2R2C2", "r42-release-candidate-v2r2c2-20260819", "71e736b05fab13efc06af7a4fe2dc022c1cf9a85", nonce="h" * 32), connection=object())
def test_server_owned_source_rehashes_every_fixed_evidence_file_and_rejects_tampering(tmp_path) -> None:
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH))
recipe = config.fixed_recipe("V2R2C2")
raw = passing_bundle("V2R2C2", recipe)
candidate = ReleaseCandidate.from_dict(raw["candidate"])
plan = ReleasePlan.from_dict(raw["release_plan"])
evidence_root = tmp_path / "evidence"
version_root = evidence_root / "V2R2C2"
version_root.mkdir(parents=True)
def write_bytes(name: str, value: bytes) -> str:
path = version_root / name
path.write_bytes(value)
path.chmod(0o600)
return hashlib.sha256(value).hexdigest()
def canonical(value: dict) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
tests = {"passed": True, "receipt": "tests-fixed"}
canary = {"passed": True, "receipt": "canary-fixed"}
gitea = {
"consistent": True,
"remote_ref_oid": recipe["source_commit"],
"repository": "aios/aios",
"source_commit": recipe["source_commit"],
"tree_oid": candidate.tree_oid,
}
git_consistency = {
"consistent": True,
"repository": "aios/aios",
"source_commit": recipe["source_commit"],
"tree_oid": candidate.tree_oid,
}
rollback = {
"ready": True,
"rollback_sha256": plan.deployment.rollback_sha256,
"source_commit": recipe["source_commit"],
}
artifact_bytes = b"immutable-artifact"
manifest_bytes = b'{"manifest":"fixed"}\n'
closure_bytes = b"closure artifact.tar.gz\n"
policy_bytes = b'{"policy":"fixed"}\n'
artifact_sha = write_bytes("artifact.tar.gz", artifact_bytes)
manifest_sha = write_bytes("manifest.json", manifest_bytes)
closure_sha = write_bytes("checksums.sha256", closure_bytes)
tests_sha = write_bytes("tests.json", canonical(tests))
canary_sha = write_bytes("canary.json", canonical(canary))
gitea_sha = write_bytes("gitea.json", canonical(gitea))
rollback_sha = write_bytes("rollback.json", canonical(rollback))
policy_sha = write_bytes("silent-release-policy.json", policy_bytes)
secret_scan = {"artifact_sha256": artifact_sha, "passed": True}
secret_sha = write_bytes("secret-scan.json", canonical(secret_scan))
candidate.artifact_sha256 = artifact_sha
candidate.manifest_sha256 = manifest_sha
candidate.tests = tests
candidate.canary = canary
candidate.git_consistency = git_consistency
candidate.gitea_consistency = gitea
deployment = dataclasses.replace(
plan.deployment,
artifact_sha256=artifact_sha,
manifest_sha256=manifest_sha,
package_closure_sha256=closure_sha,
policy_sha256=policy_sha,
)
plan = dataclasses.replace(plan, deployment=deployment, policy_sha256=policy_sha)
receipt = {
"schema": "aios.trusted-candidate-builder.evidence.v1",
"version": "V2R2C2",
"candidate": candidate.to_dict(),
"release_plan": plan.to_dict(),
}
write_bytes("receipt.json", canonical(receipt))
git_state = {"tree": candidate.tree_oid, "ancestry": 0}
def git_runner(argv, **_kwargs):
if "rev-parse" in argv:
return SimpleNamespace(stdout=git_state["tree"] + "\n", returncode=0)
return SimpleNamespace(stdout="", returncode=git_state["ancestry"])
source = ServerOwnedEvidenceSource(
config,
tmp_path / "repo.git",
evidence_root,
expected_owner_uid=None,
git_runner=git_runner,
)
collected = source("V2R2C2", recipe)
assert collected["candidate"]["artifact_sha256"] == artifact_sha
assert collected["evidence_digests"] == {
"artifact": artifact_sha,
"manifest": manifest_sha,
"package_closure": closure_sha,
"tests": tests_sha,
"canary": canary_sha,
"gitea": gitea_sha,
"rollback": rollback_sha,
"policy": policy_sha,
"secret_scan": secret_sha,
}
(version_root / "manifest.json").write_bytes(b"tampered")
with pytest.raises(BuilderError, match="COLLECTOR_DIGEST_MISMATCH"):
source("V2R2C2", recipe)
(version_root / "manifest.json").write_bytes(manifest_bytes)
for name, tampered, expected in (
("artifact.tar.gz", b"tampered", "COLLECTOR_DIGEST_MISMATCH"),
("checksums.sha256", b"tampered", "COLLECTOR_DIGEST_MISMATCH"),
("silent-release-policy.json", b"tampered", "COLLECTOR_DIGEST_MISMATCH"),
("tests.json", canonical({"passed": True, "receipt": "wrong"}), "COLLECTOR_TESTS_MISMATCH"),
("canary.json", canonical({"passed": True, "receipt": "wrong"}), "COLLECTOR_CANARY_MISMATCH"),
("gitea.json", canonical({**gitea, "consistent": False}), "COLLECTOR_GITEA_MISMATCH"),
("rollback.json", canonical({**rollback, "ready": False}), "COLLECTOR_ROLLBACK_MISMATCH"),
("secret-scan.json", canonical({**secret_scan, "passed": False}), "COLLECTOR_SECRET_SCAN_MISMATCH"),
):
path = version_root / name
original = path.read_bytes()
path.write_bytes(tampered)
with pytest.raises(BuilderError, match=expected):
source("V2R2C2", recipe)
path.write_bytes(original)
git_state["tree"] = "f" * 40
with pytest.raises(BuilderError, match="COLLECTOR_GIT_MISMATCH"):
source("V2R2C2", recipe)
git_state["tree"] = candidate.tree_oid
git_state["ancestry"] = 1
with pytest.raises(BuilderError, match="COLLECTOR_GIT_MISMATCH"):
source("V2R2C2", recipe)
def test_systemd_socket_and_service_are_af_unix_hardened_and_credential_bound() -> None:
socket_unit = (ROOT / "deploy/systemd/aios-trusted-candidate-builder.socket").read_text()
service_unit = (ROOT / "deploy/systemd/aios-trusted-candidate-builder.service").read_text()
assert "ListenStream=/run/aios-trusted-candidate-builder.sock" in socket_unit
assert "SocketMode=0660" in socket_unit
assert "LoadCredentialEncrypted=signing_private_key:" in service_unit
assert "LoadCredentialEncrypted=builder_hmac:" in service_unit
assert "NoNewPrivileges=true" in service_unit
assert "ProtectSystem=strict" in service_unit
assert "PrivateNetwork=true" in service_unit
assert "RestrictAddressFamilies=AF_UNIX" in service_unit
assert "StateDirectory=aios-trusted-candidate-builder" in service_unit
assert "AIOS_TRUSTED_BUILDER_KEY_GENERATION=1" in service_unit
assert "aios-approved-release-executor" not in service_unit
assert "--socket-fd 3" in service_unit
policy = load_builder_policy(POLICY_PATH)
assert policy["allowed_peer_uid"] == 1000
assert policy["allowed_peer_cgroups"] == [
"/system.slice/aios-gitops-model-api.service"
]