autonomy-smoke-tests V2R2C2 release-runtime binding smoke and offline package preflight PASS
autonomy-unit-tests 782 passed, 3 skipped, 154 subtests passed on source branch
git-diff-check governed source diff-check PASS against merged main f1695ce
py-compile all tracked Python source compile PASS
reconciliation-contract completed-replay reconciliation contract preserved; 11 focused tests PASS
secret-scan narrow source and deployable payload secret scan PASS; no credentials included
signer-validator-contract trusted release plan/adapter contract 48 tests PASS; formal Owner signature not asserted here
campaign-approval-gate V2R2C2 candidate binding contract PASS; production approval remains formal-envelope gated
release-state-machine V2R2C2 plan-driven release state contract PASS; no production mutation performed
118 lines
9.0 KiB
Python
118 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Plan-bound production adapter for releases newer than the legacy baseline.
|
|
|
|
All identity comes from the already verified executor request. The adapter has
|
|
no Git/ref/tag authority and refuses a missing candidate/release-plan binding.
|
|
"""
|
|
from __future__ import annotations
|
|
import hashlib, json, os, shutil, subprocess, sys, tempfile, urllib.error, urllib.request
|
|
from pathlib import Path
|
|
from typing import Any, Mapping
|
|
|
|
class AdapterBlocked(RuntimeError): pass
|
|
|
|
ALLOWED_SERVICE_PORTS = {"aios-v2r2c2-production.service": 18928}
|
|
|
|
def _safe(value: str) -> str:
|
|
if not value or any(ch not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-" for ch in value):
|
|
raise AdapterBlocked("invalid_release_identity")
|
|
return value
|
|
|
|
def _run(*args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
|
|
result = subprocess.run(args, check=False, capture_output=True, text=True, timeout=60, env={"PATH": os.environ.get("PATH", "")})
|
|
if check and result.returncode != 0: raise AdapterBlocked(result.stderr.strip() or "system_command_failed")
|
|
return result
|
|
|
|
def _sha(path: Path) -> str:
|
|
if path.is_symlink() or not path.is_file(): raise AdapterBlocked("artifact_must_be_regular_file")
|
|
digest=hashlib.sha256()
|
|
with path.open("rb") as stream:
|
|
for block in iter(lambda: stream.read(1024*1024), b""): digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
def _binding(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
required=("version","target_oid","artifact_sha256","artifact_path","artifact_size","production_service","production_port","candidate_id","release_plan_sha256")
|
|
if any(key not in value for key in required): raise AdapterBlocked("trusted_binding_incomplete")
|
|
if str(value["version"]).startswith("V2R1") or not value["candidate_id"] or not value["release_plan_sha256"]: raise AdapterBlocked("legacy_or_unbound_release_rejected")
|
|
service = str(value["production_service"])
|
|
port = int(value["production_port"])
|
|
if ALLOWED_SERVICE_PORTS.get(service) != port:
|
|
raise AdapterBlocked("production_adapter_policy_rejected")
|
|
return {key:value[key] for key in required}
|
|
|
|
def _paths(binding: Mapping[str, Any]) -> dict[str, Path]:
|
|
version=_safe(str(binding["version"])); service=_safe(str(binding["production_service"]))
|
|
root=Path(os.environ.get("AIOS_RELEASE_ROOT", "/opt/aios/releases"))
|
|
slug=version.lower()
|
|
return {"release_dir":root/(version+"-"+str(binding["target_oid"])[:12]+"-"+str(binding["artifact_sha256"])[:12]),"current_link":Path("/opt/aios")/slug,"env_file":Path("/etc/aios")/(slug+".env"),"state_file":Path("/var/lib/aios")/slug/"deployment-state.json","unit_file":Path("/etc/systemd/system")/(service+".service" if not service.endswith(".service") else service),"health_url":f"http://127.0.0.1:{int(binding['production_port'])}/health"}
|
|
|
|
def _health(url: str, binding: Mapping[str, Any]) -> dict[str, Any]:
|
|
try:
|
|
with urllib.request.urlopen(url, timeout=5) as response: body=json.loads(response.read().decode()); status=response.status
|
|
except (OSError, ValueError, urllib.error.URLError): return {"status":"UNAVAILABLE"}
|
|
exact=(status==200 and body.get("version")==binding["version"] and body.get("commit_oid")==binding["target_oid"] and body.get("artifact_sha256")==binding["artifact_sha256"] and body.get("deployment_state")=="ACTIVE" and body.get("production_deployed") is True)
|
|
return {"status":"ACTIVE_VERIFIED" if exact else "CONFLICT", "http_status":status, **body}
|
|
|
|
def _extract(artifact: Path, destination: Path) -> None:
|
|
import tarfile
|
|
with tarfile.open(artifact, "r:*") as archive:
|
|
for member in archive.getmembers():
|
|
name=Path(member.name)
|
|
if name.is_absolute() or ".." in name.parts or member.issym() or member.islnk() or not member.isfile() or not member.name.startswith("payload/"):
|
|
raise AdapterBlocked("unsafe_artifact_member")
|
|
rel=name.relative_to("payload"); target=destination/rel; target.parent.mkdir(parents=True,exist_ok=True)
|
|
source=archive.extractfile(member)
|
|
if source is None: raise AdapterBlocked("artifact_member_unreadable")
|
|
target.write_bytes(source.read()); os.chmod(target,0o755 if rel.parts and rel.parts[0]=="scripts" else 0o644)
|
|
|
|
def probe(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
binding=_binding(value); p=_paths(binding); active=_run("systemctl","is-active","--quiet",str(binding["production_service"]),check=False).returncode==0
|
|
return _health(str(p["health_url"]),binding) if active else {"status":"NOT_APPLIED","version":binding["version"],"target_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"production_deployed":False,"deployment_state":"ABSENT"}
|
|
|
|
def apply(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
binding=_binding(value); p=_paths(binding); p["release_dir"].parent.mkdir(parents=True, exist_ok=True); artifact=Path(str(binding["artifact_path"]))
|
|
if artifact.stat().st_size != int(binding["artifact_size"]) or _sha(artifact)!=binding["artifact_sha256"]: raise AdapterBlocked("artifact_binding_mismatch")
|
|
marker={"schema":"aios.trusted-runtime-release-binding.v1",**binding}
|
|
marker_path=p["release_dir"]/"DEPLOYMENT-BINDING.json"
|
|
if p["release_dir"].exists():
|
|
if not marker_path.is_file() or json.loads(marker_path.read_text())!=marker: raise AdapterBlocked("existing_release_binding_conflict")
|
|
else:
|
|
temp=Path(tempfile.mkdtemp(prefix=p["release_dir"].name+".",dir=str(p["release_dir"].parent)))
|
|
try:
|
|
_extract(artifact,temp); (temp/"DEPLOYMENT-BINDING.json").write_text(json.dumps(marker,sort_keys=True,separators=(",",":"))+"\n"); os.rename(temp,p["release_dir"])
|
|
except Exception:
|
|
shutil.rmtree(temp,ignore_errors=True); raise
|
|
version_file=p["release_dir"] / "VERSION"
|
|
if not version_file.is_file() or version_file.read_text().strip()!=binding["version"]: raise AdapterBlocked("release_version_mismatch")
|
|
unit_source=p["release_dir"] / "deploy" / "aios-v2" / "systemd" / str(binding["production_service"])
|
|
if not unit_source.is_file(): raise AdapterBlocked("release_unit_missing")
|
|
p["unit_file"].parent.mkdir(parents=True,exist_ok=True); shutil.copyfile(unit_source,p["unit_file"])
|
|
p["current_link"].parent.mkdir(parents=True,exist_ok=True)
|
|
if p["current_link"].exists() and not p["current_link"].is_symlink(): raise AdapterBlocked("current_release_path_conflict")
|
|
if not p["current_link"].is_symlink(): p["current_link"].symlink_to(p["release_dir"])
|
|
p["state_file"].parent.mkdir(parents=True,exist_ok=True); p["state_file"].write_text(json.dumps({"version":binding["version"],"commit_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"candidate_id":binding["candidate_id"],"release_plan_sha256":binding["release_plan_sha256"],"deployment_state":"ACTIVE","production_deployed":True},sort_keys=True)+"\n")
|
|
env=(f"RELEASE_VERSION={binding['version']}\n" f"TARGET_OID={binding['target_oid']}\n"
|
|
f"ARTIFACT_SHA256={binding['artifact_sha256']}\n" f"CANDIDATE_ID={binding['candidate_id']}\n"
|
|
f"RELEASE_PLAN_SHA256={binding['release_plan_sha256']}\n" f"DEPLOYMENT_STATE=ACTIVE\n"
|
|
f"DEPLOYMENT_STATE_FILE={p['state_file']}\n" f"AIOS_V2R2C2_PORT={binding['production_port']}\n"
|
|
"AIOS_GITOPS_WRITES_ENABLED=false\n")
|
|
p["env_file"].parent.mkdir(parents=True,exist_ok=True); p["env_file"].write_text(env)
|
|
_run("systemctl","daemon-reload"); _run("systemctl","enable",str(binding["production_service"])); _run("systemctl","restart",str(binding["production_service"]))
|
|
result=_health(str(p["health_url"]),binding)
|
|
if result["status"]!="ACTIVE_VERIFIED": raise AdapterBlocked("production_health_binding_mismatch")
|
|
return result
|
|
|
|
def rollback(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
binding=_binding(value); _run("systemctl","stop",str(binding["production_service"]),check=False); _run("systemctl","disable",str(binding["production_service"]),check=False)
|
|
p=_paths(binding); p["state_file"].parent.mkdir(parents=True,exist_ok=True); p["state_file"].write_text(json.dumps({"version":binding["version"],"commit_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"deployment_state":"ROLLED_BACK","production_deployed":False},sort_keys=True)+"\n")
|
|
return {"status":"ROLLED_BACK","version":binding["version"],"target_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"production_deployed":False,"deployment_state":"ROLLED_BACK"}
|
|
|
|
def main() -> int:
|
|
value=json.loads(sys.stdin.read()); operation=value.get("operation")
|
|
result=probe(value) if operation=="probe" else apply(value) if operation=="apply" else rollback(value) if operation=="rollback" else (_ for _ in ()).throw(AdapterBlocked("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)
|