569 lines
25 KiB
Python
569 lines
25 KiB
Python
"""Governed V2R2C1 production release orchestration.
|
|
|
|
This module is deliberately scoped to the Owner-approved V2R2C1 binding. It
|
|
uses the existing TagPublisher and metadata adapters; it does not introduce a
|
|
second Git writer, a second durable job store, or an alternate baseline.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import fcntl
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import tarfile
|
|
import tempfile
|
|
import time
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Mapping
|
|
|
|
from gitea_tag_publication import (
|
|
GiteaTagClient,
|
|
GitTagBackend,
|
|
PublicationManualOwnerReviewRequired,
|
|
PublicationPending,
|
|
PublicationReconciliationRequired,
|
|
PublicationRequest,
|
|
TagPublisher,
|
|
)
|
|
|
|
|
|
VERSION = "V2R2C1"
|
|
TARGET_OID = "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
|
|
EVIDENCE_CLOSURE_OID = "a8b906d1bc24088c19647e98de6a679268f6d0a2"
|
|
APPROVAL_PLAN_ID = "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e"
|
|
REPOSITORY = "aios/aios"
|
|
SERVICE = "aios-v2r1c7-production.service"
|
|
PORT = 18927
|
|
OID_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
|
|
|
|
|
|
class ReleaseBlocked(RuntimeError):
|
|
"""A release gate failed and no unsafe recovery is permitted."""
|
|
|
|
|
|
class ManualOwnerReviewRequired(ReleaseBlocked):
|
|
pass
|
|
|
|
|
|
class ReconciliationRequired(ReleaseBlocked):
|
|
pass
|
|
|
|
|
|
def canonical(value: object) -> bytes:
|
|
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
|
|
|
|
|
def sha256_file(path: Path) -> str:
|
|
if path.is_symlink() or not path.is_file():
|
|
raise ReleaseBlocked("artifact must be a 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 atomic_json(path: Path, value: Mapping[str, Any], mode: int = 0o600) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
temporary = path.with_name(path.name + ".tmp")
|
|
temporary.write_bytes(canonical(dict(value)))
|
|
os.chmod(temporary, mode)
|
|
os.replace(temporary, path)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ReleaseBinding:
|
|
version: str = VERSION
|
|
target_oid: str = TARGET_OID
|
|
evidence_closure_oid: str = EVIDENCE_CLOSURE_OID
|
|
approval_plan_id: str = APPROVAL_PLAN_ID
|
|
repository: str = REPOSITORY
|
|
production_service: str = SERVICE
|
|
production_port: int = PORT
|
|
|
|
def validate(self) -> None:
|
|
if self.version != VERSION or self.repository != REPOSITORY:
|
|
raise ManualOwnerReviewRequired("release binding is outside V2R2C1 scope")
|
|
if not OID_RE.fullmatch(self.target_oid) or self.target_oid != TARGET_OID:
|
|
raise ManualOwnerReviewRequired("V2R2C1 target_oid mismatch")
|
|
if not OID_RE.fullmatch(self.evidence_closure_oid) or self.evidence_closure_oid != EVIDENCE_CLOSURE_OID:
|
|
raise ManualOwnerReviewRequired("V2R2C1 evidence closure mismatch")
|
|
if self.approval_plan_id != APPROVAL_PLAN_ID:
|
|
raise ManualOwnerReviewRequired("V2R2C1 approval plan mismatch")
|
|
if self.production_service != SERVICE or int(self.production_port) != PORT:
|
|
raise ManualOwnerReviewRequired("production service binding mismatch")
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"version": self.version,
|
|
"target_oid": self.target_oid,
|
|
"evidence_closure_oid": self.evidence_closure_oid,
|
|
"approval_plan_id": self.approval_plan_id,
|
|
"repository": self.repository,
|
|
"production_service": self.production_service,
|
|
"production_port": self.production_port,
|
|
}
|
|
|
|
|
|
class StateLock:
|
|
def __init__(self, path: Path):
|
|
self.path = path
|
|
self.handle = None
|
|
|
|
def __enter__(self) -> "StateLock":
|
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
self.handle = self.path.open("a+", encoding="utf-8")
|
|
fcntl.flock(self.handle.fileno(), fcntl.LOCK_EX)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
if self.handle is not None:
|
|
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
|
|
self.handle.close()
|
|
|
|
|
|
class ReleaseState:
|
|
def __init__(self, root: Path, binding: ReleaseBinding):
|
|
self.root = root
|
|
self.path = root / "release-state.json"
|
|
self.binding = binding
|
|
root.mkdir(parents=True, exist_ok=True)
|
|
if self.path.exists():
|
|
value = json.loads(self.path.read_text(encoding="utf-8"))
|
|
if value.get("binding") != binding.as_dict():
|
|
raise ManualOwnerReviewRequired("persisted release binding conflict")
|
|
self.value = value
|
|
else:
|
|
self.value = {
|
|
"schema": "aios.v2r2c1.governed-production-release-state.v1",
|
|
"binding": binding.as_dict(),
|
|
"state": "INITIAL",
|
|
"completed": {},
|
|
"history": [],
|
|
}
|
|
self.save()
|
|
|
|
def save(self) -> None:
|
|
atomic_json(self.path, self.value)
|
|
|
|
def record(self, state: str, step: str, result: Mapping[str, Any]) -> None:
|
|
safe = json.loads(json.dumps(dict(result)))
|
|
self.value["state"] = state
|
|
self.value.setdefault("completed", {})[step] = safe
|
|
self.value.setdefault("history", []).append(
|
|
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "step": step, "result": safe}
|
|
)
|
|
self.save()
|
|
|
|
def fail(self, state: str, reason: str) -> None:
|
|
self.value["state"] = state
|
|
self.value.setdefault("history", []).append(
|
|
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "reason": str(reason)[:500]}
|
|
)
|
|
self.save()
|
|
|
|
|
|
def _git(git_dir: Path, *args: str, check: bool = True) -> str:
|
|
completed = subprocess.run(
|
|
["git", f"--git-dir={git_dir}", *args],
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=45,
|
|
env={"PATH": os.environ.get("PATH", "")},
|
|
)
|
|
if check and completed.returncode != 0:
|
|
raise ReconciliationRequired(completed.stderr.strip() or "authoritative git query failed")
|
|
return completed.stdout.strip()
|
|
|
|
|
|
def _ancestor(git_dir: Path, ancestor: str, descendant: str) -> bool:
|
|
return subprocess.run(
|
|
["git", f"--git-dir={git_dir}", "merge-base", "--is-ancestor", ancestor, descendant],
|
|
check=False,
|
|
capture_output=True,
|
|
timeout=45,
|
|
env={"PATH": os.environ.get("PATH", "")},
|
|
).returncode == 0
|
|
|
|
|
|
def verify_lineage(git_dir: Path, binding: ReleaseBinding) -> dict[str, Any]:
|
|
binding.validate()
|
|
if _git(git_dir, "cat-file", "-t", binding.target_oid) != "commit":
|
|
raise ReleaseBlocked("approved target_oid is not a commit")
|
|
if _git(git_dir, "cat-file", "-t", binding.evidence_closure_oid) != "commit":
|
|
raise ReleaseBlocked("evidence closure is not a commit")
|
|
if not _ancestor(git_dir, binding.target_oid, binding.evidence_closure_oid):
|
|
raise ManualOwnerReviewRequired("approved target is not an ancestor of evidence closure")
|
|
return {
|
|
"target_oid": binding.target_oid,
|
|
"evidence_closure_oid": binding.evidence_closure_oid,
|
|
"target_is_ancestor_of_evidence": True,
|
|
"main_oid": _git(git_dir, "rev-parse", "refs/heads/main"),
|
|
}
|
|
|
|
|
|
def verify_owner_approval(value: Mapping[str, Any], binding: ReleaseBinding) -> dict[str, Any]:
|
|
approval = value.get("owner_approval")
|
|
if not isinstance(approval, Mapping):
|
|
raise ReleaseBlocked("OWNER_APPROVAL_RECEIPT_REQUIRED")
|
|
if approval.get("approved") is not True or approval.get("status") != "APPROVED":
|
|
raise ReleaseBlocked("OWNER_APPROVAL_NOT_ACCEPTED")
|
|
if approval.get("approved_by") != "aiman":
|
|
raise ManualOwnerReviewRequired("OWNER_APPROVAL_ACTOR_MISMATCH")
|
|
if approval.get("approval_source") != "explicit_owner_approval_in_chat":
|
|
raise ManualOwnerReviewRequired("OWNER_APPROVAL_SOURCE_MISMATCH")
|
|
for key, expected in binding.as_dict().items():
|
|
if key in {"repository", "production_service", "production_port"}:
|
|
continue
|
|
if approval.get(key) != expected:
|
|
raise ManualOwnerReviewRequired("OWNER_APPROVAL_BINDING_MISMATCH:" + key)
|
|
return dict(approval)
|
|
|
|
|
|
def _payload_file(git_dir: Path, oid: str, path: str) -> bytes:
|
|
completed = subprocess.run(
|
|
["git", f"--git-dir={git_dir}", "show", f"{oid}:{path}"],
|
|
check=False,
|
|
capture_output=True,
|
|
timeout=45,
|
|
env={"PATH": os.environ.get("PATH", "")},
|
|
)
|
|
if completed.returncode != 0:
|
|
raise ReleaseBlocked("required release source missing:" + path)
|
|
return completed.stdout
|
|
|
|
|
|
def _commit_epoch(git_dir: Path, oid: str) -> int:
|
|
return int(_git(git_dir, "show", "-s", "--format=%ct", oid))
|
|
|
|
|
|
def build_production_artifact(
|
|
git_dir: Path,
|
|
binding: ReleaseBinding,
|
|
output_root: Path,
|
|
) -> dict[str, Any]:
|
|
"""Build a deterministic executable V2R2C1 health artifact from target_oid."""
|
|
binding.validate()
|
|
verify_lineage(git_dir, binding)
|
|
epoch = _commit_epoch(git_dir, binding.target_oid)
|
|
output_root.mkdir(parents=True, exist_ok=True)
|
|
stem = f"aios-{VERSION}-{binding.target_oid[:12]}"
|
|
output = output_root / (stem + ".tar.zst")
|
|
with tempfile.TemporaryDirectory(prefix="v2r2c1-artifact-") as temp:
|
|
root = Path(temp) / "payload"
|
|
root.mkdir(parents=True)
|
|
source_paths = {
|
|
"active-runtime/app/deployment_state.py": "active-runtime/app/deployment_state.py",
|
|
"active-runtime/app/v2r1c6_privacy_tools.py": "active-runtime/app/v2r1c6_privacy_tools.py",
|
|
}
|
|
for destination, source in source_paths.items():
|
|
target = root / destination
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_bytes(_payload_file(git_dir, binding.target_oid, source))
|
|
generated: dict[str, str] = {
|
|
"VERSION": VERSION + "\n",
|
|
"active-runtime/app/v2r2c1_health_service.py": (
|
|
"from v2r1c6_privacy_tools import main\n\n"
|
|
"if __name__ == '__main__':\n raise SystemExit(main())\n"
|
|
),
|
|
"deploy/aios-v2/systemd/aios-v2r1c7-production.service": (
|
|
"[Unit]\nDescription=AIOS V2R2C1 governed health contract service\n"
|
|
"After=network.target\n\n[Service]\nType=simple\n"
|
|
"User=aios-v2r1c7\nGroup=aios-v2r1c7\n"
|
|
"WorkingDirectory=/opt/aios/v2r2c1\n"
|
|
"EnvironmentFile=/etc/aios/aios-v2r2c1.env\n"
|
|
"ExecStart=/usr/bin/python3 /opt/aios/v2r2c1/active-runtime/app/v2r2c1_health_service.py --serve --host 127.0.0.1 --port ${AIOS_V2R1C7_PORT}\n"
|
|
"Restart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nPrivateTmp=true\n"
|
|
"ProtectHome=true\nProtectSystem=strict\nProtectKernelLogs=true\n"
|
|
"ProtectKernelModules=true\nProtectKernelTunables=true\nProtectControlGroups=true\n"
|
|
"RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\nIPAddressDeny=any\n"
|
|
"IPAddressAllow=localhost\nUMask=0077\n\n[Install]\nWantedBy=multi-user.target\n"
|
|
),
|
|
"deploy/aios-v2/v2r2c1-runtime-config.json": json.dumps({
|
|
"schema": "aios.v2r2c1.production-runtime.v1",
|
|
"version": VERSION,
|
|
"target_oid": binding.target_oid,
|
|
"service": SERVICE,
|
|
"port": PORT,
|
|
"health_path": "/health",
|
|
"deployment_state_source": "environment_and_state_file",
|
|
}, sort_keys=True, separators=(",", ":")) + "\n",
|
|
"deploy/aios-v2/v2r2c1-rollback.json": json.dumps({
|
|
"schema": "aios.v2r2c1.rollback-metadata.v1",
|
|
"rollback_version": "V2R1C7",
|
|
"rollback_service": SERVICE,
|
|
"rollback_target_is_previous_production": True,
|
|
"automatic_rollback": False,
|
|
}, sort_keys=True, separators=(",", ":")) + "\n",
|
|
"scripts/probe_v2r2c1_health.py": (
|
|
"import json,sys,urllib.request\n"
|
|
"url=sys.argv[1] if len(sys.argv)>1 else 'http://127.0.0.1:18927/health'\n"
|
|
"with urllib.request.urlopen(url, timeout=5) as r: print(json.dumps(json.load(r), sort_keys=True))\n"
|
|
),
|
|
"scripts/rollback_v2r2c1_release.sh": "#!/bin/sh\nset -eu\nprintf '%s\\n' 'rollback requires governed owner-approved executor'\nexit 2\n",
|
|
"release-manifests/V2R2C1.json": json.dumps({
|
|
"schema": "aios.release-source.v2",
|
|
"version": VERSION,
|
|
"repository": REPOSITORY,
|
|
"target_oid": binding.target_oid,
|
|
"evidence_closure_oid": binding.evidence_closure_oid,
|
|
"production_deployed": False,
|
|
"deployment_state": "STAGING",
|
|
"artifact_kind": "executable-production-health",
|
|
}, sort_keys=True, separators=(",", ":")) + "\n",
|
|
}
|
|
for rel, text in generated.items():
|
|
target = root / rel
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_text(text, encoding="utf-8")
|
|
if rel.startswith("scripts/"):
|
|
target.chmod(0o755)
|
|
metadata = {
|
|
"schema": "aios.release-artifact-build.v3",
|
|
"version": VERSION,
|
|
"repository": REPOSITORY,
|
|
"source_head_oid": binding.target_oid,
|
|
"evidence_closure_oid": binding.evidence_closure_oid,
|
|
"source_date_epoch": epoch,
|
|
"clean_source": True,
|
|
"executable_payload": True,
|
|
"production_deployed": False,
|
|
"tag_created": False,
|
|
"baseline_changed": False,
|
|
"secrets_included": False,
|
|
}
|
|
(root / "BUILD-METADATA.json").write_bytes(canonical(metadata))
|
|
(root / "ROLLBACK-METADATA.json").write_bytes(canonical(json.loads((root / "deploy/aios-v2/v2r2c1-rollback.json").read_text(encoding="utf-8"))))
|
|
names = sorted("payload/" + str(path.relative_to(root)).replace(os.sep, "/") for path in root.rglob("*") if path.is_file())
|
|
(root / "ARTIFACT-MANIFEST.json").write_bytes(canonical({
|
|
"schema": "aios.release-artifact-manifest.v3",
|
|
"version": VERSION,
|
|
"source_head_oid": binding.target_oid,
|
|
"evidence_closure_oid": binding.evidence_closure_oid,
|
|
"repository": REPOSITORY,
|
|
"files": names + ["payload/ARTIFACT-MANIFEST.json"],
|
|
"deployable": True,
|
|
"executable_payload": True,
|
|
"production_deployed": False,
|
|
"tag_created": False,
|
|
"baseline_changed": False,
|
|
}))
|
|
tar_path = Path(temp) / "release.tar"
|
|
with tarfile.open(tar_path, "w") as archive:
|
|
for path in sorted(root.rglob("*")):
|
|
relative = str(path.relative_to(root)).replace(os.sep, "/")
|
|
info = archive.gettarinfo(str(path), arcname="payload/" + relative)
|
|
info.uid = info.gid = 0
|
|
info.uname = "root"
|
|
info.gname = "root"
|
|
info.mtime = epoch
|
|
info.mode = 0o755 if path.is_dir() or path.suffix == ".sh" else 0o644
|
|
if path.is_file():
|
|
with path.open("rb") as stream:
|
|
archive.addfile(info, stream)
|
|
else:
|
|
archive.addfile(info)
|
|
subprocess.run(["zstd", "-T1", "-19", "--no-progress", "-q", "-f", str(tar_path), "-o", str(output)], check=True, timeout=120)
|
|
digest = sha256_file(output)
|
|
receipt = {
|
|
"schema": "aios.release-artifact-receipt.v3",
|
|
"version": VERSION,
|
|
"repository": REPOSITORY,
|
|
"source_head_oid": binding.target_oid,
|
|
"evidence_closure_oid": binding.evidence_closure_oid,
|
|
"artifact": str(output),
|
|
"artifact_sha256": digest,
|
|
"artifact_size": output.stat().st_size,
|
|
"deployable": True,
|
|
"executable_payload": True,
|
|
"production_deployed": False,
|
|
"tag_created": False,
|
|
"baseline_changed": False,
|
|
}
|
|
atomic_json(output.with_suffix(output.suffix + ".receipt.json"), receipt, mode=0o640)
|
|
return receipt
|
|
|
|
|
|
class GovernedProductionReleaseExecutor:
|
|
def __init__(self, config: Any):
|
|
self.config = config
|
|
self.binding = ReleaseBinding()
|
|
self.binding.validate()
|
|
self.root = Path(config.state_root) / "v2r2c1-governed-production-release"
|
|
self.state = ReleaseState(self.root, self.binding)
|
|
workspace = self.root / "publication-workspace"
|
|
workspace.mkdir(parents=True, exist_ok=True)
|
|
self.publisher = TagPublisher(
|
|
workspace,
|
|
GitTagBackend(workspace, authoritative_repo=Path(config.authoritative_git_dir)),
|
|
GiteaTagClient(config.api_base, REPOSITORY, Path(config.token_path)),
|
|
config.remote_name,
|
|
state_root=self.root / "tag-publication",
|
|
)
|
|
|
|
def _request(self, request: Mapping[str, Any]) -> PublicationRequest:
|
|
binding = self.binding
|
|
for key, expected in binding.as_dict().items():
|
|
if key in request and request[key] != expected:
|
|
raise ManualOwnerReviewRequired("release request binding mismatch:" + key)
|
|
return PublicationRequest(binding.version, binding.target_oid, binding.evidence_closure_oid, binding.approval_plan_id)
|
|
|
|
def approval_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
if request.get("execute") is not False:
|
|
raise ReleaseBlocked("TAG_APPROVAL_REQUEST_MUST_BE_DRY_RUN")
|
|
owner = verify_owner_approval(request, self.binding)
|
|
with StateLock(self.root / "release.lock"):
|
|
current = self.state.value.get("approval")
|
|
approval_digest = hashlib.sha256(canonical(owner)).hexdigest()
|
|
record = {
|
|
"schema": "aios.v2r2c1.tag-approval-request.v1",
|
|
"binding": self.binding.as_dict(),
|
|
"owner_approval": owner,
|
|
"owner_approval_digest_sha256": approval_digest,
|
|
"execute": False,
|
|
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
}
|
|
if current and current != record:
|
|
raise ManualOwnerReviewRequired("persisted Owner approval differs")
|
|
self.state.value["approval"] = record
|
|
self.state.record("APPROVAL_REQUEST_RECORDED", "tag_approval_request", record)
|
|
return {"status": "RECORDED", "execute": False, "binding": self.binding.as_dict(), "owner_approval_digest_sha256": approval_digest}
|
|
|
|
def _require_approval(self) -> None:
|
|
approval = self.state.value.get("approval")
|
|
if not isinstance(approval, Mapping) or approval.get("binding") != self.binding.as_dict():
|
|
raise ReleaseBlocked("OWNER_APPROVAL_REQUEST_NOT_RECORDED")
|
|
|
|
def publish_tag(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
if request.get("execute") is not True:
|
|
raise ReleaseBlocked("TAG_PUBLICATION_REQUIRES_EXECUTE_TRUE")
|
|
self._require_approval()
|
|
publication = self._request(request)
|
|
with StateLock(self.root / "release.lock"):
|
|
try:
|
|
verified = self.publisher._verify_publication(publication, allow_push=True, previously_deployed=False)
|
|
except PublicationManualOwnerReviewRequired as exc:
|
|
self.state.fail("MANUAL_OWNER_REVIEW_REQUIRED", str(exc))
|
|
raise ManualOwnerReviewRequired(str(exc)) from exc
|
|
except PublicationPending as exc:
|
|
self.state.fail("REMOTE_PUSH_PENDING", str(exc))
|
|
raise ReconciliationRequired(str(exc)) from exc
|
|
except PublicationReconciliationRequired as exc:
|
|
self.state.fail("RECONCILIATION_REQUIRED", str(exc))
|
|
raise ReconciliationRequired(str(exc)) from exc
|
|
result = dict(verified)
|
|
self.state.record("TAG_VERIFIED", "tag", result)
|
|
return {"status": "REMOTE_VERIFIED", **result}
|
|
|
|
def tag_status(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
self._request(request)
|
|
return self.publisher.get_tag_publication_status(VERSION)
|
|
|
|
def build_artifact(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
self._require_approval()
|
|
self._request(request)
|
|
receipt = build_production_artifact(Path(self.config.authoritative_git_dir), self.binding, self.root / "artifacts")
|
|
self.state.record("ARTIFACT_VERIFIED", "production_artifact", receipt)
|
|
return {"status": "VERIFIED", **receipt}
|
|
|
|
def execute_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
self._require_approval()
|
|
publication = self._request(request)
|
|
tag_state = self.state.value.get("completed", {}).get("tag")
|
|
if not isinstance(tag_state, Mapping) or tag_state.get("state") not in {"GITEA_API_CONFIRMED", "REMOTE_VERIFIED"}:
|
|
raise ReleaseBlocked("REMOTE_TAG_PROOF_REQUIRED")
|
|
artifact = self.state.value.get("completed", {}).get("production_artifact")
|
|
if not isinstance(artifact, Mapping):
|
|
artifact = self.build_artifact(request)
|
|
value = {
|
|
**self.binding.as_dict(),
|
|
"tag_oid": tag_state.get("tag_oid") or tag_state.get("gitea_tag_oid"),
|
|
"artifact_path": artifact["artifact"],
|
|
"artifact_size": int(artifact["artifact_size"]),
|
|
"artifact_sha256": artifact["artifact_sha256"],
|
|
}
|
|
# The production adapter is privileged and is run through the root
|
|
# approved-release service boundary by the caller. This handler is
|
|
# still useful for a root-owned staging invocation and remains
|
|
# fail-closed when the adapter is not available.
|
|
import v2r2c1_production_adapter
|
|
runtime = v2r2c1_production_adapter.execute_request({"schema": "aios.production-executor-request.v1", "operation": "apply", **value})
|
|
if runtime.get("status") != "ACTIVE_VERIFIED":
|
|
self.state.fail("RECONCILIATION_REQUIRED", "production health did not verify")
|
|
raise ReconciliationRequired("production health did not verify")
|
|
self.state.record("PRODUCTION_VERIFIED", "production", runtime)
|
|
return {"status": "PRODUCTION_VERIFIED", "runtime": runtime, "artifact": artifact, "tag_oid": value["tag_oid"]}
|
|
|
|
def consistency(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
publication = self._request(request)
|
|
result = self.publisher.get_release_consistency(publication)
|
|
result["release_binding"] = self.binding.as_dict()
|
|
result["production_health"] = _health(self.config.health_url, self.binding, self.state.value.get("completed", {}).get("production_artifact", {}))
|
|
result["registry"] = _read_json(Path(self.config.registry_root) / f"{VERSION}.json")
|
|
result["baseline"] = _read_json(Path(self.config.baseline_path))
|
|
return result
|
|
|
|
def acceptance(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
result = self.consistency(request)
|
|
result["accepted"] = result.get("status") == "PASS" and result.get("production_health", {}).get("status") == "ACTIVE_VERIFIED"
|
|
self.state.record("ACCEPTED" if result["accepted"] else "RECONCILIATION_REQUIRED", "acceptance", result)
|
|
return result
|
|
|
|
|
|
def _read_json(path: Path) -> dict[str, Any]:
|
|
try:
|
|
value = json.loads(path.read_text(encoding="utf-8"))
|
|
except FileNotFoundError:
|
|
return {"status": "MISSING", "path": str(path)}
|
|
if not isinstance(value, dict):
|
|
return {"status": "INVALID", "path": str(path)}
|
|
return {"status": "PRESENT", "path": str(path), **value}
|
|
|
|
|
|
def _health(url: str, binding: ReleaseBinding, artifact: Mapping[str, Any]) -> dict[str, Any]:
|
|
import urllib.request
|
|
try:
|
|
with urllib.request.urlopen(url, timeout=5) as response:
|
|
body = json.loads(response.read().decode("utf-8"))
|
|
status = response.status
|
|
except Exception as exc:
|
|
return {"status": "UNAVAILABLE", "error": type(exc).__name__}
|
|
expected_artifact = artifact.get("artifact_sha256")
|
|
exact = (
|
|
status == 200
|
|
and body.get("version") == binding.version
|
|
and body.get("commit_oid") == binding.target_oid
|
|
and (not expected_artifact or body.get("artifact_sha256") == expected_artifact)
|
|
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 build_executor_config(config: Any) -> GovernedProductionReleaseExecutor:
|
|
return GovernedProductionReleaseExecutor(config)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("operation", choices=("build-artifact", "status"))
|
|
parser.add_argument("--git-dir", type=Path, default=Path("/data/aios-gitops/repo.git"))
|
|
parser.add_argument("--output-root", type=Path, default=Path("/var/lib/aios-gitops/approved-artifacts/V2R2C1"))
|
|
args = parser.parse_args()
|
|
binding = ReleaseBinding()
|
|
if args.operation == "build-artifact":
|
|
print(json.dumps(build_production_artifact(args.git_dir, binding, args.output_root), sort_keys=True))
|
|
else:
|
|
print(json.dumps({"binding": binding.as_dict(), "status": "READY"}, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|