Files
aios/scripts/build_agent_task_api.py
T

101 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Build deterministic immutable staging artifact for AIOS Agent Task API."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import tarfile
import tempfile
REQUIRED = (
"active-runtime/app/aios_agent_task_protocol.py",
"active-runtime/app/aios_agent_task_api.py",
"docs/gitops/openapi-agent-task-v1.yaml",
)
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-root", required=True, type=Path)
parser.add_argument("--output-root", required=True, type=Path)
parser.add_argument("--source-oid", required=True)
args = parser.parse_args()
oid = args.source_oid.strip().lower()
if len(oid) != 40 or any(ch not in "0123456789abcdef" for ch in oid):
raise SystemExit("source oid must be exact 40 hex")
source = args.source_root.resolve(strict=True)
output = args.output_root.resolve()
for relative in REQUIRED:
if not (source / relative).is_file():
raise SystemExit("required source missing: " + relative)
output.mkdir(parents=True, exist_ok=True)
archive = output / f"aios-agent-task-api-{oid}.tar.gz"
manifest = {
"schema": "aios.agent-task-artifact.v1",
"source_oid": oid,
"repository_identity": "aios/aios",
"deployment_state": "STAGING",
"runtime_mode": "IMMUTABLE_STAGING",
"entrypoint": "app/aios_agent_task_api.py",
"listen": "127.0.0.1:18940",
"plaintext_credentials_in_artifact": False,
"included_paths": list(REQUIRED),
}
with tempfile.TemporaryDirectory(prefix="agent-task-build-", dir=str(output)) as temp:
stage = Path(temp) / "runtime"
for relative in REQUIRED:
src = source / relative
if relative.startswith("active-runtime/"):
dest = stage / Path(relative).relative_to("active-runtime")
else:
dest = stage / relative
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(src.read_bytes())
(stage / "manifest.json").write_text(
json.dumps(manifest, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
with tarfile.open(archive, "w:gz", format=tarfile.PAX_FORMAT) as tar:
for path in sorted(stage.rglob("*")):
arcname = str(path.relative_to(stage.parent)).replace("\\", "/")
info = tar.gettarinfo(str(path), arcname=arcname)
info.mtime = 0
info.uid = info.gid = 0
info.uname = info.gname = "root"
if path.is_file():
with path.open("rb") as fh:
tar.addfile(info, fh)
else:
tar.addfile(info)
receipt = {
**manifest,
"artifact_path": str(archive),
"artifact_sha256": sha256(archive),
}
receipt_path = archive.with_suffix(archive.suffix + ".receipt.json")
receipt_path.write_text(
json.dumps(receipt, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
print(json.dumps(receipt, sort_keys=True, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())