Compare commits

...
Author SHA1 Message Date
owner 9adec87c4b feat(governed): Release Source Admission (AIOS-BASELINE-1.1-RELEASE-SOURCE-ADMISSION)
Make governed release source closure explicit so a pushed governed candidate
can enter the authoritative Git/release closure without manual runtime
overwrite or fixed-recipe bypass.

- Gitea machine transport: add _safe_branch() and exact OID/branch verification.
- release_source_admission.py (new): admit a governed source by verifying remote
  branch -> exact OID, fetching only to refs/aios/release-sources/<OID>,
  validating authoritative main ancestry + tree OID, and writing a policy-bound,
  HMAC-authenticated immutable admission record (source_admission_hmac).
- trusted candidate builder: read-only admission verifier, remains
  PrivateNetwork / read-only; missing admission surfaces SOURCE_ADMISSION_REQUIRED.
- Model API / release saga: consume admission records and return machine-actionable
  SOURCE_ADMISSION_REQUIRED (not NOT_FOUND) when a non-fixed source is unadmitted.
- systemd: add aios-release-source-admission.service (oneshot, root/gitops,
  AIOS_GITOPS_WRITES_ENABLED=false, independent source_admission_hmac credential);
  wire source_admission_hmac into trusted builder; update staging unit.
- Fixed V2R2C2/V2R2C3 recipes are not overridden by admission.
- Tests: new release_source_admission + adjusted builder/model-api/live-executor/
  readiness tests.

Verified: 13-file after_sha256 byte-exact; py_compile PASS;
Linux 326 targeted passed / 0 failed; acceptance 165; regression 1401;
systemd-analyze verify 3/3 PASS.

