Files
aios/active-runtime/app/v2r1c7_production_adapter.py
T
aios-autonomy-agent 5e5a46f040
autonomy-unit-tests 23 passed on exact PR head
autonomy-smoke-tests executor CLI smoke passed on exact PR head
git-diff-check 11 additive paths; protected paths unchanged
py-compile 5 runtime modules compiled
reconciliation-contract Authority/Gitea main and branch OIDs reconciled
release-state-machine executor and metadata tests passed
secret-scan 11 changed files scanned; no secret markers
signer-validator-contract validator self-test and signer CLI contract passed
owner-approval-gate Owner actor 2 auto-merge intent verified for exact current head; protected target main; no unresolved conversations; signed-commit gate not required
fix(release): add local owner ingress and governed production executor
2026-08-07 17:22:03 +08:00

456 lines
16 KiB
Python
Executable File

#!/usr/bin/env python3
"""Concrete V2R1C7 production deployment adapter.
The adapter consumes only an already approved immutable artifact. It never
writes Git refs, Registry, or Baseline. A rollback only tears down the V2R1C7
runtime and leaves V2R1C5/V2R1C6 untouched.
"""
from __future__ import annotations
import argparse
import grp
import hashlib
import json
import os
import pwd
import shutil
import stat
import subprocess
import sys
import tarfile
import tempfile
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any, Mapping
VERSION = "V2R1C7"
SERVICE = "aios-v2r1c7-production.service"
PORT = 18927
RUNTIME_USER = "aios-v2r1c7"
RUNTIME_GROUP = "aios-v2r1c7"
REQUIRED_MEMBERS = {
"payload/VERSION",
"payload/active-runtime/app/deployment_state.py",
"payload/active-runtime/app/v2r1c6_privacy_tools.py",
"payload/active-runtime/app/v2r1c7_health_service.py",
"payload/deploy/aios-v2/systemd/aios-v2r1c7-production.service",
"payload/deploy/aios-v2/v2r1c7-runtime-config.json",
"payload/deploy/aios-v2/v2r1c7-rollback.json",
"payload/scripts/probe_v2r1c7_health.py",
"payload/scripts/rollback_v2r1c7_release.sh",
"payload/BUILD-METADATA.json",
"payload/ARTIFACT-MANIFEST.json",
"payload/ROLLBACK-METADATA.json",
}
def load_request() -> dict[str, Any]:
value = json.load(sys.stdin)
if not isinstance(value, dict):
raise ValueError("request_object_required")
if value.get("schema") != "aios.production-executor-request.v1":
raise ValueError("request_schema_invalid")
if value.get("version") != VERSION:
raise ValueError("version_mismatch")
if value.get("production_service") != SERVICE:
raise ValueError("service_mismatch")
if int(value.get("production_port", -1)) != PORT:
raise ValueError("port_mismatch")
return value
def sha256_file(path: Path) -> str:
if path.is_symlink() or not path.is_file():
raise ValueError("artifact_regular_file_required")
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def atomic_json(path: Path, value: Mapping[str, Any], *, mode: int, gid: int | None = None) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_name(path.name + ".tmp")
temporary.write_text(
json.dumps(dict(value), sort_keys=True, separators=(",", ":")) + "\n",
encoding="utf-8",
)
os.chmod(temporary, mode)
if gid is not None:
os.chown(temporary, 0, gid)
os.replace(temporary, path)
def atomic_text(path: Path, text: str, *, mode: int, gid: int | None = None) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_name(path.name + ".tmp")
temporary.write_text(text, encoding="utf-8")
os.chmod(temporary, mode)
if gid is not None:
os.chown(temporary, 0, gid)
os.replace(temporary, path)
def systemctl(*args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["systemctl", *args],
check=check,
capture_output=True,
text=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
def identity() -> tuple[int, int]:
try:
user = pwd.getpwnam(RUNTIME_USER)
group = grp.getgrnam(RUNTIME_GROUP)
except KeyError as exc:
raise RuntimeError("RUNTIME_IDENTITY_MISSING") from exc
if user.pw_gid != group.gr_gid:
raise RuntimeError("RUNTIME_IDENTITY_GROUP_MISMATCH")
return user.pw_uid, group.gr_gid
def safe_extract(artifact: Path, destination: Path) -> set[str]:
with tempfile.TemporaryDirectory(prefix="aios-v2r1c7-unpack-") as temp:
tar_path = Path(temp) / "release.tar"
with tar_path.open("wb") as output:
completed = subprocess.run(
["zstd", "-dc", str(artifact)],
check=False,
stdout=output,
stderr=subprocess.PIPE,
timeout=60,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise RuntimeError("ARTIFACT_DECOMPRESSION_FAILED")
with tarfile.open(tar_path, "r:") as archive:
members = archive.getmembers()
names = {m.name for m in members}
missing = REQUIRED_MEMBERS - names
if missing:
raise RuntimeError("ARTIFACT_MEMBERS_MISSING:" + ",".join(sorted(missing)))
for member in members:
relative = Path(member.name)
if (
relative.is_absolute()
or ".." in relative.parts
or member.issym()
or member.islnk()
or not (member.isfile() or member.isdir())
):
raise RuntimeError("UNSAFE_ARTIFACT_MEMBER:" + member.name)
if not member.name.startswith("payload/"):
raise RuntimeError("UNEXPECTED_ARTIFACT_ROOT:" + member.name)
payload = destination
payload.mkdir(parents=True, exist_ok=True)
for member in members:
rel = Path(member.name).relative_to("payload")
if str(rel) == ".":
continue
target = payload / rel
resolved_parent = target.parent.resolve()
resolved_parent.relative_to(payload.resolve())
if member.isdir():
target.mkdir(parents=True, exist_ok=True)
os.chmod(target, 0o755)
continue
target.parent.mkdir(parents=True, exist_ok=True)
source = archive.extractfile(member)
if source is None:
raise RuntimeError("ARTIFACT_MEMBER_READ_FAILED")
with target.open("wb") as output:
shutil.copyfileobj(source, output)
# Scripts must be executable; all other payload files are read-only.
mode = 0o755 if rel.parts and rel.parts[0] == "scripts" else 0o644
os.chmod(target, mode)
return names
def health(url: str, expected: Mapping[str, Any]) -> dict[str, Any]:
try:
with urllib.request.urlopen(url, timeout=5) as response:
body = json.loads(response.read().decode("utf-8"))
status = response.status
except (urllib.error.URLError, TimeoutError, ValueError):
return {"status": "UNAVAILABLE"}
exact = (
status == 200
and body.get("version") == expected["version"]
and body.get("commit_oid") == expected["target_oid"]
and body.get("artifact_sha256") == expected["artifact_sha256"]
and body.get("deployment_state") == "ACTIVE"
and body.get("production_deployed") is True
)
if not exact:
return {
"status": "CONFLICT",
"http_status": status,
"version": body.get("version"),
"target_oid": body.get("commit_oid"),
"artifact_sha256": body.get("artifact_sha256"),
"deployment_state": body.get("deployment_state"),
"production_deployed": body.get("production_deployed"),
}
return {
"status": "ACTIVE_VERIFIED",
"http_status": 200,
"version": body["version"],
"target_oid": body["commit_oid"],
"artifact_sha256": body["artifact_sha256"],
"deployment_state": body["deployment_state"],
"production_deployed": True,
}
def paths(args: argparse.Namespace, value: Mapping[str, Any]) -> dict[str, Path]:
release_id = (
f"{VERSION}-{str(value['target_oid'])[:12]}-"
f"{str(value['artifact_sha256'])[:12]}"
)
return {
"release_dir": args.release_root / release_id,
"current_link": args.current_link,
"env_file": args.env_file,
"state_file": args.state_file,
"unit_file": args.unit_file,
}
def probe(args: argparse.Namespace, value: Mapping[str, Any]) -> dict[str, Any]:
p = paths(args, value)
url = f"http://127.0.0.1:{PORT}/health"
active = systemctl("is-active", "--quiet", SERVICE, check=False).returncode == 0
if not active:
# A stale exact release directory is safe to reuse; it is not a deployment.
return {
"status": "NOT_APPLIED",
"version": VERSION,
"target_oid": value["target_oid"],
"artifact_sha256": value["artifact_sha256"],
"production_deployed": False,
"deployment_state": "ROLLED_BACK"
if p["state_file"].exists()
and "ROLLED_BACK" in p["state_file"].read_text(encoding="utf-8")
else "ABSENT",
}
result = health(url, value)
if result["status"] == "UNAVAILABLE":
return {
"status": "CONFLICT",
"version": VERSION,
"target_oid": value["target_oid"],
"artifact_sha256": value["artifact_sha256"],
"reason": "service_active_health_unavailable",
}
return result
def install_release(args: argparse.Namespace, value: Mapping[str, Any]) -> dict[str, Any]:
_, gid = identity()
artifact = Path(str(value["artifact_path"]))
if artifact.stat().st_size != int(value["artifact_size"]):
raise RuntimeError("ARTIFACT_SIZE_MISMATCH")
if sha256_file(artifact) != value["artifact_sha256"]:
raise RuntimeError("ARTIFACT_DIGEST_MISMATCH")
p = paths(args, value)
release_dir = p["release_dir"]
marker = release_dir / "DEPLOYMENT-BINDING.json"
marker_value = {
"schema": "aios.runtime-release-binding.v1",
"version": VERSION,
"target_oid": value["target_oid"],
"artifact_sha256": value["artifact_sha256"],
"approval_plan_id": value["approval_plan_id"],
}
if release_dir.exists():
if not marker.is_file():
raise RuntimeError("EXISTING_RELEASE_DIR_UNTRUSTED")
current_marker = json.loads(marker.read_text(encoding="utf-8"))
if current_marker != marker_value:
raise RuntimeError("EXISTING_RELEASE_DIR_BINDING_CONFLICT")
else:
release_dir.parent.mkdir(parents=True, exist_ok=True)
temp_dir = Path(
tempfile.mkdtemp(prefix=release_dir.name + ".", dir=str(release_dir.parent))
)
try:
safe_extract(artifact, temp_dir)
atomic_json(
temp_dir / "DEPLOYMENT-BINDING.json",
marker_value,
mode=0o644,
)
os.rename(temp_dir, release_dir)
except Exception:
shutil.rmtree(temp_dir, ignore_errors=True)
raise
if (release_dir / "VERSION").read_text(encoding="utf-8").strip() != VERSION:
raise RuntimeError("RELEASE_VERSION_FILE_MISMATCH")
# Only replace our dedicated V2R1C7 link. Never touch previous versions.
current_link = p["current_link"]
if current_link.exists() and not current_link.is_symlink():
raise RuntimeError("CURRENT_RELEASE_PATH_NOT_SYMLINK")
if current_link.is_symlink():
existing = current_link.resolve()
if existing != release_dir.resolve():
raise RuntimeError("CURRENT_RELEASE_LINK_CONFLICT")
else:
current_link.parent.mkdir(parents=True, exist_ok=True)
new_link = current_link.with_name(current_link.name + ".new")
try:
new_link.unlink()
except FileNotFoundError:
pass
new_link.symlink_to(release_dir)
os.replace(new_link, current_link)
state_value = {
"version": VERSION,
"commit_oid": value["target_oid"],
"artifact_sha256": value["artifact_sha256"],
"deployment_state": "ACTIVE",
}
atomic_json(p["state_file"], state_value, mode=0o640, gid=gid)
env = (
f"RELEASE_VERSION={VERSION}\n"
f"TARGET_OID={value['target_oid']}\n"
f"ARTIFACT_SHA256={value['artifact_sha256']}\n"
"DEPLOYMENT_STATE=ACTIVE\n"
f"AIOS_V2R1C7_PORT={PORT}\n"
f"DEPLOYMENT_STATE_FILE={p['state_file']}\n"
"AIOS_GITOPS_WRITES_ENABLED=false\n"
)
atomic_text(p["env_file"], env, mode=0o640, gid=gid)
unit_source = (
release_dir
/ "deploy"
/ "aios-v2"
/ "systemd"
/ "aios-v2r1c7-production.service"
)
unit_text = unit_source.read_text(encoding="utf-8")
if "aios-v2r1c7-production.service" in unit_text:
# Description text is irrelevant; service identity is the destination.
pass
atomic_text(p["unit_file"], unit_text, mode=0o644)
verify = subprocess.run(
["systemd-analyze", "verify", str(p["unit_file"])],
check=False,
capture_output=True,
text=True,
timeout=30,
env={"PATH": os.environ.get("PATH", "")},
)
if verify.returncode != 0:
raise RuntimeError("PRODUCTION_SYSTEMD_VERIFY_FAILED")
systemctl("daemon-reload")
systemctl("enable", SERVICE)
systemctl("restart", SERVICE)
deadline = time.time() + args.health_timeout
result = {"status": "UNAVAILABLE"}
while time.time() < deadline:
result = health(f"http://127.0.0.1:{PORT}/health", value)
if result["status"] == "ACTIVE_VERIFIED":
break
if result["status"] == "CONFLICT":
break
time.sleep(1)
if result["status"] != "ACTIVE_VERIFIED":
raise RuntimeError("PRODUCTION_HEALTH_VERIFY_FAILED")
# Restart recovery is part of production acceptance, not just staging.
systemctl("restart", SERVICE)
deadline = time.time() + args.health_timeout
while time.time() < deadline:
result = health(f"http://127.0.0.1:{PORT}/health", value)
if result["status"] == "ACTIVE_VERIFIED":
break
if result["status"] == "CONFLICT":
break
time.sleep(1)
if result["status"] != "ACTIVE_VERIFIED":
raise RuntimeError("PRODUCTION_RESTART_RECOVERY_FAILED")
return result
def rollback(args: argparse.Namespace, value: Mapping[str, Any]) -> dict[str, Any]:
_, gid = identity()
p = paths(args, value)
systemctl("stop", SERVICE, check=False)
systemctl("disable", SERVICE, check=False)
state = {
"version": VERSION,
"commit_oid": value["target_oid"],
"artifact_sha256": value["artifact_sha256"],
"deployment_state": "ROLLED_BACK",
}
atomic_json(p["state_file"], state, mode=0o640, gid=gid)
return {
"status": "ROLLED_BACK",
"version": VERSION,
"target_oid": value["target_oid"],
"artifact_sha256": value["artifact_sha256"],
"deployment_state": "ROLLED_BACK",
"production_deployed": False,
}
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--release-root", type=Path, default=Path("/opt/aios/releases"))
p.add_argument("--current-link", type=Path, default=Path("/opt/aios/v2r1c7"))
p.add_argument("--env-file", type=Path, default=Path("/etc/aios/aios-v2r1c7.env"))
p.add_argument(
"--state-file",
type=Path,
default=Path("/var/lib/aios/v2r1c7/deployment-state.json"),
)
p.add_argument(
"--unit-file",
type=Path,
default=Path("/etc/systemd/system/aios-v2r1c7-production.service"),
)
p.add_argument("--health-timeout", type=int, default=30)
args = p.parse_args()
value = load_request()
operation = value.get("operation")
if operation == "probe":
result = probe(args, value)
elif operation == "apply":
result = install_release(args, value)
elif operation == "rollback":
result = rollback(args, value)
else:
raise ValueError("unsupported_operation")
print(json.dumps(result, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc:
print(
json.dumps(
{"status": "FAIL_CLOSED", "error": type(exc).__name__, "reason": str(exc)},
sort_keys=True,
)
)
raise SystemExit(2)