Compare commits

..
15 changed files with 4744 additions and 1313 deletions
@@ -1,748 +0,0 @@
#!/usr/bin/env python3
"""Server-local governed immutable Host seed executor.
This executor is deliberately not a general-purpose privileged command runner.
It polls one fixed Git ref/path, verifies a high-risk governed-task binding as
the durable authorization evidence, materializes one exact candidate from the
authoritative Git object database plus a declared patch/manifest, and atomically
publishes the immutable release tree.
It never starts/restarts services, never creates the continuation Task/Job,
never writes Git refs, and never switches Production.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import argparse
import fcntl
import hashlib
import json
import os
import pwd
from pathlib import Path, PurePosixPath
import re
import shutil
import stat
import subprocess
import tempfile
from typing import Any, Mapping
REQUEST_SCHEMA = "aios.control-plane-host-seed-request.v1"
MANIFEST_SCHEMAS = {
"aios.control-plane-immutable-candidate.v5",
"aios.control-plane-immutable-candidate.v6",
}
OPERATION = "HOST_SEED_IMMUTABLE_CANDIDATE"
REPOSITORY = "aios/aios"
REQUEST_REF = "refs/heads/agent/host-seed-authorization-requests"
REQUEST_PATH = "control-plane/host-seed/request.json"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
TASK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
RELEASE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{3,191}$")
CANDIDATE_RE = re.compile(r"^V[0-9]+$")
MAX_REQUEST_BYTES = 128 * 1024
MAX_MANIFEST_BYTES = 4 * 1024 * 1024
MAX_PATCH_BYTES = 2 * 1024 * 1024
MAX_MACHINE_GOAL_BYTES = 512 * 1024
MAX_BOOTSTRAP_BYTES = 512 * 1024
class HostSeedError(RuntimeError):
pass
def canonical(value: object) -> bytes:
return (json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
def sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def canonical_compact(value: object) -> bytes:
return json.dumps(
value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
def _git(git_dir: Path, *args: str, input_bytes: bytes | None = None) -> bytes:
env = {"PATH": os.environ.get("PATH", "")}
proc = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
input=input_bytes,
capture_output=True,
timeout=120,
env=env,
)
if proc.returncode:
raise HostSeedError("GIT_" + (args[0] if args else "COMMAND").upper() + "_FAILED")
return proc.stdout
def _git_text(git_dir: Path, *args: str) -> str:
return _git(git_dir, *args).decode("utf-8", "strict").strip()
def _safe_repo_path(value: object, *, field: str) -> str:
if not isinstance(value, str) or not value or len(value) > 512:
raise HostSeedError(field + "_INVALID")
p = PurePosixPath(value)
if p.is_absolute() or any(part in {"", ".", ".."} for part in p.parts):
raise HostSeedError(field + "_INVALID")
normalized = p.as_posix()
if normalized.startswith(".git/") or normalized == ".git":
raise HostSeedError(field + "_INVALID")
return normalized
def _read_git_blob(git_dir: Path, oid: str, path: str, *, maximum: int) -> bytes:
if not OID_RE.fullmatch(oid):
raise HostSeedError("PAYLOAD_COMMIT_INVALID")
raw = _git(git_dir, "show", f"{oid}:{path}")
if len(raw) <= 0 or len(raw) > maximum:
raise HostSeedError("GIT_BLOB_SIZE_INVALID")
return raw
def _parse_json(raw: bytes, *, code: str) -> dict[str, Any]:
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise HostSeedError(code) from exc
if not isinstance(value, dict):
raise HostSeedError(code)
return value
def _parse_time(value: object, *, field: str) -> datetime:
if not isinstance(value, str) or not value:
raise HostSeedError(field + "_INVALID")
text = value[:-1] + "+00:00" if value.endswith("Z") else value
try:
parsed = datetime.fromisoformat(text)
except ValueError as exc:
raise HostSeedError(field + "_INVALID") from exc
if parsed.tzinfo is None:
raise HostSeedError(field + "_INVALID")
return parsed.astimezone(timezone.utc)
def _regular_file_bytes(
path: Path, *, maximum: int, expected_uid: int | None = None
) -> bytes:
if path.parent.is_symlink():
raise HostSeedError("AUTHORIZATION_BINDING_PARENT_SYMLINK_REJECTED")
flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
try:
fd = os.open(path, flags)
except OSError as exc:
raise HostSeedError("AUTHORIZATION_BINDING_UNAVAILABLE") from exc
try:
info = os.fstat(fd)
if not stat.S_ISREG(info.st_mode) or info.st_size <= 0 or info.st_size > maximum:
raise HostSeedError("AUTHORIZATION_BINDING_INVALID")
if info.st_mode & 0o022:
raise HostSeedError("AUTHORIZATION_BINDING_WRITABLE_BY_GROUP_OR_OTHER")
if expected_uid is not None and info.st_uid != expected_uid:
raise HostSeedError("AUTHORIZATION_BINDING_OWNER_MISMATCH")
raw = os.read(fd, maximum + 1)
if len(raw) > maximum:
raise HostSeedError("AUTHORIZATION_BINDING_INVALID")
return raw
finally:
os.close(fd)
def _atomic_json(path: Path, value: Mapping[str, Any], *, mode: int = 0o600) -> None:
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
if path.parent.is_symlink() or path.is_symlink():
raise HostSeedError("STATE_SYMLINK_REJECTED")
fd, temporary = tempfile.mkstemp(prefix=".tmp-", dir=path.parent)
try:
os.fchmod(fd, mode)
with os.fdopen(fd, "wb", closefd=True) as stream:
stream.write(canonical(value))
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
os.chmod(path, mode)
parent_fd = os.open(path.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(parent_fd)
finally:
os.close(parent_fd)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
@dataclass(frozen=True)
class Intent:
candidate: str
release_id: str
base_oid: str
payload_commit: str
patch_path: str
manifest_path: str
machine_goal_path: str
seed_bootstrap_path: str
artifact_sha256: str
manifest_sha256: str
machine_goal_sha256: str
release_tree_sha256: str
seed_bootstrap_sha256: str
target_host: str
def as_dict(self) -> dict[str, Any]:
return {
"candidate": self.candidate,
"release_id": self.release_id,
"base_oid": self.base_oid,
"payload_commit": self.payload_commit,
"patch_path": self.patch_path,
"manifest_path": self.manifest_path,
"machine_goal_path": self.machine_goal_path,
"seed_bootstrap_path": self.seed_bootstrap_path,
"artifact_sha256": self.artifact_sha256,
"manifest_sha256": self.manifest_sha256,
"machine_goal_sha256": self.machine_goal_sha256,
"release_tree_sha256": self.release_tree_sha256,
"seed_bootstrap_sha256": self.seed_bootstrap_sha256,
"target_host": self.target_host,
}
@property
def digest(self) -> str:
return sha256(canonical(self.as_dict()))
@dataclass(frozen=True)
class Request:
intent: Intent
authorization_task_id: str
authorization_binding_sha256: str
authorization_sha256: str
expires_at: datetime
@classmethod
def load(cls, raw: Mapping[str, Any], *, host_identity: str, now: datetime) -> "Request":
if set(raw) != {"schema", "operation", "repository", "intent", "authorization"}:
raise HostSeedError("REQUEST_FIELDS_INVALID")
if raw.get("schema") != REQUEST_SCHEMA or raw.get("operation") != OPERATION:
raise HostSeedError("REQUEST_CONTRACT_INVALID")
if raw.get("repository") != REPOSITORY:
raise HostSeedError("REQUEST_REPOSITORY_MISMATCH")
intent_raw = raw.get("intent")
if not isinstance(intent_raw, Mapping) or set(intent_raw) != {
"candidate", "release_id", "base_oid", "payload_commit", "patch_path",
"manifest_path", "machine_goal_path", "seed_bootstrap_path",
"artifact_sha256", "manifest_sha256", "machine_goal_sha256",
"release_tree_sha256", "seed_bootstrap_sha256", "target_host",
}:
raise HostSeedError("INTENT_FIELDS_INVALID")
candidate = str(intent_raw.get("candidate") or "")
release_id = str(intent_raw.get("release_id") or "")
base_oid = str(intent_raw.get("base_oid") or "")
payload_commit = str(intent_raw.get("payload_commit") or "")
artifact_sha = str(intent_raw.get("artifact_sha256") or "")
manifest_sha = str(intent_raw.get("manifest_sha256") or "")
machine_goal_sha = str(intent_raw.get("machine_goal_sha256") or "")
release_tree_sha = str(intent_raw.get("release_tree_sha256") or "")
seed_bootstrap_sha = str(intent_raw.get("seed_bootstrap_sha256") or "")
target_host = str(intent_raw.get("target_host") or "")
if not CANDIDATE_RE.fullmatch(candidate):
raise HostSeedError("CANDIDATE_INVALID")
if not RELEASE_RE.fullmatch(release_id):
raise HostSeedError("RELEASE_ID_INVALID")
if not OID_RE.fullmatch(base_oid) or not OID_RE.fullmatch(payload_commit):
raise HostSeedError("INTENT_OID_INVALID")
if not all(SHA_RE.fullmatch(x) for x in (
artifact_sha, manifest_sha, machine_goal_sha, release_tree_sha, seed_bootstrap_sha
)):
raise HostSeedError("INTENT_SHA_INVALID")
if target_host != host_identity:
raise HostSeedError("TARGET_HOST_MISMATCH")
intent = Intent(
candidate=candidate,
release_id=release_id,
base_oid=base_oid,
payload_commit=payload_commit,
patch_path=_safe_repo_path(intent_raw.get("patch_path"), field="PATCH_PATH"),
manifest_path=_safe_repo_path(intent_raw.get("manifest_path"), field="MANIFEST_PATH"),
machine_goal_path=_safe_repo_path(intent_raw.get("machine_goal_path"), field="MACHINE_GOAL_PATH"),
seed_bootstrap_path=_safe_repo_path(intent_raw.get("seed_bootstrap_path"), field="SEED_BOOTSTRAP_PATH"),
artifact_sha256=artifact_sha,
manifest_sha256=manifest_sha,
machine_goal_sha256=machine_goal_sha,
release_tree_sha256=release_tree_sha,
seed_bootstrap_sha256=seed_bootstrap_sha,
target_host=target_host,
)
auth = raw.get("authorization")
if not isinstance(auth, Mapping) or set(auth) != {
"task_id", "binding_sha256", "expires_at", "intent_sha256",
"authorization_sha256",
}:
raise HostSeedError("AUTHORIZATION_FIELDS_INVALID")
task_id = str(auth.get("task_id") or "")
binding_sha = str(auth.get("binding_sha256") or "")
authorization_sha = str(auth.get("authorization_sha256") or "")
if (
not TASK_RE.fullmatch(task_id)
or not SHA_RE.fullmatch(binding_sha)
or not SHA_RE.fullmatch(authorization_sha)
):
raise HostSeedError("AUTHORIZATION_IDENTITY_INVALID")
if auth.get("intent_sha256") != intent.digest:
raise HostSeedError("AUTHORIZATION_INTENT_MISMATCH")
expires_text = str(auth.get("expires_at") or "")
expires_at = _parse_time(expires_text, field="AUTHORIZATION_EXPIRES_AT")
expected_authorization_sha = sha256(canonical({
"task_id": task_id,
"intent_sha256": intent.digest,
"expires_at": expires_text,
}))
if authorization_sha != expected_authorization_sha:
raise HostSeedError("AUTHORIZATION_DIGEST_MISMATCH")
if now >= expires_at:
raise HostSeedError("AUTHORIZATION_EXPIRED")
if (expires_at - now).total_seconds() > 3600:
raise HostSeedError("AUTHORIZATION_WINDOW_TOO_LARGE")
return cls(
intent=intent,
authorization_task_id=task_id,
authorization_binding_sha256=binding_sha,
authorization_sha256=authorization_sha,
expires_at=expires_at,
)
@dataclass(frozen=True)
class Config:
git_dir: Path
task_binding_root: Path
state_root: Path
release_root: Path
host_identity: str
task_binding_owner: str | None = "aios-bridge-staging"
request_ref: str = REQUEST_REF
request_path: str = REQUEST_PATH
def _load_task_binding(config: Config, request: Request) -> dict[str, Any]:
safe = re.sub(r"[^A-Za-z0-9_.-]+", "-", request.authorization_task_id).strip("-")[:120] or "task"
path = config.task_binding_root / f"{safe}.json"
expected_uid = None
if config.task_binding_owner:
try:
expected_uid = pwd.getpwnam(config.task_binding_owner).pw_uid
except KeyError as exc:
raise HostSeedError("AUTHORIZATION_BINDING_OWNER_UNKNOWN") from exc
raw = _regular_file_bytes(path, maximum=256 * 1024, expected_uid=expected_uid)
if sha256(raw) != request.authorization_binding_sha256:
raise HostSeedError("AUTHORIZATION_BINDING_HASH_MISMATCH")
binding = _parse_json(raw, code="AUTHORIZATION_BINDING_JSON_INVALID")
required = {
"task_id", "repository", "base_oid", "target_branch", "risk",
"description", "idempotency_key", "expected_branch_oid",
"branch_mode", "generation", "job_id",
}
if set(binding) != required:
raise HostSeedError("AUTHORIZATION_BINDING_FIELDS_INVALID")
if binding.get("task_id") != request.authorization_task_id:
raise HostSeedError("AUTHORIZATION_TASK_MISMATCH")
if binding.get("repository") != REPOSITORY:
raise HostSeedError("AUTHORIZATION_REPOSITORY_MISMATCH")
if binding.get("risk") != "high":
raise HostSeedError("AUTHORIZATION_RISK_MISMATCH")
if binding.get("description") != "HOST_SEED_AUTHORIZATION:" + request.authorization_sha256:
raise HostSeedError("AUTHORIZATION_DESCRIPTION_MISMATCH")
if not str(binding.get("target_branch") or "").startswith("agent/host-seed-authorization-"):
raise HostSeedError("AUTHORIZATION_BRANCH_MISMATCH")
if not isinstance(binding.get("generation"), int) or binding["generation"] < 1:
raise HostSeedError("AUTHORIZATION_GENERATION_INVALID")
return binding
def _load_manifest(config: Config, request: Request) -> dict[str, Any]:
raw = _read_git_blob(
config.git_dir, request.intent.payload_commit, request.intent.manifest_path,
maximum=MAX_MANIFEST_BYTES,
)
if sha256(raw) != request.intent.manifest_sha256:
raise HostSeedError("MANIFEST_HASH_MISMATCH")
manifest = _parse_json(raw, code="MANIFEST_JSON_INVALID")
if manifest.get("schema") not in MANIFEST_SCHEMAS:
raise HostSeedError("MANIFEST_SCHEMA_REJECTED")
if manifest.get("candidate") != request.intent.candidate:
raise HostSeedError("MANIFEST_CANDIDATE_MISMATCH")
if manifest.get("repository") != REPOSITORY:
raise HostSeedError("MANIFEST_REPOSITORY_MISMATCH")
if manifest.get("base_oid") != request.intent.base_oid:
raise HostSeedError("MANIFEST_BASE_MISMATCH")
if manifest.get("release_id") != request.intent.release_id:
raise HostSeedError("MANIFEST_RELEASE_ID_MISMATCH")
members = manifest.get("package_members")
changes = manifest.get("changed_files")
if not isinstance(members, Mapping) or not isinstance(changes, list):
raise HostSeedError("MANIFEST_MEMBERS_INVALID")
return manifest
def _load_identity_evidence(config: Config, request: Request) -> dict[str, Any]:
goal_raw = _read_git_blob(
config.git_dir, request.intent.payload_commit, request.intent.machine_goal_path,
maximum=MAX_MACHINE_GOAL_BYTES,
)
if sha256(goal_raw) != request.intent.machine_goal_sha256:
raise HostSeedError("MACHINE_GOAL_HASH_MISMATCH")
goal = _parse_json(goal_raw, code="MACHINE_GOAL_JSON_INVALID")
required_pairs = {
"candidate": request.intent.candidate,
"repository": REPOSITORY,
"base_oid": request.intent.base_oid,
"release_id": request.intent.release_id,
"artifact_sha256": request.intent.artifact_sha256,
"manifest_sha256": request.intent.manifest_sha256,
"release_tree_sha256": request.intent.release_tree_sha256,
"seed_bootstrap_sha256": request.intent.seed_bootstrap_sha256,
}
for key, expected in required_pairs.items():
if goal.get(key) != expected:
raise HostSeedError("MACHINE_GOAL_BINDING_MISMATCH:" + key)
if goal.get("host_seed_authorized") is not False:
raise HostSeedError("MACHINE_GOAL_HOST_SEED_FLAG_INVALID")
bootstrap = _read_git_blob(
config.git_dir, request.intent.payload_commit, request.intent.seed_bootstrap_path,
maximum=MAX_BOOTSTRAP_BYTES,
)
if sha256(bootstrap) != request.intent.seed_bootstrap_sha256:
raise HostSeedError("SEED_BOOTSTRAP_HASH_MISMATCH")
return goal
def _load_patch(config: Config, request: Request) -> bytes:
patch = _read_git_blob(
config.git_dir, request.intent.payload_commit, request.intent.patch_path,
maximum=MAX_PATCH_BYTES,
)
if b"\x00" in patch:
raise HostSeedError("PATCH_BINARY_NUL_REJECTED")
return patch
def _manifest_release_map(manifest: Mapping[str, Any]) -> dict[str, str]:
members = manifest["package_members"]
result: dict[str, str] = {}
prefix = "payload/repo/after/"
for name, digest in members.items():
if not isinstance(name, str) or not name.startswith(prefix) or not isinstance(digest, str) or not SHA_RE.fullmatch(digest):
continue
rel = _safe_repo_path(name[len(prefix):], field="RELEASE_MEMBER")
if rel in result:
raise HostSeedError("DUPLICATE_RELEASE_MEMBER")
result[rel] = digest
declared_count = manifest.get("release_member_count")
if not isinstance(declared_count, int) or declared_count != len(result):
raise HostSeedError("RELEASE_MEMBER_COUNT_MISMATCH")
return result
def _expected_changes(manifest: Mapping[str, Any]) -> dict[str, tuple[str, str]]:
result: dict[str, tuple[str, str]] = {}
for entry in manifest["changed_files"]:
if not isinstance(entry, Mapping):
raise HostSeedError("CHANGED_FILE_ENTRY_INVALID")
rel = _safe_repo_path(entry.get("path"), field="CHANGED_PATH")
digest = str(entry.get("after_sha256") or "")
status = str(entry.get("status") or "")
if not SHA_RE.fullmatch(digest) or status not in {"modified", "added"} or rel in result:
raise HostSeedError("CHANGED_FILE_ENTRY_INVALID")
result[rel] = (digest, status)
if manifest.get("changed_file_count") != len(result):
raise HostSeedError("CHANGED_FILE_COUNT_MISMATCH")
return result
def _regular_member(root: Path, relative: str) -> Path:
current = root
for part in PurePosixPath(relative).parts:
current = current / part
try:
info = current.lstat()
except FileNotFoundError as exc:
raise HostSeedError("RELEASE_MEMBER_MISSING:" + relative) from exc
if stat.S_ISLNK(info.st_mode):
raise HostSeedError("RELEASE_MEMBER_SYMLINK_REJECTED:" + relative)
if not current.is_file():
raise HostSeedError("RELEASE_MEMBER_NOT_REGULAR:" + relative)
return current
def _materialize_and_verify(config: Config, request: Request, manifest: Mapping[str, Any], patch: bytes, workspace: Path) -> dict[str, str]:
if _git_text(config.git_dir, "cat-file", "-t", request.intent.base_oid) != "commit":
raise HostSeedError("BASE_COMMIT_NOT_FOUND")
if _git_text(config.git_dir, "cat-file", "-t", request.intent.payload_commit) != "commit":
raise HostSeedError("PAYLOAD_COMMIT_NOT_FOUND")
workspace.parent.mkdir(parents=True, exist_ok=True)
clone = subprocess.run(
["git", "clone", "--no-checkout", "--shared", str(config.git_dir), str(workspace)],
capture_output=True, timeout=120, env={"PATH": os.environ.get("PATH", "")},
)
if clone.returncode:
raise HostSeedError("WORKSPACE_CLONE_FAILED")
checkout = subprocess.run(
["git", "-C", str(workspace), "checkout", "--detach", request.intent.base_oid],
capture_output=True, timeout=120, env={"PATH": os.environ.get("PATH", "")},
)
if checkout.returncode:
raise HostSeedError("WORKSPACE_CHECKOUT_FAILED")
try:
proc = subprocess.run(
["git", "-C", str(workspace), "apply", "--whitespace=nowarn", "-"],
input=patch, capture_output=True, timeout=120, env={"PATH": os.environ.get("PATH", "")},
)
if proc.returncode:
raise HostSeedError("PATCH_APPLY_FAILED")
changes = _expected_changes(manifest)
status_raw = subprocess.run(
["git", "-C", str(workspace), "status", "--porcelain", "-z"],
check=True, capture_output=True, timeout=120, env={"PATH": os.environ.get("PATH", "")},
).stdout
actual: set[str] = set()
chunks = status_raw.split(b"\0")
for chunk in chunks:
if not chunk:
continue
text = chunk.decode("utf-8", "surrogateescape")
if len(text) < 4:
raise HostSeedError("WORKTREE_STATUS_INVALID")
actual.add(text[3:].replace("\\", "/"))
if actual != set(changes):
raise HostSeedError("PATCH_CHANGESET_MISMATCH")
base_paths = {
_safe_repo_path(path, field="TRACKED_PATH")
for path in _git_text(config.git_dir, "ls-tree", "-r", "--name-only", request.intent.base_oid).splitlines()
if path
}
for rel, (digest, status) in changes.items():
if status == "modified" and rel not in base_paths:
raise HostSeedError("MODIFIED_PATH_NOT_IN_BASE:" + rel)
if status == "added" and rel in base_paths:
raise HostSeedError("ADDED_PATH_ALREADY_IN_BASE:" + rel)
p = _regular_member(workspace, rel)
if sha256(p.read_bytes()) != digest:
raise HostSeedError("CHANGED_FILE_HASH_MISMATCH:" + rel)
release_map = _manifest_release_map(manifest)
expected_release_paths = base_paths | {
rel for rel, (_digest, status) in changes.items() if status == "added"
}
if set(release_map) != expected_release_paths:
raise HostSeedError("RELEASE_MEMBER_SET_MISMATCH")
for rel, digest in release_map.items():
p = _regular_member(workspace, rel)
if sha256(p.read_bytes()) != digest:
raise HostSeedError("RELEASE_MEMBER_HASH_MISMATCH:" + rel)
tree_digest = sha256(canonical_compact({f"payload/repo/after/{k}": release_map[k] for k in sorted(release_map)}))
if tree_digest != request.intent.release_tree_sha256:
raise HostSeedError("RELEASE_TREE_HASH_MISMATCH")
if manifest.get("release_tree_sha256") not in {None, request.intent.release_tree_sha256}:
raise HostSeedError("MANIFEST_RELEASE_TREE_HASH_MISMATCH")
return release_map
except Exception:
raise
def _publish_release(config: Config, request: Request, workspace: Path, release_map: Mapping[str, str]) -> Path:
if config.release_root.exists() and config.release_root.is_symlink():
raise HostSeedError("RELEASE_ROOT_SYMLINK_REJECTED")
final = config.release_root / request.intent.release_id
if final.exists():
if final.is_symlink() or not final.is_dir():
raise HostSeedError("RELEASE_DESTINATION_CONFLICT")
for rel, digest in release_map.items():
p = final / PurePosixPath(rel)
if p.is_symlink() or not p.is_file() or sha256(p.read_bytes()) != digest:
raise HostSeedError("EXISTING_RELEASE_HASH_MISMATCH")
return final
config.release_root.mkdir(parents=True, exist_ok=True)
os.chmod(config.release_root, 0o755)
staging = Path(tempfile.mkdtemp(prefix=".host-seed-", dir=config.release_root))
os.chmod(staging, 0o755)
try:
for rel, digest in release_map.items():
source = _regular_member(workspace, rel)
target = staging / PurePosixPath(rel)
target.parent.mkdir(parents=True, exist_ok=True)
current = target.parent
while current != staging:
os.chmod(current, 0o755)
current = current.parent
data = source.read_bytes()
if sha256(data) != digest:
raise HostSeedError("STAGING_SOURCE_DRIFT")
fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0), 0o600)
try:
with os.fdopen(fd, "wb", closefd=True) as out:
out.write(data)
out.flush()
os.fsync(out.fileno())
except Exception:
raise
source_mode = stat.S_IMODE(source.stat(follow_symlinks=False).st_mode)
os.chmod(target, source_mode & 0o755 or 0o644)
for rel, digest in release_map.items():
p = staging / PurePosixPath(rel)
if p.is_symlink() or sha256(p.read_bytes()) != digest:
raise HostSeedError("STAGING_VERIFY_FAILED")
os.replace(staging, final)
parent_fd = os.open(config.release_root, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(parent_fd)
finally:
os.close(parent_fd)
return final
finally:
if staging.exists():
shutil.rmtree(staging)
def execute(config: Config, *, now: datetime | None = None, require_root: bool = True) -> dict[str, Any]:
if require_root and os.geteuid() != 0:
raise HostSeedError("ROOT_REQUIRED")
current = now or datetime.now(timezone.utc)
config.state_root.mkdir(parents=True, exist_ok=True, mode=0o700)
lock_path = config.state_root / "executor.lock"
lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600)
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
ref_probe = subprocess.run(
["git", f"--git-dir={config.git_dir}", "rev-parse", "--verify", config.request_ref],
capture_output=True, timeout=30, env={"PATH": os.environ.get("PATH", "")},
)
if ref_probe.returncode:
return {
"schema": "aios.control-plane-host-seed-poll.v1",
"state": "NO_REQUEST",
"request_ref": config.request_ref,
"service_mutation": False,
"task_creation": False,
}
request_oid = ref_probe.stdout.decode("utf-8", "strict").strip()
if not OID_RE.fullmatch(request_oid):
raise HostSeedError("REQUEST_REF_INVALID")
raw = _read_git_blob(config.git_dir, request_oid, config.request_path, maximum=MAX_REQUEST_BYTES)
request_value = _parse_json(raw, code="REQUEST_JSON_INVALID")
request = Request.load(request_value, host_identity=config.host_identity, now=current)
binding = _load_task_binding(config, request)
manifest = _load_manifest(config, request)
_load_identity_evidence(config, request)
patch = _load_patch(config, request)
op_id = request.intent.digest
receipt_path = config.state_root / "receipts" / f"{op_id}.json"
if receipt_path.exists():
receipt = _parse_json(_regular_file_bytes(receipt_path, maximum=256 * 1024), code="RECEIPT_INVALID")
if receipt.get("state") == "EXECUTED" and receipt.get("intent_sha256") == op_id:
return receipt
raise HostSeedError("OPERATION_STATE_CONFLICT")
_atomic_json(config.state_root / "operations" / f"{op_id}.json", {
"schema": "aios.control-plane-host-seed-operation.v1",
"state": "PENDING",
"intent_sha256": op_id,
"request_commit": request_oid,
"authorization_task_id": request.authorization_task_id,
"authorization_binding_sha256": request.authorization_binding_sha256,
"started_at": current.isoformat().replace("+00:00", "Z"),
})
workspace_root = config.state_root / "workspaces"
workspace_root.mkdir(parents=True, exist_ok=True)
workspace = workspace_root / f"seed-{op_id[:20]}"
if workspace.exists():
raise HostSeedError("WORKSPACE_ALREADY_EXISTS")
release_map: dict[str, str] | None = None
try:
release_map = _materialize_and_verify(config, request, manifest, patch, workspace)
destination = _publish_release(config, request, workspace, release_map)
finally:
if workspace.exists():
shutil.rmtree(workspace, ignore_errors=True)
receipt = {
"schema": "aios.control-plane-host-seed-receipt.v1",
"state": "EXECUTED",
"operation": OPERATION,
"intent_sha256": op_id,
"request_commit": request_oid,
"repository": REPOSITORY,
"target_host": request.intent.target_host,
"candidate": request.intent.candidate,
"release_id": request.intent.release_id,
"base_oid": request.intent.base_oid,
"payload_commit": request.intent.payload_commit,
"validation_artifact_sha256": request.intent.artifact_sha256,
"artifact_delivery_used": False,
"manifest_sha256": request.intent.manifest_sha256,
"machine_goal_sha256": request.intent.machine_goal_sha256,
"release_tree_sha256": request.intent.release_tree_sha256,
"seed_bootstrap_sha256": request.intent.seed_bootstrap_sha256,
"authorization_task_id": request.authorization_task_id,
"authorization_binding_sha256": request.authorization_binding_sha256,
"release_path": str(destination),
"release_member_count": len(release_map or {}),
"service_mutation": False,
"service_restart": False,
"projector_started": False,
"task_creation": False,
"production_write": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"executed_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
}
_atomic_json(receipt_path, receipt)
_atomic_json(config.state_root / "operations" / f"{op_id}.json", receipt)
return receipt
finally:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
os.close(lock_fd)
def config_from_environment() -> Config:
return Config(
git_dir=Path(os.environ.get("AIOS_HOST_SEED_GIT_DIR", "/data/aios-gitops/repo.git")),
task_binding_root=Path(os.environ.get(
"AIOS_HOST_SEED_TASK_BINDING_ROOT",
"/var/lib/aios-gitops/bridge-v2-staging/task-bindings",
)),
state_root=Path(os.environ.get(
"AIOS_HOST_SEED_STATE_ROOT",
"/var/lib/aios-control-plane-host-seed-executor",
)),
release_root=Path(os.environ.get("AIOS_HOST_SEED_RELEASE_ROOT", "/opt/aios/releases")),
host_identity=os.environ.get("AIOS_HOST_IDENTITY", "cn-network-mkt"),
task_binding_owner=os.environ.get(
"AIOS_HOST_SEED_TASK_BINDING_OWNER", "aios-bridge-staging"
) or None,
request_ref=os.environ.get("AIOS_HOST_SEED_REQUEST_REF", REQUEST_REF),
request_path=os.environ.get("AIOS_HOST_SEED_REQUEST_PATH", REQUEST_PATH),
)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=("execute",))
args = parser.parse_args()
if args.command == "execute":
result = execute(config_from_environment())
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,52 +0,0 @@
#!/bin/sh
set -eu
if [ "$(id -u)" -ne 0 ]; then
echo "ROOT_REQUIRED" >&2
exit 20
fi
SOURCE_ROOT=${1:-}
if [ -z "$SOURCE_ROOT" ]; then
echo "SOURCE_ROOT_REQUIRED" >&2
exit 21
fi
EXECUTOR="$SOURCE_ROOT/active-runtime/app/aios_control_plane_host_seed_executor.py"
SERVICE="$SOURCE_ROOT/deploy/systemd/aios-control-plane-host-seed-executor.service"
TIMER="$SOURCE_ROOT/deploy/systemd/aios-control-plane-host-seed-executor.timer"
for file in "$EXECUTOR" "$SERVICE" "$TIMER"; do
if [ ! -f "$file" ] || [ -L "$file" ]; then
echo "SOURCE_FILE_INVALID:$file" >&2
exit 22
fi
done
AUTHORITY_GIT=/data/aios-gitops/repo.git
REQUEST_REF=refs/heads/agent/host-seed-authorization-requests
if [ ! -d "$AUTHORITY_GIT" ]; then
echo "AUTHORITATIVE_GIT_UNAVAILABLE" >&2
exit 23
fi
if ! id -u aios-bridge-staging >/dev/null 2>&1; then
echo "BRIDGE_SERVICE_ACCOUNT_UNAVAILABLE" >&2
exit 24
fi
if git --git-dir="$AUTHORITY_GIT" show-ref --verify --quiet "$REQUEST_REF"; then
echo "HOST_SEED_REQUEST_REF_ALREADY_PRESENT" >&2
exit 25
fi
install -d -m 0755 /usr/local/libexec
install -m 0755 "$EXECUTOR" /usr/local/libexec/aios-control-plane-host-seed-executor
install -m 0644 "$SERVICE" /etc/systemd/system/aios-control-plane-host-seed-executor.service
install -m 0644 "$TIMER" /etc/systemd/system/aios-control-plane-host-seed-executor.timer
install -d -m 0700 /var/lib/aios-control-plane-host-seed-executor
systemctl daemon-reload
systemctl enable --now aios-control-plane-host-seed-executor.timer
systemctl is-enabled --quiet aios-control-plane-host-seed-executor.timer
systemctl is-active --quiet aios-control-plane-host-seed-executor.timer
echo "HOST_SEED_EXECUTOR_INSTALL=PASS"
@@ -1,15 +0,0 @@
#!/bin/sh
set -eu
if [ "$(id -u)" -ne 0 ]; then
echo "ROOT_REQUIRED" >&2
exit 20
fi
systemctl disable --now aios-control-plane-host-seed-executor.timer 2>/dev/null || true
rm -f /etc/systemd/system/aios-control-plane-host-seed-executor.timer
rm -f /etc/systemd/system/aios-control-plane-host-seed-executor.service
rm -f /usr/local/libexec/aios-control-plane-host-seed-executor
systemctl daemon-reload
echo "HOST_SEED_EXECUTOR_ROLLBACK=PASS"
@@ -1,41 +0,0 @@
[Unit]
Description=AIOS governed immutable Host seed executor
After=local-fs.target
[Service]
Type=oneshot
User=root
Group=root
UMask=0077
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=AIOS_HOST_IDENTITY=cn-network-mkt
Environment=AIOS_HOST_SEED_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_HOST_SEED_TASK_BINDING_ROOT=/var/lib/aios-gitops/bridge-v2-staging/task-bindings
Environment=AIOS_HOST_SEED_TASK_BINDING_OWNER=aios-bridge-staging
Environment=AIOS_HOST_SEED_STATE_ROOT=/var/lib/aios-control-plane-host-seed-executor
Environment=AIOS_HOST_SEED_RELEASE_ROOT=/opt/aios/releases
Environment=AIOS_HOST_SEED_REQUEST_REF=refs/heads/agent/host-seed-authorization-requests
Environment=AIOS_HOST_SEED_REQUEST_PATH=control-plane/host-seed/request.json
ExecStart=/usr/bin/python3 -B /usr/local/libexec/aios-control-plane-host-seed-executor execute
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictNamespaces=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX
IPAddressDeny=any
CapabilityBoundingSet=CAP_DAC_OVERRIDE CAP_FOWNER CAP_CHOWN
ReadOnlyPaths=/usr/local/libexec/aios-control-plane-host-seed-executor
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadOnlyPaths=/var/lib/aios-gitops/bridge-v2-staging/task-bindings
ReadWritePaths=/var/lib/aios-control-plane-host-seed-executor
ReadWritePaths=/opt/aios/releases
@@ -1,12 +0,0 @@
[Unit]
Description=Poll governed immutable Host seed requests
[Timer]
OnBootSec=2min
OnUnitActiveSec=30s
AccuracySec=1s
Persistent=true
Unit=aios-control-plane-host-seed-executor.service
[Install]
WantedBy=timers.target
@@ -0,0 +1,721 @@
{
"schema_version": "aios.chatgpt-action-schema-capability-manifest.v2",
"source_schema": {
"path": "docs/gitops/openapi-chatgpt-actions.yaml",
"operation_count": 31,
"sha256": "d6129421085c9516ba88796d8727bc4ba1c4b4b48bac74f146e68ebf7cc9cd96"
},
"public_runtime_schema": {
"path": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml",
"immutable_runtime_oid": "2e5996463de564793df881df5c5f684359733627",
"operation_count": 30,
"sha256": "4cee48511e9435cb147b8925c37aaa135c384a468ede34d6828bad42c97c5fcc"
},
"authoritative_server_urls": [
"https://cnnetmkt.top"
],
"union_operation_count": 46,
"schemas": [
{
"path": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"operation_count": 26,
"public_action_import": true
},
{
"path": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"operation_count": 14,
"public_action_import": true
},
{
"path": "docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml",
"trust_domain": "online-agent-task",
"operation_count": 6,
"public_action_import": true
}
],
"operations": [
{
"operation_id": "acquireWriterLease",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/acquireWriterLease",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "cancelOnlineAgentTask",
"schema": "docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml",
"trust_domain": "online-agent-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/cancelOnlineAgentTask",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "agent-task:control",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "commitChanges",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/commit",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "commit:create",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "commitFile",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/file",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "commitFileAtomic",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/commit-file",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "commitFileGoverned",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/commitFileGoverned",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "commitFilesGoverned",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/commitFilesGoverned",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "createBranch",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/branch",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "branch:create",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "createGovernedTask",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/createGovernedTask",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "branch:create",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "createOnlineAgentTask",
"schema": "docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml",
"trust_domain": "online-agent-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/createOnlineAgentTask",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "agent-task:control",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "createPatch",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/patch",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "patch:create",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "createReleaseCandidate",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/createReleaseCandidate",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "release:candidate",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "createReleaseIntent",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/createReleaseIntent",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "release:candidate",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "getApprovalEnvelope",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getApprovalEnvelope",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getBaseline",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/base",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getCapabilityStatus",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getCapabilityStatus",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "getControlPlaneSnapshot",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/control-plane-snapshot",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getDiff",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/diff",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getGitOpsJob",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getGitOpsJob",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getGovernedWriteCapabilities",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/governed-write-capabilities",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getOnlineAgentTask",
"schema": "docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml",
"trust_domain": "online-agent-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/online-agent-task",
"method": "GET",
"declared_openapi_security": [],
"required_scope": "agent-task:control",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getOnlineAgentTaskEvents",
"schema": "docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml",
"trust_domain": "online-agent-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/online-agent-task-events",
"method": "GET",
"declared_openapi_security": [],
"required_scope": "agent-task:control",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getRecoveryStatus",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getRecoveryStatus",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "getReleaseAcceptance",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getReleaseAcceptance",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "getReleaseCandidate",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getReleaseCandidate",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getReleaseConsistency",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/release-consistency",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getReleasePlan",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getReleasePlan",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "getReleaseSaga",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/getReleaseSaga",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getRuntimeStatus",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/run",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "getTagPublicationStatus",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/tag-publication-status",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "listPatches",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/patch",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "listVersions",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/ver",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "planNaturalLanguageGitOpsAction",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/nl",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "nl:plan",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "readModelRegistry",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/model",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "reconcileGitOpsJob",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/reconcileGitOpsJob",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "registerModel",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/model",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "model:write",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "registerOnlineAgent",
"schema": "docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml",
"trust_domain": "online-agent-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/registerOnlineAgent",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "agent-task:control",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "requestOrCreateTag",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/tag",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "tag:create",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "requestRollback",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/rollback",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "rollback:request",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "resumeGitOpsJob",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/resumeGitOpsJob",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "file:write",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "runReleaseAcceptance",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/release-acceptance",
"method": "GET",
"declared_openapi_security": [],
"required_scope": null,
"exposure": "public",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "setModelPriority",
"schema": "docs/gitops/openapi-chatgpt-actions-online-gitops-task.yaml",
"trust_domain": "online-gitops-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/api/model-priority",
"method": "PUT",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "model:priority",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "startDeployment",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/startDeployment",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "release:deploy",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "submitOwnerApproval",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/submitOwnerApproval",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "release:approve",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
},
{
"operation_id": "submitSignedApproval",
"schema": "docs/gitops/openapi-chatgpt-actions-governed-release.yaml",
"trust_domain": "governed-release",
"server_url": "https://cnnetmkt.top",
"server_route": "/submitSignedApproval",
"method": "POST",
"declared_openapi_security": [
{
"bearerAuth": []
}
],
"required_scope": "release:approve",
"exposure": "control-plane",
"source_origin": "docs/gitops/openapi-chatgpt-actions.yaml",
"public_origin": null
},
{
"operation_id": "verifyOnlineAgentTask",
"schema": "docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml",
"trust_domain": "online-agent-task",
"server_url": "https://cnnetmkt.top",
"server_route": "/verifyOnlineAgentTask",
"method": "POST",
"declared_openapi_security": [],
"required_scope": "agent-task:control",
"exposure": "control-plane",
"source_origin": null,
"public_origin": "docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml"
}
],
"scope_provenance": {
"release_and_gitops": "immutable staging runtime aios_model_api_staging.py handler scope checks",
"agent_task_control": {
"required_scope": "agent-task:control",
"immutable_runtime_oid": "2e5996463de564793df881df5c5f684359733627",
"proxy_routes": [
"/api/online-agent-task",
"/api/online-agent-task-events",
"/createOnlineAgentTask",
"/registerOnlineAgent",
"/verifyOnlineAgentTask",
"/cancelOnlineAgentTask"
],
"operation_ids": [
"cancelOnlineAgentTask",
"createOnlineAgentTask",
"getOnlineAgentTask",
"getOnlineAgentTaskEvents",
"registerOnlineAgent",
"verifyOnlineAgentTask"
]
}
}
}
@@ -0,0 +1,13 @@
# ChatGPT Action schema trust-domain reconciliation
The main source has **31** operations while immutable public runtime `2e5996463de564793df881df5c5f684359733627` serves a different **30**. Both are retained as non-import parity fixtures. Their exact compatible union is **46** operations.
Import only these bounded specs:
- `openapi-chatgpt-actions-governed-release.yaml` — 26 Governed GitOps and Release operations.
- `openapi-chatgpt-actions-online-gitops-task.yaml` — 14 Online GitOps Task operations.
- `openapi-chatgpt-actions-online-agent-task.yaml` — 6 actual public Online Agent Task operations.
The machine-readable capability manifest records split schema, server route, declared OpenAPI security, required runtime scope, exposure, and `source_origin` / `public_origin` for every union operation. The dedicated Online Agent Task document includes `createOnlineAgentTask`, `registerOnlineAgent`, `getOnlineAgentTask`, `getOnlineAgentTaskEvents`, `verifyOnlineAgentTask`, and `cancelOnlineAgentTask`; each requires runtime scope `agent-task:control`. Its declared OpenAPI security is copied from the public snapshot without rewrite, while the manifest records the runtime scope separately.
Run `python3 scripts/validate_chatgpt_action_schema_split.py` and `python3 -m unittest discover -s tests -p 'test_chatgpt_action_schema_split.py' -v`. The validator reports `formal_validator=NOT_INSTALLED` rather than a false formal pass if `openapi-spec-validator` is absent.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,782 @@
openapi: 3.1.0
info:
title: AIOS ChatGPT Actions - Online Agent Task
version: V1R1C0-chatgpt-actions-online-agent-task
description: Trust-domain import from the exact union of the 31-operation main source and 30-operation
immutable public runtime; operation, server route, and declared security are preserved.
servers:
- url: https://cnnetmkt.top
security: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque-token
schemas:
PublicationState:
type: string
description: Canonical durable publication state. Legacy TAG_CREATED_LOCAL is normalized on read
and is never emitted by new writes.
enum:
- PENDING
- LOCAL_TAG_CREATED
- REMOTE_PUSH_PENDING
- REMOTE_PUSH_CONFIRMED
- GITEA_API_CONFIRMED
- BASELINE_UPDATE_PENDING
- BASELINE_UPDATED
- PRODUCTION_DEPLOYED
- RECONCILIATION_REQUIRED
- MANUAL_OWNER_REVIEW_REQUIRED
PublicationStatus:
type: object
required:
- version
- state
properties:
version:
type: string
state:
$ref: '#/components/schemas/PublicationState'
fail_closed:
type: boolean
BranchRequest:
type: object
required:
- branch
properties:
branch:
type: string
example: patch/chatgpt-actions-demo
base:
type: string
default: main
message:
type: string
FileCommitRequest:
type: object
required:
- branch
- path
- content
properties:
branch:
type: string
base:
type: string
default: main
create_branch:
type: boolean
default: false
path:
type: string
example: docs/gitops/chatgpt-change.md
content:
type: string
message:
type: string
AtomicFileCommitRequest:
type: object
additionalProperties: false
required:
- branch
- path
- content
- message
- expected_head_oid
- idempotency_key
properties:
branch:
type: string
pattern: ^patch/[A-Za-z0-9._/-]+$
path:
type: string
minLength: 1
maxLength: 512
content:
type: string
maxLength: 1048576
message:
type: string
minLength: 1
maxLength: 200
expected_head_oid:
type: string
pattern: ^[0-9a-f]{40}$
idempotency_key:
type: string
pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$
CommitRequest:
type: object
properties:
branch:
type: string
paths:
type: array
items:
type: string
message:
type: string
PatchRequest:
type: object
required:
- patch_name
properties:
patch_name:
type: string
branch:
type: string
base:
type: string
default: main
risk:
type: string
enum:
- low
- medium
- high
description:
type: string
message:
type: string
TagRequest:
type: object
required:
- version
- target_oid
- evidence_closure_oid
- approval_plan_id
properties:
version:
type: string
example: V1R1C1
target_oid:
type: string
pattern: ^[0-9a-fA-F]{40}$
description: Full approved commit OID. The tag is created explicitly at this commit, never implicitly
at workspace HEAD.
evidence_closure_oid:
type: string
pattern: ^[0-9a-fA-F]{40}$
description: Required evidence closure commit. target_oid must be its ancestor.
approval_plan_id:
type: string
description: Audited owner-approved release or tag plan identifier.
message:
type: string
execute:
type: boolean
description: Owner-approved calls only. Otherwise an approval request commit is created.
RollbackRequest:
type: object
required:
- target_version
properties:
target_version:
type: string
reason:
type: string
branch:
type: string
ModelRequest:
type: object
required:
- provider_name
- base_url
- model_name
properties:
username:
type: string
provider_name:
type: string
base_url:
type: string
model_name:
type: string
display_name:
type: string
api_key_secret_ref:
type: string
description: Preferred. Git stores this secret_ref only.
api_key:
type: string
writeOnly: true
description: Accepted only for runtime secret storage; never committed to Git or logs.
branch:
type: string
message:
type: string
ModelPriorityRequest:
type: object
required:
- items
properties:
username:
type: string
branch:
type: string
items:
type: array
items:
type: object
properties:
model_id:
type: string
priority:
type: integer
enabled:
type: boolean
fallback_policy:
type: string
NLRequest:
type: object
required:
- text
properties:
text:
type: string
branch:
type: string
DeploymentPlan:
type: object
required:
- service
- release_path
- release_root
- cutover_link
- dropin_path
- dropin_sha256
- systemd_unit_sha256
- rollback_path
- rollback_sha256
- source_commit
- tree_oid
- artifact_sha256
- manifest_sha256
- package_closure_sha256
- authoritative_git_dir
- repository_identity
- writer_expected
- baseline_before_sha256
- registry_before_sha256
- production_tag_refset_before_sha256
- health_contract_version
- timeout_policy
- retry_policy
- policy_sha256
- health_contract
properties:
service:
type: string
release_path:
type: string
release_root:
type: string
cutover_link:
type: string
dropin_path:
type: string
dropin_sha256:
type: string
pattern: ^[0-9a-f]{64}$
systemd_unit_sha256:
type: string
pattern: ^[0-9a-f]{64}$
rollback_path:
type: string
rollback_sha256:
type: string
pattern: ^[0-9a-f]{64}$
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
tree_oid:
type: string
pattern: ^[0-9a-f]{40}$
artifact_sha256:
type: string
pattern: ^[0-9a-f]{64}$
manifest_sha256:
type: string
pattern: ^[0-9a-f]{64}$
package_closure_sha256:
type: string
pattern: ^[0-9a-f]{64}$
authoritative_git_dir:
type: string
repository_identity:
type: string
writer_expected:
type: boolean
enum:
- false
baseline_before_sha256:
type: string
pattern: ^[0-9a-f]{64}$
registry_before_sha256:
type: string
pattern: ^[0-9a-f]{64}$
production_tag_refset_before_sha256:
type: string
pattern: ^[0-9a-f]{64}$
health_contract_version:
type: string
timeout_policy:
type: object
retry_policy:
type: object
policy_sha256:
type: string
pattern: ^[0-9a-f]{64}$
health_contract:
type: object
additionalProperties: true
ReleaseCandidateInput:
type: object
required:
- source_commit
- version
additionalProperties: false
properties:
candidate_id:
type: string
pattern: ^[A-Za-z0-9][A-Za-z0-9_.-]{2,127}$
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
version:
type: string
pattern: ^V[0-9]+R[0-9]+C[0-9]+(?:[-.][A-Za-z0-9][A-Za-z0-9.-]*)?$
ReleaseIntentInput:
type: object
required:
- event_id
- source_commit
additionalProperties: false
properties:
event_id:
type: string
pattern: ^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
SignedReleasePlanApproval:
type: object
required:
- release_plan_id
- candidate_id
- source_commit
- tree_oid
- artifact_sha256
- manifest_sha256
- deployment_plan_sha256
- release_plan_sha256
- policy_sha256
- approval_plan_id
- evidence_closure_oid
- owner_approval_record_oid
- capabilities
- owner_identity
- trust_domain
- issued_at
- not_before
- expires_at
- nonce
- key_id
- signature
properties:
release_plan_id:
type: string
candidate_id:
type: string
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
tree_oid:
type: string
pattern: ^[0-9a-f]{40}$
artifact_sha256:
type: string
pattern: ^[0-9a-f]{64}$
manifest_sha256:
type: string
pattern: ^[0-9a-f]{64}$
deployment_plan_sha256:
type: string
pattern: ^[0-9a-f]{64}$
release_plan_sha256:
type: string
pattern: ^[0-9a-f]{64}$
policy_sha256:
type: string
pattern: ^[0-9a-f]{64}$
approval_plan_id:
type: string
evidence_closure_oid:
type: string
pattern: ^[0-9a-f]{40}$
owner_approval_record_oid:
type: string
pattern: ^[0-9a-f]{40}$
owner_identity:
type: string
trust_domain:
type: string
issued_at:
type: string
format: date-time
not_before:
type: string
format: date-time
capabilities:
type: array
uniqueItems: true
items:
type: string
enum:
- runtime_switch
- tag_publish
- baseline_registry_finalize
expires_at:
type: string
format: date-time
nonce:
type: string
minLength: 16
key_id:
type: string
signature:
type: string
format: byte
writeOnly: true
StartDeploymentRequest:
type: object
required:
- candidate_id
additionalProperties: false
properties:
candidate_id:
type: string
Oid40:
type: string
pattern: ^[0-9a-f]{40}$
CreateGovernedTaskRequest:
type: object
additionalProperties: false
required:
- task_id
- repository
- base_oid
- target_branch
- risk
- description
- idempotency_key
properties:
task_id:
type: string
repository:
type: string
const: aios/aios
base_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
risk:
type: string
enum:
- low
- medium
- high
- engineering
description:
type: string
idempotency_key:
type: string
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
generation:
type: integer
minimum: 1
WriterLeaseRequest:
type: object
additionalProperties: false
required:
- schema
- task_id
- repository
- branch_mode
- base_oid
- expected_branch_oid
- target_branch
- generation
- lease_seconds
properties:
schema:
type: string
task_id:
type: string
repository:
type: string
const: aios/aios
branch_mode:
type: string
enum:
- CREATE_NEW
- CONTINUE_EXISTING
base_oid:
$ref: '#/components/schemas/Oid40'
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
generation:
type: integer
minimum: 1
lease_seconds:
type: integer
minimum: 1
maximum: 86400
CommitFileGovernedRequest:
type: object
additionalProperties: false
required:
- task_id
- repository
- expected_branch_oid
- target_branch
- generation
- idempotency_key
- path
- content_base64
- message
properties:
task_id:
type: string
repository:
type: string
const: aios/aios
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
generation:
type: integer
minimum: 1
idempotency_key:
type: string
path:
type: string
content_base64:
type: string
message:
type: string
CommitFilesGovernedRequest:
type: object
additionalProperties: false
required:
- task_id
- repository
- expected_branch_oid
- target_branch
- generation
- idempotency_key
- files
- message
properties:
task_id:
type: string
repository:
type: string
const: aios/aios
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
generation:
type: integer
minimum: 1
idempotency_key:
type: string
files:
type: object
additionalProperties:
type: string
description: Base64 encoded file bytes
message:
type: string
CreateOnlineAgentTaskRequest:
type: object
additionalProperties: false
required:
- goal
- instructions
- success_criteria
- idempotency_key
properties:
task_id:
type: string
goal:
type: string
instructions:
type: string
success_criteria:
type: array
items: {}
required_capabilities:
type: array
items:
type: string
constraints:
type: object
additionalProperties: true
metadata:
type: object
additionalProperties: true
deadline:
type: string
format: date-time
priority:
type: integer
minimum: -100
maximum: 100
max_attempts:
type: integer
minimum: 1
maximum: 20
idempotency_key:
type: string
paths:
/createOnlineAgentTask:
post:
operationId: createOnlineAgentTask
x-openai-isConsequential: true
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOnlineAgentTaskRequest'
responses:
'200':
description: Online task created
'201':
description: Online task created
/registerOnlineAgent:
post:
operationId: registerOnlineAgent
x-openai-isConsequential: true
description: 'Control operation. The returned agent token is secret material and must be delivered
through runtime secret storage, never Git.
'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- agent_id
- agent_type
- agent_version
- capabilities
properties:
agent_id:
type: string
agent_type:
type: string
agent_version:
type: string
capabilities:
type: array
items:
type: string
metadata:
type: object
additionalProperties: true
responses:
'201':
description: Agent registered
/api/online-agent-task:
get:
operationId: getOnlineAgentTask
parameters:
- name: task_id
in: query
required: true
schema:
type: string
responses:
'200':
description: Task status
/api/online-agent-task-events:
get:
operationId: getOnlineAgentTaskEvents
parameters:
- name: task_id
in: query
required: true
schema:
type: string
responses:
'200':
description: Task events
/verifyOnlineAgentTask:
post:
operationId: verifyOnlineAgentTask
x-openai-isConsequential: true
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- task_id
- decision
properties:
task_id:
type: string
decision:
type: string
enum:
- SUCCEEDED
- NEEDS_REWORK
- FAILED
verification:
type: object
additionalProperties: true
responses:
'200':
description: Verification applied
/cancelOnlineAgentTask:
post:
operationId: cancelOnlineAgentTask
x-openai-isConsequential: true
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- task_id
properties:
task_id:
type: string
reason:
type: string
responses:
'200':
description: Task cancelled
x-aios-schema-reconciliation:
source_schema: docs/gitops/openapi-chatgpt-actions.yaml
public_runtime_schema: docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml
trust_domain: online-agent-task
operation_count: 6
public_action_import: true
@@ -0,0 +1,881 @@
openapi: 3.1.0
info:
title: AIOS ChatGPT Actions - Online GitOps Task
version: V1R1C0-chatgpt-actions-online-gitops-task
description: Trust-domain import from the exact union of the 31-operation main source and 30-operation
immutable public runtime; operation, server route, and declared security are preserved.
servers:
- url: https://cnnetmkt.top
security: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque-token
schemas:
PublicationState:
type: string
description: Canonical durable publication state. Legacy TAG_CREATED_LOCAL is normalized on read
and is never emitted by new writes.
enum:
- PENDING
- LOCAL_TAG_CREATED
- REMOTE_PUSH_PENDING
- REMOTE_PUSH_CONFIRMED
- GITEA_API_CONFIRMED
- BASELINE_UPDATE_PENDING
- BASELINE_UPDATED
- PRODUCTION_DEPLOYED
- RECONCILIATION_REQUIRED
- MANUAL_OWNER_REVIEW_REQUIRED
PublicationStatus:
type: object
required:
- version
- state
properties:
version:
type: string
state:
$ref: '#/components/schemas/PublicationState'
fail_closed:
type: boolean
BranchRequest:
type: object
required:
- branch
properties:
branch:
type: string
example: patch/chatgpt-actions-demo
base:
type: string
default: main
message:
type: string
FileCommitRequest:
type: object
required:
- branch
- path
- content
properties:
branch:
type: string
base:
type: string
default: main
create_branch:
type: boolean
default: false
path:
type: string
example: docs/gitops/chatgpt-change.md
content:
type: string
message:
type: string
AtomicFileCommitRequest:
type: object
additionalProperties: false
required:
- branch
- path
- content
- message
- expected_head_oid
- idempotency_key
properties:
branch:
type: string
pattern: ^patch/[A-Za-z0-9._/-]+$
path:
type: string
minLength: 1
maxLength: 512
content:
type: string
maxLength: 1048576
message:
type: string
minLength: 1
maxLength: 200
expected_head_oid:
type: string
pattern: ^[0-9a-f]{40}$
idempotency_key:
type: string
pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$
CommitRequest:
type: object
properties:
branch:
type: string
paths:
type: array
items:
type: string
message:
type: string
PatchRequest:
type: object
required:
- patch_name
properties:
patch_name:
type: string
branch:
type: string
base:
type: string
default: main
risk:
type: string
enum:
- low
- medium
- high
description:
type: string
message:
type: string
TagRequest:
type: object
required:
- version
- target_oid
- evidence_closure_oid
- approval_plan_id
properties:
version:
type: string
example: V1R1C1
target_oid:
type: string
pattern: ^[0-9a-fA-F]{40}$
description: Full approved commit OID. The tag is created explicitly at this commit, never implicitly
at workspace HEAD.
evidence_closure_oid:
type: string
pattern: ^[0-9a-fA-F]{40}$
description: Required evidence closure commit. target_oid must be its ancestor.
approval_plan_id:
type: string
description: Audited owner-approved release or tag plan identifier.
message:
type: string
execute:
type: boolean
description: Owner-approved calls only. Otherwise an approval request commit is created.
RollbackRequest:
type: object
required:
- target_version
properties:
target_version:
type: string
reason:
type: string
branch:
type: string
ModelRequest:
type: object
required:
- provider_name
- base_url
- model_name
properties:
username:
type: string
provider_name:
type: string
base_url:
type: string
model_name:
type: string
display_name:
type: string
api_key_secret_ref:
type: string
description: Preferred. Git stores this secret_ref only.
api_key:
type: string
writeOnly: true
description: Accepted only for runtime secret storage; never committed to Git or logs.
branch:
type: string
message:
type: string
ModelPriorityRequest:
type: object
required:
- items
properties:
username:
type: string
branch:
type: string
items:
type: array
items:
type: object
properties:
model_id:
type: string
priority:
type: integer
enabled:
type: boolean
fallback_policy:
type: string
NLRequest:
type: object
required:
- text
properties:
text:
type: string
branch:
type: string
DeploymentPlan:
type: object
required:
- service
- release_path
- release_root
- cutover_link
- dropin_path
- dropin_sha256
- systemd_unit_sha256
- rollback_path
- rollback_sha256
- source_commit
- tree_oid
- artifact_sha256
- manifest_sha256
- package_closure_sha256
- authoritative_git_dir
- repository_identity
- writer_expected
- baseline_before_sha256
- registry_before_sha256
- production_tag_refset_before_sha256
- health_contract_version
- timeout_policy
- retry_policy
- policy_sha256
- health_contract
properties:
service:
type: string
release_path:
type: string
release_root:
type: string
cutover_link:
type: string
dropin_path:
type: string
dropin_sha256:
type: string
pattern: ^[0-9a-f]{64}$
systemd_unit_sha256:
type: string
pattern: ^[0-9a-f]{64}$
rollback_path:
type: string
rollback_sha256:
type: string
pattern: ^[0-9a-f]{64}$
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
tree_oid:
type: string
pattern: ^[0-9a-f]{40}$
artifact_sha256:
type: string
pattern: ^[0-9a-f]{64}$
manifest_sha256:
type: string
pattern: ^[0-9a-f]{64}$
package_closure_sha256:
type: string
pattern: ^[0-9a-f]{64}$
authoritative_git_dir:
type: string
repository_identity:
type: string
writer_expected:
type: boolean
enum:
- false
baseline_before_sha256:
type: string
pattern: ^[0-9a-f]{64}$
registry_before_sha256:
type: string
pattern: ^[0-9a-f]{64}$
production_tag_refset_before_sha256:
type: string
pattern: ^[0-9a-f]{64}$
health_contract_version:
type: string
timeout_policy:
type: object
retry_policy:
type: object
policy_sha256:
type: string
pattern: ^[0-9a-f]{64}$
health_contract:
type: object
additionalProperties: true
ReleaseCandidateInput:
type: object
required:
- source_commit
- version
additionalProperties: false
properties:
candidate_id:
type: string
pattern: ^[A-Za-z0-9][A-Za-z0-9_.-]{2,127}$
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
version:
type: string
pattern: ^V[0-9]+R[0-9]+C[0-9]+(?:[-.][A-Za-z0-9][A-Za-z0-9.-]*)?$
ReleaseIntentInput:
type: object
required:
- event_id
- source_commit
additionalProperties: false
properties:
event_id:
type: string
pattern: ^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
SignedReleasePlanApproval:
type: object
required:
- release_plan_id
- candidate_id
- source_commit
- tree_oid
- artifact_sha256
- manifest_sha256
- deployment_plan_sha256
- release_plan_sha256
- policy_sha256
- approval_plan_id
- evidence_closure_oid
- owner_approval_record_oid
- capabilities
- owner_identity
- trust_domain
- issued_at
- not_before
- expires_at
- nonce
- key_id
- signature
properties:
release_plan_id:
type: string
candidate_id:
type: string
source_commit:
type: string
pattern: ^[0-9a-f]{40}$
tree_oid:
type: string
pattern: ^[0-9a-f]{40}$
artifact_sha256:
type: string
pattern: ^[0-9a-f]{64}$
manifest_sha256:
type: string
pattern: ^[0-9a-f]{64}$
deployment_plan_sha256:
type: string
pattern: ^[0-9a-f]{64}$
release_plan_sha256:
type: string
pattern: ^[0-9a-f]{64}$
policy_sha256:
type: string
pattern: ^[0-9a-f]{64}$
approval_plan_id:
type: string
evidence_closure_oid:
type: string
pattern: ^[0-9a-f]{40}$
owner_approval_record_oid:
type: string
pattern: ^[0-9a-f]{40}$
owner_identity:
type: string
trust_domain:
type: string
issued_at:
type: string
format: date-time
not_before:
type: string
format: date-time
capabilities:
type: array
uniqueItems: true
items:
type: string
enum:
- runtime_switch
- tag_publish
- baseline_registry_finalize
expires_at:
type: string
format: date-time
nonce:
type: string
minLength: 16
key_id:
type: string
signature:
type: string
format: byte
writeOnly: true
StartDeploymentRequest:
type: object
required:
- candidate_id
additionalProperties: false
properties:
candidate_id:
type: string
Oid40:
type: string
pattern: ^[0-9a-f]{40}$
CreateGovernedTaskRequest:
type: object
additionalProperties: false
required:
- task_id
- repository
- base_oid
- target_branch
- risk
- description
- idempotency_key
properties:
task_id:
type: string
repository:
type: string
const: aios/aios
base_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
risk:
type: string
enum:
- low
- medium
- high
- engineering
description:
type: string
idempotency_key:
type: string
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
generation:
type: integer
minimum: 1
WriterLeaseRequest:
type: object
additionalProperties: false
required:
- schema
- task_id
- repository
- branch_mode
- base_oid
- expected_branch_oid
- target_branch
- generation
- lease_seconds
properties:
schema:
type: string
task_id:
type: string
repository:
type: string
const: aios/aios
branch_mode:
type: string
enum:
- CREATE_NEW
- CONTINUE_EXISTING
base_oid:
$ref: '#/components/schemas/Oid40'
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
generation:
type: integer
minimum: 1
lease_seconds:
type: integer
minimum: 1
maximum: 86400
CommitFileGovernedRequest:
type: object
additionalProperties: false
required:
- task_id
- repository
- expected_branch_oid
- target_branch
- generation
- idempotency_key
- path
- content_base64
- message
properties:
task_id:
type: string
repository:
type: string
const: aios/aios
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
generation:
type: integer
minimum: 1
idempotency_key:
type: string
path:
type: string
content_base64:
type: string
message:
type: string
CommitFilesGovernedRequest:
type: object
additionalProperties: false
required:
- task_id
- repository
- expected_branch_oid
- target_branch
- generation
- idempotency_key
- files
- message
properties:
task_id:
type: string
repository:
type: string
const: aios/aios
expected_branch_oid:
$ref: '#/components/schemas/Oid40'
target_branch:
type: string
pattern: ^agent/
generation:
type: integer
minimum: 1
idempotency_key:
type: string
files:
type: object
additionalProperties:
type: string
description: Base64 encoded file bytes
message:
type: string
CreateOnlineAgentTaskRequest:
type: object
additionalProperties: false
required:
- goal
- instructions
- success_criteria
- idempotency_key
properties:
task_id:
type: string
goal:
type: string
instructions:
type: string
success_criteria:
type: array
items: {}
required_capabilities:
type: array
items:
type: string
constraints:
type: object
additionalProperties: true
metadata:
type: object
additionalProperties: true
deadline:
type: string
format: date-time
priority:
type: integer
minimum: -100
maximum: 100
max_attempts:
type: integer
minimum: 1
maximum: 20
idempotency_key:
type: string
paths:
/api/base:
get:
operationId: getBaseline
summary: Read current AIOS baseline
responses:
'200':
description: OK
/api/ver:
get:
operationId: listVersions
summary: List GitOps versions
responses:
'200':
description: OK
/api/diff:
get:
operationId: getDiff
summary: Read Git diff between refs
parameters:
- in: query
name: from
schema:
type: string
default: HEAD~1
- in: query
name: to
schema:
type: string
default: HEAD
responses:
'200':
description: OK
/api/patch:
get:
operationId: listPatches
summary: List patch records
responses:
'200':
description: OK
post:
operationId: createPatch
summary: Create a patch branch and commit metadata
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PatchRequest'
responses:
'200':
description: Patch branch committed
'401':
description: Unauthorized
'403':
description: Forbidden
x-openai-isConsequential: true
/api/model:
get:
operationId: readModelRegistry
summary: Read visible model registry and priority
responses:
'200':
description: OK
post:
operationId: registerModel
summary: Register a custom model; API keys are stored as runtime secrets and Git receives secret_ref
only
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ModelRequest'
responses:
'200':
description: Model registered and committed
'401':
description: Unauthorized
'403':
description: Forbidden
x-openai-isConsequential: true
/api/run:
get:
operationId: getRuntimeStatus
summary: Read runtime status
responses:
'200':
description: OK
/api/branch:
post:
operationId: createBranch
summary: Create a non-main branch and commit branch metadata
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BranchRequest'
responses:
'200':
description: Branch created
'401':
description: Unauthorized
'403':
description: Forbidden
x-openai-isConsequential: true
/api/file:
post:
operationId: commitFile
summary: Write a file and create a Git commit on a non-main branch
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FileCommitRequest'
responses:
'200':
description: File committed
'400':
description: Secret rejected or invalid path
'401':
description: Unauthorized
x-openai-isConsequential: true
/api/commit-file:
post:
operationId: commitFileAtomic
summary: Atomically write one allowlisted file, commit, push, and verify using Token Policy V2
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AtomicFileCommitRequest'
responses:
'200':
description: Completed or idempotently replayed
'400':
description: Invalid canonical request
'401':
description: Missing
invalid: null
expired: null
or revoked Token Policy V2 grant: null
'403':
description: Audience
source: null
branch: null
operation: null
path: null
or policy denied: null
'409':
description: Idempotency conflict or manual review required
'413':
description: Request too large
'503':
description: Push or remote verification unavailable; recoverable
x-openai-isConsequential: true
/api/commit:
post:
operationId: commitChanges
summary: Commit existing workspace changes on a non-main branch
security:
- bearerAuth: []
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/CommitRequest'
responses:
'200':
description: Commit result
'401':
description: Unauthorized
x-openai-isConsequential: true
/api/model-priority:
put:
operationId: setModelPriority
summary: Set model priority and commit policy file
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ModelPriorityRequest'
responses:
'200':
description: Priority updated and committed
'401':
description: Unauthorized
x-openai-isConsequential: true
/api/nl:
post:
operationId: planNaturalLanguageGitOpsAction
summary: Convert natural language to a safe committed plan without executing V-level changes
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NLRequest'
responses:
'200':
description: Safe GitOps plan
'401':
description: Unauthorized
x-openai-isConsequential: true
x-aios-schema-reconciliation:
source_schema: docs/gitops/openapi-chatgpt-actions.yaml
public_runtime_schema: docs/gitops/openapi-chatgpt-actions-public-runtime-2e599646.yaml
trust_domain: online-gitops-task
operation_count: 14
public_action_import: true
File diff suppressed because it is too large Load Diff
@@ -1,117 +0,0 @@
# Server-local governed immutable Host seed executor
Date: 2026-08-22
## Goal
Provide a server-local, fail-closed way to publish an already validated
immutable control-plane candidate without depending on Windows, an external
Agent worker, SSH, or an arbitrary root command channel.
The executor is deliberately narrower than deployment:
- it reads only the authoritative local Git object database;
- it consumes one fixed request ref and path;
- it accepts only `HOST_SEED_IMMUTABLE_CANDIDATE`;
- it requires a high-risk governed-task binding whose description binds the
exact candidate intent *and* authorization expiry;
- it materializes `base_oid + candidate.patch`, verifies every release-member
SHA against the candidate manifest, and verifies the candidate Machine Goal;
- it atomically publishes one immutable directory under `/opt/aios/releases`;
- it never starts or restarts services, starts the Projector, creates the fixed
continuation Task/Job, switches Production, or writes `main`, tags, or the
baseline.
## Canonical server path
```text
explicit Owner HOST_SEED authorization
-> high-risk governed authorization task binding
-> governed request commit on refs/heads/agent/host-seed-authorization-requests
-> root timer polls local authoritative Git
-> validate exact intent, expiry, binding SHA, candidate evidence
-> read payload commit from local Git
-> shared disposable clone at exact base
-> apply declared patch
-> verify exact changed set + all release-member bytes
-> atomic immutable publish
-> durable sanitized receipt
```
The Host seed request is not the fixed continuation Task. The fixed
continuation Task remains a separate later gate.
## Trust and authorization binding
The request contains an immutable `intent` with:
- candidate / release id;
- base OID and payload commit;
- fixed patch, manifest, Machine Goal and bootstrap paths;
- validation artifact SHA;
- Manifest, Machine Goal, release-tree and bootstrap SHAs;
- exact target Host identity.
`intent_sha256` is the canonical SHA of that object. Authorization adds
`task_id` and `expires_at`; those are hashed into `authorization_sha256`.
The server-side governed-task binding must contain:
```text
risk=high
description=HOST_SEED_AUTHORIZATION:<authorization_sha256>
target_branch=agent/host-seed-authorization-...
```
The request also binds the exact byte SHA of that server-side binding. This
prevents extending the authorization window or changing the candidate without
creating a new governed authorization binding.
The executor additionally requires the binding file to be a non-symlink
regular file, not group/world writable, and owned by the configured Bridge
service account in production.
## Delivery model
The live delivery payload is server-native Git data, not the external ZIP.
The payload commit carries:
```text
control-plane/candidates/<candidate>/candidate.patch
control-plane/candidates/<candidate>/implementation_manifest_*.json
control-plane/candidates/<candidate>/machine_goal_*.json
control-plane/candidates/<candidate>/aios_control_plane_immutable_seed_*.py
```
The external ZIP SHA remains bound as validation evidence through Machine
Goal, but the executor records `artifact_delivery_used=false`: it does not
claim that ZIP bytes were copied to the Host.
The release tree itself is reconstructed and verified from authoritative Git
objects plus the declared patch.
## Idempotency and recovery
Before publishing, the executor writes a durable `PENDING` operation record
keyed by the exact intent SHA. The final receipt is `EXECUTED`.
If the immutable release directory already exists, every member must match the
manifest before the operation is accepted as idempotent. Any drift fails
closed.
A missing request ref is a normal `NO_REQUEST` poll and has no side effects.
## Systemd confinement
The service:
- runs as root only because immutable release publication requires it;
- has no network access;
- reads authoritative Git and Bridge task bindings read-only;
- can write only its private state root and `/opt/aios/releases`;
- has no service-control, Projector, Task, Git-ref, Production, tag or baseline
operation in its code path.
The timer polls every 30 seconds. Installing/enabling this executor is itself a
separate governed control-plane deployment. Installing it does **not** grant a
Host seed authorization and does not seed any candidate while the fixed
request ref is absent.
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
from pathlib import Path
import copy,hashlib,importlib.util,json,sys,yaml
R=Path(__file__).resolve().parents[1];D=R/'docs/gitops';M={'get','post','put','patch','delete','head','options','trace'}
N=['openapi-chatgpt-actions-governed-release.yaml','openapi-chatgpt-actions-online-gitops-task.yaml','openapi-chatgpt-actions-online-agent-task.yaml']
def die(s):raise AssertionError(s)
def load(p):
x=yaml.safe_load(Path(p).read_text());
if not isinstance(x,dict):die('yaml mapping required:'+str(p))
return x
def op(d,label):
r={}
for route,ms in d.get('paths',{}).items():
if not isinstance(ms,dict) or not isinstance(route,str) or not route.startswith('/'):die(label+': paths')
for method,x in ms.items():
if method.lower() in M:
if not isinstance(x,dict) or not x.get('operationId'):die(label+': operationId')
i=x['operationId'];
if i in r:die(label+': duplicate '+i)
r[i]={'route':route,'method':method.upper(),'security':copy.deepcopy(x.get('security',d.get('security',[])))}
return r
def st(x):return json.dumps(x,sort_keys=True,separators=(',',':'))
def refs(x,c,label):
if isinstance(x,dict):
if isinstance(x.get('$ref'),str) and x['$ref'].startswith('#/components/'):
z={'components':c}
for k in x['$ref'][2:].split('/'):
if not isinstance(z,dict) or k not in z:die(label+': unresolved '+x['$ref'])
z=z[k]
for v in x.values():refs(v,c,label)
elif isinstance(x,list):
for v in x:refs(v,c,label)
def main():
s,p=load(D/'openapi-chatgpt-actions.yaml'),load(D/'openapi-chatgpt-actions-public-runtime-2e599646.yaml');so,po=op(s,'source'),op(p,'public')
if (len(so),len(po))!=(31,30) or s.get('servers')!=p.get('servers'):die('input count/server drift')
for i in so.keys()&po.keys():
if any(st(so[i][k])!=st(po[i][k]) for k in ('route','method','security')):die('common drift '+i)
u={**so,**po}
if len(u)!=46:die('union count')
z={}
for n in N:
d=load(D/n)
if not str(d.get('openapi','')).startswith('3.') or d.get('servers')!=s['servers'] or not isinstance(d.get('components'),dict):die('openapi structure '+n)
refs(d.get('paths'),d['components'],n); q=op(d,n)
if not 1<=len(q)<=30 or z.keys()&q.keys():die('limit/duplicate '+n)
z.update(q)
if set(z)!=set(u):die('split coverage')
for i in u:
if any(st(z[i][k])!=st(u[i][k]) for k in ('route','method','security')):die('route/auth '+i)
m=json.loads((D/'chatgpt-action-schema-capability-manifest.json').read_text());r={x.get('operation_id'):x for x in m.get('operations',[]) if isinstance(x,dict)}
if m.get('union_operation_count')!=46 or set(r)!=set(u):die('manifest coverage')
for i,x in u.items():
a=r[i]
if a.get('server_route')!=x['route'] or a.get('method')!=x['method'] or st(a.get('declared_openapi_security'))!=st(x['security']):die('manifest route/auth '+i)
if bool(a.get('source_origin'))!=(i in so) or bool(a.get('public_origin'))!=(i in po):die('manifest origins '+i)
A={'createOnlineAgentTask','registerOnlineAgent','getOnlineAgentTask','getOnlineAgentTaskEvents','verifyOnlineAgentTask','cancelOnlineAgentTask'}
if set(m.get('scope_provenance',{}).get('agent_task_control',{}).get('operation_ids',[]))!=A:die('agent ids')
for i in A:
if r[i].get('required_scope')!='agent-task:control' or r[i].get('schema')!='docs/gitops/openapi-chatgpt-actions-online-agent-task.yaml':die('agent scope '+i)
if importlib.util.find_spec('openapi_spec_validator'):
from openapi_spec_validator import validate_spec
for n in N:validate_spec(load(D/n))
print('formal_validator=PASS')
else:print('formal_validator=NOT_INSTALLED')
print('yaml_parse=PASS');print('openapi_structure=PASS');print('source_coverage=PASS');print('public_runtime_coverage=PASS');print('union_route_auth_reconciliation=PASS');print('capability_manifest=PASS');print('schema_split_validation=PASS')
try:main()
except (AssertionError,OSError,ValueError,yaml.YAMLError,json.JSONDecodeError) as e:print('schema_split_validation=FAIL: '+str(e),file=sys.stderr);sys.exit(1)
@@ -0,0 +1,9 @@
import subprocess,sys,unittest
from pathlib import Path
R=Path(__file__).resolve().parents[1]
class SchemaSplitTest(unittest.TestCase):
def test_reconciliation(self):
x=subprocess.run([sys.executable,str(R/'scripts/validate_chatgpt_action_schema_split.py')],cwd=R,text=True,capture_output=True)
self.assertEqual(x.returncode,0,x.stdout+x.stderr)
for s in ('source_coverage=PASS','public_runtime_coverage=PASS','union_route_auth_reconciliation=PASS','schema_split_validation=PASS'):self.assertIn(s,x.stdout)
if __name__=='__main__':unittest.main()
@@ -1,328 +0,0 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
import hashlib
import importlib.util
import json
from pathlib import Path
import shutil
import subprocess
import pytest
MODULE_PATH = Path(__file__).parents[1] / "active-runtime" / "app" / "aios_control_plane_host_seed_executor.py"
spec = importlib.util.spec_from_file_location("host_seed_executor", MODULE_PATH)
m = importlib.util.module_from_spec(spec)
assert spec and spec.loader
import sys
sys.modules[spec.name] = m
spec.loader.exec_module(m)
def run(*args: str, cwd: Path) -> str:
return subprocess.check_output(args, cwd=cwd, text=True).strip()
def sha(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def canonical(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
def setup_case(tmp_path: Path, *, risk: str = "high", tamper_bootstrap: bool = False):
repo = tmp_path / "src"
bare = tmp_path / "repo.git"
repo.mkdir()
run("git", "init", "-b", "main", cwd=repo)
run("git", "config", "user.email", "test@example.invalid", cwd=repo)
run("git", "config", "user.name", "test", cwd=repo)
(repo / "a.txt").write_text("base\n", encoding="utf-8")
(repo / "bin.sh").write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
(repo / "bin.sh").chmod(0o755)
run("git", "add", "-A", cwd=repo)
run("git", "commit", "-m", "base", cwd=repo)
base = run("git", "rev-parse", "HEAD", cwd=repo)
run("git", "clone", "--bare", str(repo), str(bare), cwd=tmp_path)
(repo / "a.txt").write_text("changed\n", encoding="utf-8")
(repo / "c.txt").write_text("added\n", encoding="utf-8")
run("git", "add", "-N", "c.txt", cwd=repo)
patch = subprocess.check_output(["git", "diff", "--binary", base], cwd=repo)
after = {
"a.txt": sha((repo / "a.txt").read_bytes()),
"bin.sh": sha((repo / "bin.sh").read_bytes()),
"c.txt": sha((repo / "c.txt").read_bytes()),
}
package_members = {f"payload/repo/after/{k}": v for k, v in after.items()}
release_tree = sha(json.dumps({k: package_members[k] for k in sorted(package_members)}, sort_keys=True, separators=(",", ":")).encode())
manifest = {
"schema": "aios.control-plane-immutable-candidate.v6",
"candidate": "V9",
"repository": "aios/aios",
"base_oid": base,
"release_id": "control-plane-test-v9",
"changed_file_count": 2,
"changed_files": [
{"path": "a.txt", "status": "modified", "after_sha256": after["a.txt"]},
{"path": "c.txt", "status": "added", "after_sha256": after["c.txt"]},
],
"release_member_count": 3,
"package_members": package_members,
"release_tree_sha256": release_tree,
}
manifest_raw = canonical(manifest)
bootstrap = b"#!/usr/bin/env python3\nprint('seed helper evidence only')\n"
bootstrap_sha = sha(bootstrap)
artifact_sha = "a" * 64
goal = {
"schema": "aios.control-plane-immutable-candidate-goal.v6",
"candidate": "V9",
"repository": "aios/aios",
"base_oid": base,
"release_id": "control-plane-test-v9",
"artifact_sha256": artifact_sha,
"manifest_sha256": sha(manifest_raw),
"release_tree_sha256": release_tree,
"seed_bootstrap_sha256": bootstrap_sha,
"host_seed_authorized": False,
}
goal_raw = canonical(goal)
# Reset the source worktree to base; delivery files live in a separate commit.
run("git", "reset", "--hard", base, cwd=repo)
delivery = repo / "control-plane" / "candidates" / "v9"
delivery.mkdir(parents=True)
(delivery / "candidate.patch").write_bytes(patch)
(delivery / "implementation_manifest_v9.json").write_bytes(manifest_raw)
(delivery / "machine_goal_v9.json").write_bytes(goal_raw)
(delivery / "aios_control_plane_immutable_seed_v9.py").write_bytes(
b"tampered\n" if tamper_bootstrap else bootstrap
)
run("git", "add", "-A", cwd=repo)
run("git", "commit", "-m", "candidate payload", cwd=repo)
payload_commit = run("git", "rev-parse", "HEAD", cwd=repo)
run("git", "push", str(bare), f"{payload_commit}:refs/heads/agent/candidate-payload", cwd=repo)
intent = {
"candidate": "V9",
"release_id": "control-plane-test-v9",
"base_oid": base,
"payload_commit": payload_commit,
"patch_path": "control-plane/candidates/v9/candidate.patch",
"manifest_path": "control-plane/candidates/v9/implementation_manifest_v9.json",
"machine_goal_path": "control-plane/candidates/v9/machine_goal_v9.json",
"seed_bootstrap_path": "control-plane/candidates/v9/aios_control_plane_immutable_seed_v9.py",
"artifact_sha256": artifact_sha,
"manifest_sha256": sha(manifest_raw),
"machine_goal_sha256": sha(goal_raw),
"release_tree_sha256": release_tree,
"seed_bootstrap_sha256": bootstrap_sha,
"target_host": "cn-network-mkt",
}
intent_digest = sha(canonical(intent))
task_id = "host-seed-authz-v9-cn-network-mkt"
now = datetime(2026, 8, 22, 12, 0, tzinfo=timezone.utc)
expires_text = (now + timedelta(minutes=30)).isoformat().replace("+00:00", "Z")
authorization_digest = sha(canonical({
"task_id": task_id,
"intent_sha256": intent_digest,
"expires_at": expires_text,
}))
binding = {
"task_id": task_id,
"repository": "aios/aios",
"base_oid": payload_commit,
"target_branch": "agent/host-seed-authorization-v9-cn-network-mkt",
"risk": risk,
"description": "HOST_SEED_AUTHORIZATION:" + authorization_digest,
"idempotency_key": "host-seed-authz-v9-1",
"expected_branch_oid": payload_commit,
"branch_mode": "CREATE_NEW",
"generation": 1,
"job_id": "job-" + "1" * 32,
}
binding_root = tmp_path / "bindings"
binding_root.mkdir()
binding_raw = json.dumps(binding, sort_keys=True).encode()
(binding_root / f"{task_id}.json").write_bytes(binding_raw)
(binding_root / f"{task_id}.json").chmod(0o600)
request = {
"schema": "aios.control-plane-host-seed-request.v1",
"operation": "HOST_SEED_IMMUTABLE_CANDIDATE",
"repository": "aios/aios",
"intent": intent,
"authorization": {
"task_id": task_id,
"binding_sha256": sha(binding_raw),
"expires_at": expires_text,
"intent_sha256": intent_digest,
"authorization_sha256": authorization_digest,
},
}
request_path = repo / "control-plane" / "host-seed" / "request.json"
request_path.parent.mkdir(parents=True, exist_ok=True)
request_path.write_bytes(canonical(request))
run("git", "add", str(request_path.relative_to(repo)), cwd=repo)
run("git", "commit", "-m", "host seed request", cwd=repo)
request_commit = run("git", "rev-parse", "HEAD", cwd=repo)
run("git", "push", str(bare), f"{request_commit}:{m.REQUEST_REF}", cwd=repo)
config = m.Config(
git_dir=bare,
task_binding_root=binding_root,
state_root=tmp_path / "state",
release_root=tmp_path / "releases",
host_identity="cn-network-mkt",
task_binding_owner=None,
)
return config, now, after, request
def test_execute_publishes_exact_release_and_is_idempotent(tmp_path: Path):
config, now, after, request = setup_case(tmp_path)
receipt = m.execute(config, now=now, require_root=False)
assert receipt["state"] == "EXECUTED"
assert receipt["service_mutation"] is False
assert receipt["service_restart"] is False
assert receipt["projector_started"] is False
assert receipt["task_creation"] is False
assert receipt["production_write"] is False
final = config.release_root / "control-plane-test-v9"
assert final.is_dir() and not final.is_symlink()
assert final.stat().st_mode & 0o777 == 0o755
assert sha((final / "a.txt").read_bytes()) == after["a.txt"]
assert sha((final / "bin.sh").read_bytes()) == after["bin.sh"]
assert sha((final / "c.txt").read_bytes()) == after["c.txt"]
assert (final / "bin.sh").stat().st_mode & 0o111
again = m.execute(config, now=now, require_root=False)
assert again == receipt
def test_execute_rejects_non_high_risk_authorization(tmp_path: Path):
config, now, _, _ = setup_case(tmp_path, risk="engineering")
with pytest.raises(m.HostSeedError, match="AUTHORIZATION_RISK_MISMATCH"):
m.execute(config, now=now, require_root=False)
assert not config.release_root.exists()
def test_execute_rejects_tampered_seed_bootstrap(tmp_path: Path):
config, now, _, _ = setup_case(tmp_path, tamper_bootstrap=True)
with pytest.raises(m.HostSeedError, match="SEED_BOOTSTRAP_HASH_MISMATCH"):
m.execute(config, now=now, require_root=False)
assert not config.release_root.exists()
def test_request_rejects_wrong_host(tmp_path: Path):
config, now, _, _ = setup_case(tmp_path)
config = m.Config(
git_dir=config.git_dir,
task_binding_root=config.task_binding_root,
state_root=config.state_root,
release_root=config.release_root,
host_identity="other-host",
task_binding_owner=None,
)
with pytest.raises(m.HostSeedError, match="TARGET_HOST_MISMATCH"):
m.execute(config, now=now, require_root=False)
def test_existing_release_drift_is_fail_closed(tmp_path: Path):
config, now, _, _ = setup_case(tmp_path)
first = m.execute(config, now=now, require_root=False)
assert first["state"] == "EXECUTED"
(config.release_root / "control-plane-test-v9" / "a.txt").write_text("drift\n")
# remove durable receipt so existing-release verification path is exercised.
for p in (config.state_root / "receipts").glob("*.json"):
p.unlink()
with pytest.raises(m.HostSeedError, match="EXISTING_RELEASE_HASH_MISMATCH"):
m.execute(config, now=now, require_root=False)
def test_no_request_ref_is_safe_noop(tmp_path: Path):
repo = tmp_path / "src"
bare = tmp_path / "repo.git"
repo.mkdir()
run("git", "init", "-b", "main", cwd=repo)
run("git", "config", "user.email", "test@example.invalid", cwd=repo)
run("git", "config", "user.name", "test", cwd=repo)
(repo / "a").write_text("x\n")
run("git", "add", "a", cwd=repo)
run("git", "commit", "-m", "base", cwd=repo)
run("git", "clone", "--bare", str(repo), str(bare), cwd=tmp_path)
cfg = m.Config(
git_dir=bare,
task_binding_root=tmp_path / "bindings",
state_root=tmp_path / "state",
release_root=tmp_path / "releases",
host_identity="cn-network-mkt",
task_binding_owner=None,
)
result = m.execute(cfg, now=datetime(2026, 8, 22, tzinfo=timezone.utc), require_root=False)
assert result["state"] == "NO_REQUEST"
assert result["service_mutation"] is False
assert result["task_creation"] is False
assert not cfg.release_root.exists()
def test_authorization_expiry_cannot_be_extended_without_new_binding(tmp_path: Path):
config, now, _, _ = setup_case(tmp_path)
# Rewrite only the request expiry and recompute the request commit, but keep
# the original governed-task binding description. The authorization digest
# therefore no longer matches the binding and must fail closed.
repo = tmp_path / "src"
req_path = repo / "control-plane" / "host-seed" / "request.json"
value = json.loads(req_path.read_text())
value["authorization"]["expires_at"] = (now + timedelta(minutes=45)).isoformat().replace("+00:00", "Z")
value["authorization"]["authorization_sha256"] = sha(canonical({
"task_id": value["authorization"]["task_id"],
"intent_sha256": value["authorization"]["intent_sha256"],
"expires_at": value["authorization"]["expires_at"],
}))
req_path.write_bytes(canonical(value))
run("git", "add", str(req_path.relative_to(repo)), cwd=repo)
run("git", "commit", "-m", "tamper expiry", cwd=repo)
new_commit = run("git", "rev-parse", "HEAD", cwd=repo)
run("git", "push", "--force", str(config.git_dir), f"{new_commit}:{m.REQUEST_REF}", cwd=repo)
with pytest.raises(m.HostSeedError, match="AUTHORIZATION_DESCRIPTION_MISMATCH"):
m.execute(config, now=now, require_root=False)
def test_systemd_contract_has_no_network_or_implicit_activation():
root = Path(__file__).parents[1]
service = (root / "deploy/systemd/aios-control-plane-host-seed-executor.service").read_text()
timer = (root / "deploy/systemd/aios-control-plane-host-seed-executor.timer").read_text()
assert "User=root" in service
assert "IPAddressDeny=any" in service
assert "RestrictAddressFamilies=AF_UNIX" in service
assert "ReadOnlyPaths=/data/aios-gitops/repo.git" in service
assert "ReadWritePaths=/opt/aios/releases" in service
assert "systemctl start" not in service
assert "projector" not in service.lower()
assert "OnUnitActiveSec=30s" in timer
def test_release_root_symlink_is_rejected(tmp_path: Path):
config, now, _, _ = setup_case(tmp_path)
real = tmp_path / "real-releases"
real.mkdir()
config.release_root.symlink_to(real, target_is_directory=True)
with pytest.raises(m.HostSeedError, match="RELEASE_ROOT_SYMLINK_REJECTED"):
m.execute(config, now=now, require_root=False)
def test_install_and_rollback_scripts_have_valid_bash_syntax():
root = Path(__file__).parents[1]
for relative in (
"deploy/install/install_control_plane_host_seed_executor.sh",
"deploy/install/rollback_control_plane_host_seed_executor.sh",
):
subprocess.run(
["/usr/bin/bash", "-n", str(root / relative)],
check=True,
text=True,
capture_output=True,
)