Base: a320ea3 (agent/baseline-1.1-principal-recovery-20260908).
2026-09-08 11:44:09 +08:00
13 changed files with 1372 additions and 18 deletions
@@ -24,6 +24,25 @@ def _safe_oid(value: str, field: str) -> str:
return value
def _safe_branch(value: str) -> str:
branch = str(value or "")
if (
not branch
or len(branch) > 240
or branch != branch.strip()
or branch.startswith(("/", "-", "."))
or branch.endswith(("/", ".", ".lock"))
or ".." in branch
or "//" in branch
or "@{" in branch
or "\\" in branch
or any(part in {"", ".", ".."} for part in branch.split("/"))
or any(ch not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._/-" for ch in branch)
):
raise GiteaTransportError("BRANCH_INVALID")
return branch
@dataclass(slots=True)
class GiteaMachineTransport:
api_base: str
@@ -91,6 +110,7 @@ class GiteaMachineTransport:
return self.repository_info()
def read_branch(self, branch: str) -> str | None:
branch = _safe_branch(branch)
owner, name = self.repository_id.split("/", 1)
try:
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/branches/{quote(branch, safe='/')}")[1]
@@ -136,6 +156,99 @@ class GiteaMachineTransport:
finally:
askpass.unlink(missing_ok=True)
def fetch_branch_exact(self, branch: str, expected_oid: str) -> str:
"""Fetch one exact remote branch tip into a quarantine ref.
This never updates ``main`` or any caller-visible branch. The remote
ref is read before and after the fetch so a concurrent branch move
cannot be silently admitted.
"""
branch = _safe_branch(branch)
expected_oid = _safe_oid(expected_oid, "expected_oid")
remote_before = self.read_branch(branch)
if remote_before != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MISMATCH")
target_ref = f"refs/aios/release-sources/{expected_oid}"
current = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
if current.returncode == 0 and current.stdout.strip().lower() == expected_oid:
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
askpass, additions = self._askpass()
environment = os.environ.copy()
environment.update(additions)
try:
result = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"fetch",
"--no-tags",
"--no-write-fetch-head",
self.remote_url,
f"refs/heads/{branch}:{target_ref}",
],
text=True,
capture_output=True,
check=False,
env=environment,
timeout=self.timeout,
)
if result.returncode != 0:
raise GiteaTransportError("GITEA_SOURCE_FETCH_FAILED")
finally:
askpass.unlink(missing_ok=True)
local = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
local_oid = local.stdout.strip().lower()
if local.returncode != 0 or local_oid != expected_oid:
subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"update-ref",
"-d",
target_ref,
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
raise GiteaTransportError("GITEA_SOURCE_FETCH_MISMATCH")
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str:
_safe_oid(expected_oid, "expected_oid")
_safe_oid(new_oid, "new_oid")
@@ -2,6 +2,7 @@
from __future__ import annotations
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, urllib.request, uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from dataclasses import replace
from pathlib import Path
from baseline_authority import BaselineAuthority, BaselineAuthorityError
from silent_release_controller import ReleaseCandidate
@@ -114,6 +115,9 @@ TRUSTED_BUILDER_SOCKET = Path(os.getenv(
TRUSTED_BUILDER_PUBLIC_KEYRING = Path(os.getenv(
'AIOS_TRUSTED_CANDIDATE_BUILDER_PUBLIC_KEYRING', ''
))
TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT = str(os.getenv(
'AIOS_TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT', ''
)).strip()
TRUSTED_RELEASE_POLICY = Path(os.getenv(
'AIOS_SILENT_RELEASE_TRUSTED_POLICY',
str(WS / 'config' / 'gitops' / 'silent-release-policy.json'),
@@ -253,6 +257,14 @@ class _HostTrustedCandidateBuilderAdapter:
nonce=secrets.token_hex(32),
)
except BuilderError as exc:
code = str(exc)
if code in {'SOURCE_ADMISSION_REQUIRED', 'RECIPE_UNKNOWN'} or (
code == 'REQUEST_RELEASE_BINDING_INVALID'
and not intent.get('version')
):
raise CapabilityAuthorizationError(
'release source admission required'
) from exc
raise TrustedCandidateBuilderUnavailable(
'trusted builder request failed'
) from exc
@@ -290,6 +302,7 @@ def _build_silent_release_service() -> SilentReleaseActionService:
builder_attestation_verifier = None
trusted_policy = None
builder_readiness_probe = None
source_admission_resolver = None
if TRUSTED_BUILDER_ENABLED:
# Every path is opt-in. In particular, this API receives its own
# client HMAC credential, never the builder's signing private key.
@@ -317,6 +330,31 @@ def _build_silent_release_service() -> SilentReleaseActionService:
client_hmac = (
TRUSTED_BUILDER_CREDENTIALS_DIRECTORY / 'builder_hmac'
).read_bytes().strip()
if TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT:
from release_source_admission import ReleaseSourceAdmissionStore
try:
source_admission_hmac = (
TRUSTED_BUILDER_CREDENTIALS_DIRECTORY
/ 'source_admission_hmac'
).read_bytes().strip()
except OSError as exc:
raise CapabilityAuthorizationError(
'release source admission credential is unavailable'
) from exc
source_store = ReleaseSourceAdmissionStore(
Path(TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT),
repository=builder_config.repository,
policy_sha256=builder_config.policy_sha256,
hmac_key=source_admission_hmac,
expected_owner_uid=0,
writable=False,
)
builder_config = replace(
builder_config,
source_admission_resolver=source_store.resolve_recipe,
)
source_admission_resolver = source_store.lookup_source
client_keyring = BuilderKeyring(builder_hmac=client_hmac)
client = UnixTrustedCandidateBuilderClient(
builder_config,
@@ -365,6 +403,7 @@ def _build_silent_release_service() -> SilentReleaseActionService:
builder_attestation_verifier=builder_attestation_verifier,
trusted_policy=trusted_policy,
builder_readiness_probe=builder_readiness_probe,
source_admission_resolver=source_admission_resolver,
allow_legacy_candidate_input=False,
expected_repository=REPOSITORY_IDENTITY,
policy_sha256=policy_sha,
@@ -15,7 +15,7 @@ import struct
import subprocess
import time
from datetime import datetime, timezone
from dataclasses import dataclass
from dataclasses import dataclass, replace
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol
@@ -133,6 +133,7 @@ class TrustedCandidateBuilderConfig:
recipes: Mapping[str, Mapping[str, str]]
governed_recipes: Mapping[str, Mapping[str, str]]
policy_sha256: str
source_admission_resolver: Callable[[str, str | None, str], Mapping[str, str]] | None = None
@classmethod
def from_policy(cls, policy: Mapping[str, Any]) -> "TrustedCandidateBuilderConfig":
@@ -227,14 +228,63 @@ class TrustedCandidateBuilderConfig:
return recipe
def resolve_recipe(self, version: str, candidate_id: str | None, source_commit: str) -> Mapping[str, str]:
recipe = self.fixed_recipe(version)
# Intent-only callers do not choose a candidate identity. The
# policy-owned recipe supplies it; explicit worker requests are
# still checked against the same immutable recipe.
effective_candidate_id = str(candidate_id or recipe.get("candidate_id") or "")
if {"version": recipe.get("version"), "candidate_id": recipe.get("candidate_id"), "source_commit": recipe.get("source_commit")} != {"version": version, "candidate_id": effective_candidate_id, "source_commit": source_commit}:
recipe = self.recipes.get(version)
if recipe is not None:
resolved = self.fixed_recipe(version)
# Fixed/governed-derived policy recipes remain immutable. A
# source admission can never override one of these identities.
effective_candidate_id = str(candidate_id or resolved.get("candidate_id") or "")
if {
"version": resolved.get("version"),
"candidate_id": resolved.get("candidate_id"),
"source_commit": resolved.get("source_commit"),
} != {
"version": version,
"candidate_id": effective_candidate_id,
"source_commit": source_commit,
}:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
return resolved
resolver = self.source_admission_resolver
if resolver is None:
raise BuilderError("RECIPE_UNKNOWN")
try:
admitted = dict(resolver(version, candidate_id, source_commit))
except Exception as exc:
raise BuilderError("SOURCE_ADMISSION_REQUIRED") from exc
required = {
"mode",
"task_id",
"candidate_id",
"base_oid",
"source_commit",
"version",
"purpose",
}
if (
set(admitted) != required
or admitted.get("mode") != "governed-source-admission"
or admitted.get("version") != version
or admitted.get("source_commit") != source_commit
or not isinstance(admitted.get("candidate_id"), str)
or not admitted.get("candidate_id")
):
raise BuilderError("SOURCE_ADMISSION_INVALID")
effective_candidate_id = str(candidate_id or admitted["candidate_id"])
if effective_candidate_id != admitted["candidate_id"]:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
return recipe
for name in ("base_oid", "source_commit"):
value = admitted.get(name)
if (
not isinstance(value, str)
or len(value) != 40
or any(char not in "0123456789abcdef" for char in value.lower())
):
raise BuilderError("SOURCE_ADMISSION_INVALID")
if admitted.get("purpose") != "AIOS_TRUSTED_RELEASE_BUILDER":
raise BuilderError("SOURCE_ADMISSION_INVALID")
return admitted
def _invalid_governed_recipes() -> Mapping[str, Mapping[str, str]]:
@@ -776,6 +826,30 @@ def _main() -> None:
if not credentials:
raise SystemExit("CREDENTIALS_DIRECTORY is required")
keyring = BuilderKeyring.from_credentials_directory(Path(credentials))
source_admission_root = os.environ.get(
"AIOS_TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT", ""
).strip()
if source_admission_root:
from release_source_admission import ReleaseSourceAdmissionStore
try:
source_admission_hmac = (
Path(credentials) / "source_admission_hmac"
).read_bytes().strip()
except OSError as exc:
raise BuilderError("SOURCE_ADMISSION_HMAC_UNAVAILABLE") from exc
source_store = ReleaseSourceAdmissionStore(
Path(source_admission_root),
repository=config.repository,
policy_sha256=config.policy_sha256,
hmac_key=source_admission_hmac,
expected_owner_uid=0,
writable=False,
)
config = replace(
config,
source_admission_resolver=source_store.resolve_recipe,
)
signer = CredentialPathEd25519Signer(
keyring.signing_private_key_path,
key_id=os.environ["AIOS_TRUSTED_BUILDER_KEY_ID"],
@@ -0,0 +1,561 @@
#!/usr/bin/env python3
"""Governed admission of an exact remote Git commit into the release source closure.
The admitter is intentionally separate from the trusted candidate builder:
it may use a narrowly-scoped Gitea read/fetch credential, while the builder
remains PrivateNetwork=true and reads only the authoritative Git object store
plus immutable admission records.
An admission never moves ``main`` and never treats an arbitrary caller-supplied
OID as trusted. It binds:
remote branch ref -> exact full commit OID -> authoritative Git object/tree
-> current authoritative main ancestry -> immutable release recipe metadata.
"""
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import os
import re
import tempfile
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Mapping, Protocol
from authoritative_git import (
AuthoritativeGitError,
authoritative_head_oid,
authoritative_lineage,
run_authoritative_git,
)
from aios_gitea_machine_transport import GiteaMachineTransport, GiteaTransportError
from release_version import validate_release_version
SCHEMA = "aios.release-source-admission.v1"
RECIPE_MODE = "governed-source-admission"
_OID_RE = re.compile(r"^[0-9a-f]{40}$")
_DIGEST_RE = re.compile(r"^[0-9a-f]{64}$")
_IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,199}$")
_BRANCH_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]{0,239}$")
_REF_PREFIX = "refs/heads/"
class ReleaseSourceAdmissionError(RuntimeError):
"""A stable fail-closed source-admission error code."""
def _canonical(value: Mapping[str, Any]) -> bytes:
return json.dumps(
dict(value),
sort_keys=True,
separators=(",", ":"),
ensure_ascii=True,
).encode("utf-8")
def _oid(value: object, field: str) -> str:
text = str(value or "").strip().lower()
if not _OID_RE.fullmatch(text):
raise ReleaseSourceAdmissionError(f"{field.upper()}_INVALID")
return text
def _digest(value: object, field: str) -> str:
text = str(value or "").strip().lower()
if not _DIGEST_RE.fullmatch(text):
raise ReleaseSourceAdmissionError(f"{field.upper()}_INVALID")
return text
def _identifier(value: object, field: str) -> str:
text = str(value or "").strip()
if not _IDENTIFIER_RE.fullmatch(text) or ".." in text or "\x00" in text:
raise ReleaseSourceAdmissionError(f"{field.upper()}_INVALID")
return text
def _source_ref(value: object) -> str:
text = str(value or "").strip()
if not text.startswith(_REF_PREFIX):
raise ReleaseSourceAdmissionError("SOURCE_REF_INVALID")
branch = text[len(_REF_PREFIX):]
if (
not _BRANCH_RE.fullmatch(branch)
or ".." in branch
or "//" in branch
or "@{" in branch
or branch.endswith(("/", ".", ".lock"))
or any(part in {"", ".", ".."} for part in branch.split("/"))
):
raise ReleaseSourceAdmissionError("SOURCE_REF_INVALID")
return text
def _version(value: object) -> str:
try:
return validate_release_version(value)
except (TypeError, ValueError) as exc:
raise ReleaseSourceAdmissionError("VERSION_INVALID") from exc
def _version_slug(version: str) -> str:
value = re.sub(r"[^A-Za-z0-9]+", "-", version.lower()).strip("-")
if not value:
raise ReleaseSourceAdmissionError("VERSION_INVALID")
return value[:80]
def _now() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def _secure_stat(path: Path, *, expected_owner_uid: int | None, directory: bool) -> None:
try:
info = path.lstat()
except OSError as exc:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_STORE_UNAVAILABLE") from exc
if path.is_symlink():
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_STORE_UNSAFE")
if directory and not path.is_dir():
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_STORE_UNSAFE")
if not directory and not path.is_file():
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_STORE_UNSAFE")
if expected_owner_uid is not None and info.st_uid != expected_owner_uid:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_OWNER_MISMATCH")
# Group/other write would allow an untrusted process to forge release input.
if info.st_mode & 0o022:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_PERMISSIONS_INVALID")
def _validate_record(
value: Mapping[str, Any],
*,
repository: str,
policy_sha256: str,
hmac_key: bytes,
) -> dict[str, Any]:
if not isinstance(value, Mapping):
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_RECORD_INVALID")
required = {
"schema",
"admission_id",
"repository",
"task_id",
"version",
"candidate_id",
"base_oid",
"source_ref",
"source_commit",
"remote_ref_oid",
"tree_oid",
"purpose",
"policy_sha256",
"admitted_at",
"mac",
}
if set(value) != required or value.get("schema") != SCHEMA:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_RECORD_INVALID")
record = dict(value)
if _identifier(record["repository"], "repository") != repository:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_REPOSITORY_MISMATCH")
_identifier(record["task_id"], "task_id")
record["version"] = _version(record["version"])
_identifier(record["candidate_id"], "candidate_id")
_identifier(record["purpose"], "purpose")
_identifier(record["admission_id"], "admission_id")
record["base_oid"] = _oid(record["base_oid"], "base_oid")
record["source_commit"] = _oid(record["source_commit"], "source_commit")
record["remote_ref_oid"] = _oid(record["remote_ref_oid"], "remote_ref_oid")
record["tree_oid"] = _oid(record["tree_oid"], "tree_oid")
record["source_ref"] = _source_ref(record["source_ref"])
record["policy_sha256"] = _digest(record["policy_sha256"], "policy_sha256")
if record["policy_sha256"] != policy_sha256:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_POLICY_MISMATCH")
if record["remote_ref_oid"] != record["source_commit"]:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_REMOTE_MISMATCH")
admitted_at = record.get("admitted_at")
if not isinstance(admitted_at, str) or not admitted_at.endswith("Z") or len(admitted_at) < 20:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_TIME_INVALID")
supplied_mac = record.get("mac")
if (
not isinstance(supplied_mac, str)
or not re.fullmatch(r"[0-9a-f]{64}", supplied_mac)
):
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_MAC_INVALID")
unsigned = {key: value for key, value in record.items() if key != "mac"}
expected_mac = hmac.new(hmac_key, _canonical(unsigned), hashlib.sha256).hexdigest()
if not hmac.compare_digest(supplied_mac, expected_mac):
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_MAC_INVALID")
return record
class ReleaseSourceBackend(Protocol):
def authoritative_main_oid(self) -> str: ...
def remote_ref_oid(self, source_ref: str) -> str: ...
def fetch_exact_ref(self, source_ref: str, source_commit: str) -> str: ...
def tree_oid(self, source_commit: str) -> str: ...
def require_lineage(self, base_oid: str, source_commit: str) -> None: ...
class ReleaseSourceAdmissionStore:
"""Durable immutable admission records keyed by exact source commit."""
def __init__(
self,
root: str | os.PathLike[str],
*,
repository: str,
policy_sha256: str,
hmac_key: bytes,
expected_owner_uid: int | None = None,
writable: bool = False,
) -> None:
self.root = Path(root)
self.repository = _identifier(repository, "repository")
self.policy_sha256 = _digest(policy_sha256, "policy_sha256")
if not isinstance(hmac_key, (bytes, bytearray)) or len(hmac_key) < 32:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_HMAC_INVALID")
self.hmac_key = bytes(hmac_key)
self.expected_owner_uid = expected_owner_uid
self.writable = bool(writable)
if self.writable and not self.root.exists():
self.root.mkdir(parents=True, mode=0o750)
if self.root.exists():
_secure_stat(
self.root,
expected_owner_uid=self.expected_owner_uid,
directory=True,
)
def _path(self, source_commit: str) -> Path:
return self.root / f"{_oid(source_commit, 'source_commit')}.json"
def register(self, record: Mapping[str, Any]) -> dict[str, Any]:
if not self.writable:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_STORE_READ_ONLY")
supplied = dict(record)
if "mac" not in supplied:
supplied["mac"] = hmac.new(
self.hmac_key,
_canonical(supplied),
hashlib.sha256,
).hexdigest()
checked = _validate_record(
supplied,
repository=self.repository,
policy_sha256=self.policy_sha256,
hmac_key=self.hmac_key,
)
path = self._path(checked["source_commit"])
encoded = _canonical(checked) + b"\n"
if path.exists():
_secure_stat(
path,
expected_owner_uid=self.expected_owner_uid,
directory=False,
)
try:
existing = path.read_bytes()
except OSError as exc:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_STORE_UNAVAILABLE") from exc
if existing == encoded:
return dict(checked)
try:
previous = _validate_record(
json.loads(existing),
repository=self.repository,
policy_sha256=self.policy_sha256,
hmac_key=self.hmac_key,
)
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_RECORD_INVALID") from exc
comparable_previous = {
key: value
for key, value in previous.items()
if key not in {"admitted_at", "mac"}
}
comparable_checked = {
key: value
for key, value in checked.items()
if key not in {"admitted_at", "mac"}
}
if comparable_previous == comparable_checked:
return dict(previous)
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_CONFLICT")
self.root.mkdir(parents=True, exist_ok=True, mode=0o750)
_secure_stat(
self.root,
expected_owner_uid=self.expected_owner_uid,
directory=True,
)
fd, raw_name = tempfile.mkstemp(
prefix=f".{checked['source_commit']}.",
suffix=".tmp",
dir=self.root,
)
temp_path = Path(raw_name)
try:
os.fchmod(fd, 0o640)
with os.fdopen(fd, "wb", closefd=True) as handle:
handle.write(encoded)
handle.flush()
os.fsync(handle.fileno())
os.replace(temp_path, path)
os.chmod(path, 0o640)
directory_fd = os.open(self.root, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(directory_fd)
finally:
os.close(directory_fd)
except Exception:
try:
os.close(fd)
except OSError:
pass
temp_path.unlink(missing_ok=True)
raise
_secure_stat(
path,
expected_owner_uid=self.expected_owner_uid,
directory=False,
)
return dict(checked)
def lookup_source(self, source_commit: str) -> dict[str, Any] | None:
path = self._path(source_commit)
if not path.exists():
return None
_secure_stat(
path,
expected_owner_uid=self.expected_owner_uid,
directory=False,
)
try:
raw = path.read_bytes()
if len(raw) > 64 * 1024:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_RECORD_INVALID")
decoded = json.loads(raw)
except ReleaseSourceAdmissionError:
raise
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_RECORD_INVALID") from exc
return _validate_record(
decoded,
repository=self.repository,
policy_sha256=self.policy_sha256,
hmac_key=self.hmac_key,
)
def resolve_recipe(
self,
version: str,
candidate_id: str | None,
source_commit: str,
) -> dict[str, str]:
record = self.lookup_source(source_commit)
if record is None:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_NOT_FOUND")
if record["version"] != version:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_VERSION_MISMATCH")
supplied_candidate = str(candidate_id or "")
if supplied_candidate and supplied_candidate != record["candidate_id"]:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_CANDIDATE_MISMATCH")
return {
"mode": RECIPE_MODE,
"task_id": record["task_id"],
"candidate_id": record["candidate_id"],
"base_oid": record["base_oid"],
"source_commit": record["source_commit"],
"version": record["version"],
"purpose": record["purpose"],
}
@dataclass(slots=True)
class GiteaReleaseSourceBackend:
transport: GiteaMachineTransport
git_dir: Path
def authoritative_main_oid(self) -> str:
return authoritative_head_oid(git_dir=self.git_dir)
def remote_ref_oid(self, source_ref: str) -> str:
ref = _source_ref(source_ref)
branch = ref[len(_REF_PREFIX):]
value = self.transport.read_branch(branch)
if value is None:
raise ReleaseSourceAdmissionError("SOURCE_REF_NOT_FOUND")
return _oid(value, "remote_ref_oid")
def fetch_exact_ref(self, source_ref: str, source_commit: str) -> str:
ref = _source_ref(source_ref)
branch = ref[len(_REF_PREFIX):]
try:
value = self.transport.fetch_branch_exact(
branch,
_oid(source_commit, "source_commit"),
)
except GiteaTransportError as exc:
raise ReleaseSourceAdmissionError("SOURCE_FETCH_FAILED") from exc
return _oid(value, "fetched_source_commit")
def tree_oid(self, source_commit: str) -> str:
source = _oid(source_commit, "source_commit")
try:
result = run_authoritative_git(
["rev-parse", "--verify", f"{source}^{{tree}}"],
git_dir=self.git_dir,
)
except AuthoritativeGitError as exc:
raise ReleaseSourceAdmissionError("SOURCE_COMMIT_NOT_FOUND") from exc
return _oid(result.stdout.strip().lower(), "tree_oid")
def require_lineage(self, base_oid: str, source_commit: str) -> None:
try:
authoritative_lineage(
_oid(base_oid, "base_oid"),
_oid(source_commit, "source_commit"),
git_dir=self.git_dir,
)
except AuthoritativeGitError as exc:
raise ReleaseSourceAdmissionError("SOURCE_LINEAGE_MISMATCH") from exc
class ReleaseSourceAdmitter:
def __init__(
self,
store: ReleaseSourceAdmissionStore,
backend: ReleaseSourceBackend,
*,
purpose: str = "AIOS_TRUSTED_RELEASE_BUILDER",
) -> None:
if not store.writable:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_STORE_READ_ONLY")
self.store = store
self.backend = backend
self.purpose = _identifier(purpose, "purpose")
def admit(
self,
*,
version: str,
task_id: str,
source_ref: str,
source_commit: str,
) -> dict[str, Any]:
version_value = _version(version)
task_value = _identifier(task_id, "task_id")
ref_value = _source_ref(source_ref)
source_value = _oid(source_commit, "source_commit")
remote_before = self.backend.remote_ref_oid(ref_value)
if remote_before != source_value:
raise ReleaseSourceAdmissionError("SOURCE_REF_MOVED")
base_oid = _oid(self.backend.authoritative_main_oid(), "base_oid")
fetched = _oid(
self.backend.fetch_exact_ref(ref_value, source_value),
"fetched_source_commit",
)
if fetched != source_value:
raise ReleaseSourceAdmissionError("SOURCE_FETCH_MISMATCH")
remote_after = self.backend.remote_ref_oid(ref_value)
if remote_after != source_value:
raise ReleaseSourceAdmissionError("SOURCE_REF_MOVED")
self.backend.require_lineage(base_oid, source_value)
tree_oid = _oid(self.backend.tree_oid(source_value), "tree_oid")
candidate_id = f"rc-{_version_slug(version_value)}-{source_value[:12]}"
admission_id = "source-admission-" + hashlib.sha256(
f"{self.store.repository}\0{version_value}\0{source_value}\0{ref_value}".encode("utf-8")
).hexdigest()[:24]
record = {
"schema": SCHEMA,
"admission_id": admission_id,
"repository": self.store.repository,
"task_id": task_value,
"version": version_value,
"candidate_id": candidate_id,
"base_oid": base_oid,
"source_ref": ref_value,
"source_commit": source_value,
"remote_ref_oid": remote_after,
"tree_oid": tree_oid,
"purpose": self.purpose,
"policy_sha256": self.store.policy_sha256,
"admitted_at": _now(),
}
return self.store.register(record)
def _sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _main() -> None:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command", required=True)
admit = subparsers.add_parser("admit")
admit.add_argument("--version", required=True)
admit.add_argument("--task-id", required=True)
admit.add_argument("--source-ref", required=True)
admit.add_argument("--source-commit", required=True)
admit.add_argument("--policy", required=True, type=Path)
admit.add_argument("--store-root", required=True, type=Path)
arguments = parser.parse_args()
if arguments.command != "admit":
raise SystemExit("unsupported command")
repository = os.environ.get("AIOS_GITOPS_REPOSITORY_IDENTITY", "")
api_base = os.environ.get("AIOS_GITEA_API_BASE", "")
remote_url = os.environ.get("AIOS_GITEA_REMOTE_URL", "")
raw_git_dir = os.environ.get("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", "")
if not repository or not api_base or not remote_url or not raw_git_dir:
raise SystemExit("release source admission environment is incomplete")
git_dir = Path(raw_git_dir)
transport = GiteaMachineTransport(
api_base=api_base,
repository_id=repository,
remote_url=remote_url,
authority_git_dir=git_dir,
credential_name=os.environ.get(
"AIOS_GITEA_MACHINE_CREDENTIAL_NAME",
"gitea-machine-token",
),
)
credentials = os.environ.get("CREDENTIALS_DIRECTORY", "")
if not credentials:
raise SystemExit("CREDENTIALS_DIRECTORY is required")
try:
admission_hmac = (Path(credentials) / "source_admission_hmac").read_bytes().strip()
except OSError as exc:
raise SystemExit("source_admission_hmac credential is required") from exc
store = ReleaseSourceAdmissionStore(
arguments.store_root,
repository=repository,
policy_sha256=_sha256_file(arguments.policy),
hmac_key=admission_hmac,
expected_owner_uid=os.geteuid() if hasattr(os, "geteuid") else None,
writable=True,
)
result = ReleaseSourceAdmitter(
store,
GiteaReleaseSourceBackend(transport=transport, git_dir=git_dir),
).admit(
version=arguments.version,
task_id=arguments.task_id,
source_ref=arguments.source_ref,
source_commit=arguments.source_commit,
)
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
if __name__ == "__main__":
_main()
+57 -8
View File
@@ -3531,6 +3531,7 @@ class SilentReleaseActionService:
trusted_policy: TrustedReleasePolicy | None = None,
orchestrator: ReleaseOrchestrator | None = None,
builder_readiness_probe: Callable[[], Mapping[str, Any]] | None = None,
source_admission_resolver: Callable[[str], Mapping[str, Any] | None] | None = None,
allow_legacy_candidate_input: bool = False,
expected_repository: str = "",
policy_sha256: str = "",
@@ -3547,6 +3548,7 @@ class SilentReleaseActionService:
self.trusted_policy = trusted_policy
self.orchestrator = orchestrator
self.builder_readiness_probe = builder_readiness_probe
self.source_admission_resolver = source_admission_resolver
self.allow_legacy_candidate_input = bool(
allow_legacy_candidate_input
)
@@ -3638,6 +3640,40 @@ class SilentReleaseActionService:
version = str(payload.get("version") or "")
if version:
_validate_identifier("version", version)
source_admission: dict[str, Any] | None = None
resolver = self.source_admission_resolver
if resolver is not None:
try:
raw_admission = resolver(source_commit)
except Exception as exc:
raise CapabilityAuthorizationError(
"release source admission is invalid"
) from exc
if raw_admission is not None:
source_admission = dict(raw_admission)
admitted_source = str(
source_admission.get("source_commit") or ""
)
admitted_version = str(source_admission.get("version") or "")
admission_id = str(
source_admission.get("admission_id") or ""
)
if (
admitted_source != source_commit
or not admitted_version
or not admission_id
):
raise CapabilityAuthorizationError(
"release source admission is invalid"
)
_validate_identifier("version", admitted_version)
_validate_identifier("admission_id", admission_id)
if version and version != admitted_version:
raise CapabilityAuthorizationError(
"release source admission version mismatch"
)
version = admitted_version
intent_id = "intent-" + hashlib.sha256(
f"{event_id}\0{source_commit}".encode("utf-8")
).hexdigest()[:24]
@@ -3657,12 +3693,16 @@ class SilentReleaseActionService:
}
if version:
record["version"] = version
if source_admission is not None:
record["source_admission_id"] = source_admission["admission_id"]
if was_existing:
existing = json.loads(path.read_text(encoding="utf-8"))
if (
existing.get("event_id") != event_id
or existing.get("source_commit") != source_commit
or str(existing.get("version") or "") != version
or str(existing.get("source_admission_id") or "")
!= str(record.get("source_admission_id") or "")
):
raise SagaConflict(
"release intent id is bound to different evidence"
@@ -3742,14 +3782,23 @@ class SilentReleaseActionService:
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except CapabilityAuthorizationError:
record.update(
{
"failure_code": "TRUSTED_BUILDER_REJECTED",
"retryable": False,
"status": "BUILDER_TERMINAL_FAILURE",
}
)
except CapabilityAuthorizationError as exc:
if str(exc) == "release source admission required":
record.update(
{
"failure_code": "SOURCE_ADMISSION_REQUIRED",
"retryable": False,
"status": "SOURCE_ADMISSION_REQUIRED",
}
)
else:
record.update(
{
"failure_code": "TRUSTED_BUILDER_REJECTED",
"retryable": False,
"status": "BUILDER_TERMINAL_FAILURE",
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except (ConnectionError, OSError, TimeoutError):
@@ -7,8 +7,10 @@ Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_SOCKET=/run/aios-trusted-candidate-bu
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_POLICY=/opt/aios/gitops-model-api/<VERIFIED_PACKAGE_OID>/runtime/config/gitops/trusted-candidate-builder-policy.json
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_PUBLIC_KEYRING=/etc/aios-gitops/trusted-builder/public-keyring.json
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_CREDENTIALS_DIRECTORY=%d
Environment=AIOS_TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT=/var/lib/aios-release-source-admission
Environment=AIOS_SILENT_RELEASE_TRUSTED_POLICY=/opt/aios/gitops-model-api/<VERIFIED_PACKAGE_OID>/runtime/config/gitops/trusted-release-policy.json
LoadCredential=builder_hmac:/etc/aios-gitops/credential-references/trusted-candidate-builder-hmac
LoadCredential=source_admission_hmac:/etc/aios-gitops/credential-references/release-source-admission-hmac
# The credential source above is a reference only; no secret value belongs here.
# Keep execution shadow-disabled and do not add Owner/deploy/tag/baseline switches.
Environment=AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
@@ -27,6 +27,7 @@ Environment=AIOS_GITOPS_DEPLOYMENT_STATE=STAGING
Environment=AIOS_GITOPS_MODEL_API_SERVICE=aios-gitops-model-api-v2r2c1-staging.service
Environment=AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
Environment=AIOS_SILENT_RELEASE_STATE_ROOT=/var/lib/aios-gitops/model-api-staging/silent-release
Environment=AIOS_TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT=/var/lib/aios-release-source-admission
Environment=AIOS_BRIDGE_V2_URL=http://127.0.0.1:18892
Environment=AIOS_GITOPS_HOST=127.0.0.1
Environment=AIOS_GITOPS_PORT=18893
@@ -44,7 +45,7 @@ ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadOnlyPaths=@RUNTIME_ROOT@ /data/aios-gitops/repo.git /var/lib/aios-gitops-runtime-anchor/baseline
ReadOnlyPaths=@RUNTIME_ROOT@ /data/aios-gitops/repo.git /var/lib/aios-gitops-runtime-anchor/baseline -/var/lib/aios-release-source-admission
ReadWritePaths=/var/lib/aios-gitops/model-api-staging
[Install]
@@ -0,0 +1,41 @@
[Unit]
Description=AIOS governed release source admission
After=network-online.target aios-gitea.service
Wants=network-online.target
[Service]
Type=oneshot
User=root
Group=gitops
UMask=0027
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITOPS_REPOSITORY_IDENTITY=aios/aios
Environment=AIOS_GITOPS_AUTHORITATIVE_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_GITEA_API_BASE=https://g.cnnetmkt.top/git/api/v1
Environment=AIOS_GITEA_REMOTE_URL=https://g.cnnetmkt.top/git/aios/aios.git
Environment=AIOS_GITEA_MACHINE_CREDENTIAL_NAME=gitea-machine-token
EnvironmentFile=/run/aios-release-source-admission/request.env
LoadCredentialEncrypted=gitea-machine-token:/etc/aios/credentials/gitea-machine.token.cred
LoadCredentialEncrypted=source_admission_hmac:/etc/credstore.encrypted/aios-release-source-admission/source_admission_hmac
ExecStart=/usr/bin/python3 -B /opt/aios/release-source-admission/current/app/release_source_admission.py admit --version ${AIOS_RELEASE_VERSION} --task-id ${AIOS_RELEASE_TASK_ID} --source-ref ${AIOS_RELEASE_SOURCE_REF} --source-commit ${AIOS_RELEASE_SOURCE_COMMIT} --policy /opt/aios/trusted-candidate-builder/current/config/gitops/trusted-candidate-builder-policy.json --store-root /var/lib/aios-release-source-admission
ExecStartPost=/usr/bin/rm -f /run/aios-release-source-admission/request.env
RuntimeDirectory=aios-release-source-admission
RuntimeDirectoryMode=0750
StateDirectory=aios-release-source-admission
StateDirectoryMode=0750
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
CapabilityBoundingSet=
ReadOnlyPaths=/opt/aios/release-source-admission /opt/aios/trusted-candidate-builder /etc/aios/credentials /etc/credstore.encrypted
ReadWritePaths=/data/aios-gitops/repo.git /var/lib/aios-release-source-admission /run/aios-release-source-admission
@@ -10,10 +10,12 @@ Group=root
UMask=0077
LoadCredentialEncrypted=signing_private_key:/etc/credstore.encrypted/aios-trusted-candidate-builder/signing_private_key
LoadCredentialEncrypted=builder_hmac:/etc/credstore.encrypted/aios-trusted-candidate-builder/builder_hmac
LoadCredentialEncrypted=source_admission_hmac:/etc/credstore.encrypted/aios-release-source-admission/source_admission_hmac
Environment=AIOS_TRUSTED_BUILDER_POLICY=/opt/aios/trusted-candidate-builder/current/config/gitops/trusted-candidate-builder-policy.json
Environment=AIOS_TRUSTED_BUILDER_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_TRUSTED_BUILDER_EVIDENCE_ROOT=/var/lib/aios-trusted-candidate-builder/evidence
Environment=AIOS_TRUSTED_BUILDER_NONCE_LEDGER=/var/lib/aios-trusted-candidate-builder/nonce-ledger.json
Environment=AIOS_TRUSTED_BUILDER_SOURCE_ADMISSION_ROOT=/var/lib/aios-release-source-admission
Environment=AIOS_TRUSTED_BUILDER_KEY_ID=builder-attestation-ed25519-g1
Environment=AIOS_TRUSTED_BUILDER_KEY_GENERATION=1
Environment=PYTHONDONTWRITEBYTECODE=1
@@ -37,7 +39,7 @@ MemoryDenyWriteExecute=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
CapabilityBoundingSet=
ReadOnlyPaths=/opt/aios/trusted-candidate-builder /data/aios-gitops/repo.git /var/lib/aios-trusted-candidate-builder/evidence
ReadOnlyPaths=/opt/aios/trusted-candidate-builder /data/aios-gitops/repo.git /var/lib/aios-trusted-candidate-builder/evidence -/var/lib/aios-release-source-admission
ReadWritePaths=/var/lib/aios-trusted-candidate-builder
[Install]
+76
View File
@@ -1,12 +1,14 @@
from __future__ import annotations
import tempfile
from types import SimpleNamespace
from pathlib import Path
import pytest
import aios_gitops_job_executor as executor_module
from aios_authority_git_executor import AuthorityGitError, AuthorityGitExecutor
import aios_gitea_machine_transport as gitea_transport_module
from aios_gitea_machine_transport import GiteaMachineTransport
from aios_gitea_machine_broker import CampaignCapabilityReceipt, GiteaMachineBroker, MachineCredentialProjection
from aios_gitops_job_controller import GitOpsJobController, JobError, JobStore, JobState
@@ -162,3 +164,77 @@ def test_machine_transport_branch_cas_is_not_force_push(monkeypatch):
monkeypatch.setattr(GiteaMachineTransport, "_push_branch_fast_forward", lambda _self, branch, new: calls.append((branch, new)))
assert transport.compare_and_swap_branch(executor_module.BRANCH, OLD, NEW) == NEW
assert calls == [(executor_module.BRANCH, NEW)]
def test_machine_transport_fetches_exact_branch_tip_into_quarantine_ref(monkeypatch, tmp_path):
transport = GiteaMachineTransport(
"http://127.0.0.1:3000/api/v1",
"aios/aios",
"https://g.cnnetmkt.top/git/aios/aios.git",
tmp_path / "repo.git",
)
reads = iter([NEW, NEW])
monkeypatch.setattr(
GiteaMachineTransport,
"read_branch",
lambda _self, _branch: next(reads),
)
askpass = tmp_path / "askpass"
askpass.write_text("#!/bin/sh\n", encoding="utf-8")
monkeypatch.setattr(
GiteaMachineTransport,
"_askpass",
lambda _self: (askpass, {}),
)
calls = []
def run(argv, **kwargs):
calls.append(list(argv))
if "fetch" in argv:
return SimpleNamespace(returncode=0, stdout="", stderr="")
if "rev-parse" in argv:
# Before fetch the quarantine ref is absent; after fetch it resolves.
seen_fetch = any("fetch" in call for call in calls[:-1])
return SimpleNamespace(
returncode=0 if seen_fetch else 1,
stdout=(NEW + "\n") if seen_fetch else "",
stderr="",
)
if "update-ref" in argv:
return SimpleNamespace(returncode=0, stdout="", stderr="")
raise AssertionError(argv)
monkeypatch.setattr(gitea_transport_module.subprocess, "run", run)
assert transport.fetch_branch_exact(executor_module.BRANCH, NEW) == NEW
fetch = next(call for call in calls if "fetch" in call)
assert "--no-tags" in fetch
assert "--no-write-fetch-head" in fetch
assert f"refs/heads/{executor_module.BRANCH}:refs/aios/release-sources/{NEW}" in fetch
assert not any("--force" in item or item == "-f" for item in fetch)
def test_machine_transport_does_not_fetch_when_remote_tip_moved(monkeypatch, tmp_path):
transport = GiteaMachineTransport(
"http://127.0.0.1:3000/api/v1",
"aios/aios",
"https://g.cnnetmkt.top/git/aios/aios.git",
tmp_path / "repo.git",
)
monkeypatch.setattr(
GiteaMachineTransport,
"read_branch",
lambda _self, _branch: OLD,
)
monkeypatch.setattr(
gitea_transport_module.subprocess,
"run",
lambda *args, **kwargs: (_ for _ in ()).throw(
AssertionError("git must not run after remote mismatch")
),
)
with pytest.raises(
gitea_transport_module.GiteaTransportError,
match="GITEA_SOURCE_REF_MISMATCH",
):
transport.fetch_branch_exact(executor_module.BRANCH, NEW)
+252
View File
@@ -0,0 +1,252 @@
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from release_source_admission import ( # noqa: E402
ReleaseSourceAdmissionError,
ReleaseSourceAdmissionStore,
ReleaseSourceAdmitter,
)
MAIN = "1" * 40
SOURCE = "2" * 40
TREE = "3" * 40
POLICY = "4" * 64
REF = "refs/heads/agent/baseline-1.1-principal-recovery-20260908"
VERSION = "V2R2C4"
TASK = "AIOS-BASELINE-1.1-PRODUCTION-CLOSURE"
class FakeBackend:
def __init__(
self,
*,
remote: str = SOURCE,
fetched: str = SOURCE,
tree: str = TREE,
main: str = MAIN,
lineage: bool = True,
) -> None:
self.remote = remote
self.fetched = fetched
self.tree = tree
self.main = main
self.lineage = lineage
self.fetch_calls: list[tuple[str, str]] = []
self.lineage_calls: list[tuple[str, str]] = []
def authoritative_main_oid(self) -> str:
return self.main
def remote_ref_oid(self, source_ref: str) -> str:
assert source_ref == REF
return self.remote
def fetch_exact_ref(self, source_ref: str, source_commit: str) -> str:
self.fetch_calls.append((source_ref, source_commit))
return self.fetched
def tree_oid(self, source_commit: str) -> str:
assert source_commit == SOURCE
return self.tree
def require_lineage(self, base_oid: str, source_commit: str) -> None:
self.lineage_calls.append((base_oid, source_commit))
if not self.lineage:
raise ReleaseSourceAdmissionError("SOURCE_LINEAGE_MISMATCH")
def make_store(tmp_path: Path, *, writable: bool = True) -> ReleaseSourceAdmissionStore:
owner = os.geteuid() if hasattr(os, "geteuid") else None
return ReleaseSourceAdmissionStore(
tmp_path / "admissions",
repository="aios/aios",
policy_sha256=POLICY,
hmac_key=b"h" * 32,
expected_owner_uid=owner,
writable=writable,
)
def test_admit_binds_remote_ref_fetch_tree_main_lineage_and_recipe(tmp_path: Path) -> None:
store = make_store(tmp_path)
backend = FakeBackend()
record = ReleaseSourceAdmitter(store, backend).admit(
version=VERSION,
task_id=TASK,
source_ref=REF,
source_commit=SOURCE,
)
assert record["schema"] == "aios.release-source-admission.v1"
assert record["repository"] == "aios/aios"
assert record["source_commit"] == SOURCE
assert record["remote_ref_oid"] == SOURCE
assert record["source_ref"] == REF
assert record["base_oid"] == MAIN
assert record["tree_oid"] == TREE
assert record["version"] == VERSION
assert record["task_id"] == TASK
assert record["candidate_id"] == "rc-v2r2c4-" + SOURCE[:12]
assert backend.fetch_calls == [(REF, SOURCE)]
assert backend.lineage_calls == [(MAIN, SOURCE)]
recipe = store.resolve_recipe(VERSION, None, SOURCE)
assert recipe == {
"mode": "governed-source-admission",
"task_id": TASK,
"candidate_id": record["candidate_id"],
"base_oid": MAIN,
"source_commit": SOURCE,
"version": VERSION,
"purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
}
def test_repeated_exact_admission_is_idempotent(tmp_path: Path) -> None:
store = make_store(tmp_path)
admitter = ReleaseSourceAdmitter(store, FakeBackend())
first = admitter.admit(
version=VERSION,
task_id=TASK,
source_ref=REF,
source_commit=SOURCE,
)
second = admitter.admit(
version=VERSION,
task_id=TASK,
source_ref=REF,
source_commit=SOURCE,
)
assert second == first
assert len(list((tmp_path / "admissions").glob("*.json"))) == 1
@pytest.mark.parametrize(
("backend", "error"),
(
(FakeBackend(remote="5" * 40), "SOURCE_REF_MOVED"),
(FakeBackend(fetched="5" * 40), "SOURCE_FETCH_MISMATCH"),
(FakeBackend(lineage=False), "SOURCE_LINEAGE_MISMATCH"),
),
)
def test_admission_rejects_moved_fetch_mismatch_or_non_descendant(
tmp_path: Path,
backend: FakeBackend,
error: str,
) -> None:
store = make_store(tmp_path)
with pytest.raises(ReleaseSourceAdmissionError, match=error):
ReleaseSourceAdmitter(store, backend).admit(
version=VERSION,
task_id=TASK,
source_ref=REF,
source_commit=SOURCE,
)
assert not list((tmp_path / "admissions").glob("*.json"))
@pytest.mark.parametrize(
"source_ref",
(
"agent/bad",
"refs/tags/v1",
"refs/heads/../main",
"refs/heads/agent//bad",
"refs/heads/agent/bad.lock",
),
)
def test_admission_rejects_unsafe_or_non_head_refs(
tmp_path: Path, source_ref: str
) -> None:
store = make_store(tmp_path)
with pytest.raises(ReleaseSourceAdmissionError, match="SOURCE_REF_INVALID"):
ReleaseSourceAdmitter(store, FakeBackend()).admit(
version=VERSION,
task_id=TASK,
source_ref=source_ref,
source_commit=SOURCE,
)
def test_store_rejects_caller_candidate_rebinding(tmp_path: Path) -> None:
store = make_store(tmp_path)
record = ReleaseSourceAdmitter(store, FakeBackend()).admit(
version=VERSION,
task_id=TASK,
source_ref=REF,
source_commit=SOURCE,
)
with pytest.raises(
ReleaseSourceAdmissionError,
match="SOURCE_ADMISSION_CANDIDATE_MISMATCH",
):
store.resolve_recipe(VERSION, "rc-attacker", SOURCE)
assert record["candidate_id"] != "rc-attacker"
def test_store_rejects_policy_change_and_record_tamper(tmp_path: Path) -> None:
store = make_store(tmp_path)
record = ReleaseSourceAdmitter(store, FakeBackend()).admit(
version=VERSION,
task_id=TASK,
source_ref=REF,
source_commit=SOURCE,
)
path = tmp_path / "admissions" / f"{SOURCE}.json"
payload = json.loads(path.read_text(encoding="utf-8"))
payload["task_id"] = "other-task"
path.write_text(
json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n",
encoding="utf-8",
)
with pytest.raises(
ReleaseSourceAdmissionError,
match="SOURCE_ADMISSION_MAC_INVALID",
):
store.lookup_source(SOURCE)
owner = os.geteuid() if hasattr(os, "geteuid") else None
other_policy = ReleaseSourceAdmissionStore(
tmp_path / "admissions",
repository="aios/aios",
policy_sha256="6" * 64,
hmac_key=b"h" * 32,
expected_owner_uid=owner,
writable=False,
)
with pytest.raises(
ReleaseSourceAdmissionError,
match="SOURCE_ADMISSION_POLICY_MISMATCH",
):
other_policy.lookup_source(SOURCE)
def test_store_rejects_group_writable_record(tmp_path: Path) -> None:
store = make_store(tmp_path)
ReleaseSourceAdmitter(store, FakeBackend()).admit(
version=VERSION,
task_id=TASK,
source_ref=REF,
source_commit=SOURCE,
)
path = tmp_path / "admissions" / f"{SOURCE}.json"
os.chmod(path, 0o660)
with pytest.raises(
ReleaseSourceAdmissionError,
match="SOURCE_ADMISSION_PERMISSIONS_INVALID",
):
store.lookup_source(SOURCE)
@@ -726,3 +726,69 @@ def test_systemd_shadow_unit_has_both_gates_and_no_secret_environment():
assert "PRIVATE_KEY=" not in unit
assert "TOKEN=" not in unit
assert "PASSWORD=" not in unit
def test_release_intent_derives_version_from_governed_source_admission(tmp_path):
service, _ = trusted_service(tmp_path)
service.source_admission_resolver = lambda source_commit: {
"schema": "aios.release-source-admission.v1",
"admission_id": "source-admission-test-001",
"source_commit": source_commit,
"version": "V2R2C4",
}
result = service.create_release_intent(
{"event_id": "webhook-admitted-source-001", "source_commit": COMMIT}
)
assert result["version"] == "V2R2C4"
assert result["source_admission_id"] == "source-admission-test-001"
intent_path = service.intents_root / f"{result['intent_id']}.json"
persisted = json.loads(intent_path.read_text(encoding="utf-8"))
assert persisted["version"] == "V2R2C4"
assert persisted["source_admission_id"] == "source-admission-test-001"
def test_release_intent_rejects_version_rebinding_against_source_admission(tmp_path):
service, _ = trusted_service(tmp_path)
service.source_admission_resolver = lambda source_commit: {
"schema": "aios.release-source-admission.v1",
"admission_id": "source-admission-test-002",
"source_commit": source_commit,
"version": "V2R2C4",
}
with pytest.raises(
CapabilityAuthorizationError,
match="release source admission version mismatch",
):
service.create_release_intent(
{
"event_id": "webhook-admitted-source-002",
"source_commit": COMMIT,
"version": "V2R2C5",
}
)
def test_release_intent_surfaces_source_admission_required_as_typed_blocker(tmp_path):
service, _ = trusted_service(tmp_path)
class AdmissionRequiredBuilder:
def build(self, _payload):
raise CapabilityAuthorizationError(
"release source admission required"
)
service.candidate_builder = AdmissionRequiredBuilder()
result = service.create_release_intent(
{
"event_id": "webhook-source-admission-required-001",
"source_commit": COMMIT,
}
)
assert result["status"] == "SOURCE_ADMISSION_REQUIRED"
assert result["failure_code"] == "SOURCE_ADMISSION_REQUIRED"
assert result["retryable"] is False
assert result["side_effects_executed"] is False
+78
View File
@@ -590,3 +590,81 @@ def test_systemd_socket_and_service_are_af_unix_hardened_and_credential_bound()
assert policy["allowed_peer_cgroups"] == [
"/system.slice/aios-gitops-model-api-v2r2c1-staging.service"
]
def test_governed_source_admission_resolves_unknown_version_without_overriding_fixed_recipe() -> None:
policy = load_builder_policy(POLICY_PATH)
calls: list[tuple[str, str | None, str]] = []
def resolver(version: str, candidate_id: str | None, source_commit: str):
calls.append((version, candidate_id, source_commit))
return {
"mode": "governed-source-admission",
"task_id": "AIOS-BASELINE-1.1-PRODUCTION-CLOSURE",
"candidate_id": "rc-v2r2c4-" + source_commit[:12],
"base_oid": "1" * 40,
"source_commit": source_commit,
"version": version,
"purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
}
config = dataclasses.replace(
TrustedCandidateBuilderConfig.from_policy(policy),
source_admission_resolver=resolver,
)
client = UnixTrustedCandidateBuilderClient(
config,
BuilderKeyring(builder_hmac=b"h" * 32),
clock=lambda: NOW,
)
source = "2" * 40
request = client.create_request(
"V2R2C4",
None,
source,
nonce="z" * 32,
)
assert request["candidate_id"] == "rc-v2r2c4-" + source[:12]
assert request["base_oid"] == "1" * 40
assert request["source_commit"] == source
assert request["task_id"] == "AIOS-BASELINE-1.1-PRODUCTION-CLOSURE"
assert calls == [("V2R2C4", None, source)]
with pytest.raises(BuilderError, match="REQUEST_RELEASE_BINDING_INVALID"):
config.resolve_recipe(
"V2R2C2",
None,
source,
)
# Fixed policy versions never fall through to the admission resolver.
assert calls == [("V2R2C4", None, source)]
def test_unknown_release_source_requires_explicit_admission() -> None:
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH))
with pytest.raises(BuilderError, match="RECIPE_UNKNOWN"):
config.resolve_recipe("V2R2C4", None, "2" * 40)
def test_governed_source_admission_rejects_caller_candidate_rebinding() -> None:
policy = load_builder_policy(POLICY_PATH)
def resolver(version: str, candidate_id: str | None, source_commit: str):
return {
"mode": "governed-source-admission",
"task_id": "task-admitted-source",
"candidate_id": "rc-v2r2c4-" + source_commit[:12],
"base_oid": "1" * 40,
"source_commit": source_commit,
"version": version,
"purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
}
config = dataclasses.replace(
TrustedCandidateBuilderConfig.from_policy(policy),
source_admission_resolver=resolver,
)
with pytest.raises(BuilderError, match="REQUEST_RELEASE_BINDING_INVALID"):
config.resolve_recipe("V2R2C4", "rc-attacker", "2" * 40)