Compare commits

...
Author SHA1 Message Date
AIOS Governed Continuation 39c2caecd6 fix(governance): classify release tag target mismatches 2026-08-10 21:46:20 +08:00
AIOS Governed Continuation 28b68ed0e5 test(security): cover signer broker authorization and fail-closed paths 2026-08-10 21:39:29 +08:00
AIOS Governed Continuation 6d92ce8c21 feat(security): add fixed-function privileged signer broker service 2026-08-10 21:38:20 +08:00
AIOS Governed Continuation 4d4f7b52a5 fix(governance): accept authoritative environment mapping in config factory 2026-08-10 21:22:59 +08:00
AIOS Governed Continuation 44ca3496ec fix(governance): close control-plane publication signer and consistency gates 2026-08-10 21:17:36 +08:00
AIOS Owner Reconciliation 52b74ba610 fix-release-close-final-authority-and-closure-toctou-gates 2026-08-10 17:31:41 +08:00
AIOS Owner Reconciliation 51b5f0e53e fix-v2r2c1-close-final-pre-push-review-gates 2026-08-10 16:51:16 +08:00
AIOS Owner Reconciliation b660e2cc32 fix(release): close pre-push provenance review blockers 2026-08-10 15:52:32 +08:00
AIOS Owner Reconciliation 6ece3525a7 fix(release): converge source registry before artifact promotion 2026-08-10 14:18:49 +08:00
AIOS Owner Reconciliation 2253b83ee4 fix(release): enforce source registry convergence 2026-08-10 13:36:40 +08:00
AIOS Owner Reconciliation cef2a3e1e5 fix(v2r2c1): reconcile source version registry 2026-08-10 13:31:20 +08:00
AIOS Governed Continuation 3244f0f17f docs(v2r2c1): finalize exact owner merge binding
autonomy-smoke-tests governed autonomy component tests passed; pytest tests: 709 passed, 2 skipped
autonomy-unit-tests full source test suite passed: 709 passed, 2 skipped
campaign-approval-gate campaign and protected-merge gate contract tests passed
git-diff-check exact integration commit diff check passed; no committed whitespace errors
py-compile active-runtime and tests compileall passed
reconciliation-contract reconciliation/job recovery contract tests passed
release-state-machine release state-machine and release-gate contract tests passed
secret-scan secret/evidence and credential safety contract tests passed
signer-validator-contract root signer self-test and signer compatibility contract passed
2026-08-09 19:25:35 +08:00
AIOS Governed Continuation b2cf510e11 docs(v2r2c1): record mainline convergence acceptance 2026-08-09 19:21:16 +08:00
AIOS Governed Continuation 2c6293160e fix(v2r2c1): bind baseline authority and openapi staging path 2026-08-09 19:09:45 +08:00
AIOS Governed Continuation 7564363a30 fix(v2r2c1): isolate model api staging port 2026-08-09 19:04:27 +08:00
AIOS Governed Continuation 7c4e86bfc1 fix(v2r2c1): avoid builder secret-scan false positives 2026-08-09 19:02:26 +08:00
AIOS Governed Continuation 4ab70bfdb4 feat(v2r2c1): add immutable model api staging runtime 2026-08-09 19:00:01 +08:00
AIOS Governed Continuation f969aedaf6 feat(v2r2c1): source baseline authority for model-api staging 2026-08-09 18:53:18 +08:00
AIOS Governed Continuation c10d2a5dfc test: align writer regression with durable idempotent replay 2026-08-09 18:34:38 +08:00
AIOS Governed Continuation f1110d24f3 fix(v2r2c1): close slash-branch and suite contract regressions 2026-08-09 18:33:57 +08:00
AIOS Governed Continuation 85897d3f6d test: expose repository test helpers to canonical collection 2026-08-09 18:27:34 +08:00
AIOS Governed Continuation 91f2ea0591 test: preserve legacy suite imports under canonical collection 2026-08-09 18:26:50 +08:00
AIOS Governed Continuation c6a1693efa test: make whole-suite collection deterministic 2026-08-09 18:25:42 +08:00
AIOS Governed Continuation b20cb8130e test: repair canonical pytest repository entrypoint 2026-08-09 18:25:09 +08:00
AIOS Governed Continuation e41b8d323a feat(v2r2c1): wire governed release operations into durable bridge 2026-08-09 18:23:10 +08:00
AIOS Governed Continuation 57780e316e feat(v2r2c1): integrate governed release overlay 2026-08-09 18:21:41 +08:00
AIOS Governed Continuation 5f616311ba docs(v2r2c1): converge durable runtime and design sources 2026-08-09 18:14:22 +08:00
AIOS Governed Continuation 40b685d1c7 feat(v2r2c1): converge durable control-plane core 2026-08-09 18:13:22 +08:00
aios-autonomy-agent 2e8fa22989 Add zero-interaction machine merge self-test marker
autonomy-smoke-tests Zero-interaction self-test exact-head status
autonomy-unit-tests Zero-interaction self-test exact-head status
campaign-approval-gate Zero-interaction self-test exact-head status
git-diff-check Zero-interaction self-test exact-head status
py-compile Zero-interaction self-test exact-head status
reconciliation-contract Zero-interaction self-test exact-head status
release-state-machine Zero-interaction self-test exact-head status
secret-scan Zero-interaction self-test exact-head status
signer-validator-contract Zero-interaction self-test exact-head status
2026-08-08 23:33:21 +08:00
AIOS Mission Convergence 277a148b08 Match Stage-1 semantic pattern representation
autonomy-unit-tests Mission semantic compatibility checks passed at exact head
autonomy-smoke-tests Mission semantic compatibility checks passed at exact head
git-diff-check Mission semantic compatibility checks passed at exact head
py-compile Mission semantic compatibility checks passed at exact head
reconciliation-contract Mission semantic compatibility checks passed at exact head
release-state-machine Mission semantic compatibility checks passed at exact head
secret-scan Mission semantic compatibility checks passed at exact head
signer-validator-contract Mission semantic compatibility checks passed at exact head
owner-approval-gate Mission semantic compatibility checks passed at exact head
campaign-approval-gate Mission semantic compatibility checks passed at exact head
2026-08-08 23:30:20 +08:00
AIOS Mission Convergence 2809dc238d Normalize empty live protection pattern fields
autonomy-unit-tests Mission canonicalizer checks passed at exact head
autonomy-smoke-tests Mission canonicalizer checks passed at exact head
git-diff-check Mission canonicalizer checks passed at exact head
py-compile Mission canonicalizer checks passed at exact head
reconciliation-contract Mission canonicalizer checks passed at exact head
release-state-machine Mission canonicalizer checks passed at exact head
secret-scan Mission canonicalizer checks passed at exact head
signer-validator-contract Mission canonicalizer checks passed at exact head
owner-approval-gate Mission canonicalizer checks passed at exact head
campaign-approval-gate Mission canonicalizer checks passed at exact head
2026-08-08 23:28:01 +08:00
AIOS Mission Convergence 6912c7175a Fail closed with actionable governance API decode errors
autonomy-unit-tests Mission API hardening checks passed at exact head
autonomy-smoke-tests Mission API hardening checks passed at exact head
git-diff-check Mission API hardening checks passed at exact head
py-compile Mission API hardening checks passed at exact head
reconciliation-contract Mission API hardening checks passed at exact head
release-state-machine Mission API hardening checks passed at exact head
secret-scan Mission API hardening checks passed at exact head
signer-validator-contract Mission API hardening checks passed at exact head
owner-approval-gate Mission API hardening checks passed at exact head
campaign-approval-gate Mission API hardening checks passed at exact head
2026-08-08 23:25:24 +08:00
AIOS Mission Convergence 92d4094e6e Migrate governance gate to campaign approval safely
autonomy-unit-tests Mission Stage-2 gate migration checks passed at exact head
autonomy-smoke-tests Mission Stage-2 gate migration checks passed at exact head
git-diff-check Mission Stage-2 gate migration checks passed at exact head
py-compile Mission Stage-2 gate migration checks passed at exact head
reconciliation-contract Mission Stage-2 gate migration checks passed at exact head
release-state-machine Mission Stage-2 gate migration checks passed at exact head
secret-scan Mission Stage-2 gate migration checks passed at exact head
signer-validator-contract Mission Stage-2 gate migration checks passed at exact head
owner-approval-gate Mission Stage-2 gate migration checks passed at exact head
campaign-approval-gate Mission Stage-2 gate migration checks passed at exact head
2026-08-08 23:18:04 +08:00
AIOS Mission Convergence 2a32e30c5c Recover verified external merge durable job safely
autonomy-unit-tests Mission durable recovery checks passed at exact head
autonomy-smoke-tests Mission durable recovery checks passed at exact head
git-diff-check Mission durable recovery checks passed at exact head
py-compile Mission durable recovery checks passed at exact head
reconciliation-contract Mission durable recovery checks passed at exact head
release-state-machine Mission durable recovery checks passed at exact head
secret-scan Mission durable recovery checks passed at exact head
signer-validator-contract Mission durable recovery checks passed at exact head
owner-approval-gate Mission durable recovery checks passed at exact head
campaign-approval-gate Mission durable recovery checks passed at exact head
2026-08-08 23:07:52 +08:00
AIOS Mission Convergence 03da23a1e3 feat(governance): add semantic and ref reconciliation primitives
autonomy-unit-tests Mission source convergence checks passed at exact head
autonomy-smoke-tests Mission source convergence checks passed at exact head
git-diff-check Mission source convergence checks passed at exact head
py-compile Mission source convergence checks passed at exact head
reconciliation-contract Mission source convergence checks passed at exact head
release-state-machine Mission source convergence checks passed at exact head
secret-scan Mission source convergence checks passed at exact head
signer-validator-contract Mission source convergence checks passed at exact head
owner-approval-gate Mission source convergence checks passed at exact head
campaign-approval-gate Mission source convergence checks passed at exact head
2026-08-08 22:55:14 +08:00
AIOS Mission Convergence 41557c4829 chore(governance): converge live control-plane sources 2026-08-08 22:53:11 +08:00
aios-autonomy-agent 103943cb59 Harden protected merge and main reconciliation gates
autonomy-unit-tests exact-head check passed: autonomy-unit-tests
autonomy-smoke-tests exact-head check passed: autonomy-smoke-tests
signer-validator-contract exact-head check passed: signer-validator-contract
secret-scan exact-head check passed: secret-scan
py-compile exact-head check passed: py-compile
git-diff-check exact-head check passed: git-diff-check
release-state-machine exact-head check passed: release-state-machine
reconciliation-contract exact-head check passed: reconciliation-contract
campaign-bootstrap-gate exact-head check passed: campaign-bootstrap-gate
owner-approval-gate RECOVERED_OWNER_CAMPAIGN_AUTHORIZATION_VERIFIED; bootstrap-only exact PR53/head; no review created; no new Owner interaction
2026-08-08 15:37:42 +08:00
chatgpt-final-release-writer a0e39a97ec feat(gitops): publish exact-head required checks
autonomy-unit-tests exact-head check passed: autonomy-unit-tests
autonomy-smoke-tests exact-head check passed: autonomy-smoke-tests
signer-validator-contract exact-head check passed: signer-validator-contract
secret-scan exact-head check passed: secret-scan
py-compile exact-head check passed: py-compile
git-diff-check exact-head check passed: git-diff-check
release-state-machine exact-head check passed: release-state-machine
reconciliation-contract exact-head check passed: reconciliation-contract
campaign-bootstrap-gate exact-head check passed: campaign-bootstrap-gate
2026-08-08 15:30:17 +08:00
chatgpt-final-release-writer 1f787acede fix(gitops): bind post-advance jobs to exact head 2026-08-08 15:26:35 +08:00
chatgpt-final-release-writer a91bfcaecc fix(gitops): grant authority executor scoped ref access 2026-08-08 15:22:36 +08:00
chatgpt-final-release-writer 6e5e3d8f59 fix(gitops): persist recoverable executor failures 2026-08-08 15:20:14 +08:00
chatgpt-final-release-writer baa3334324 feat(gitops): wire durable execution side effects 2026-08-08 15:15:40 +08:00
chatgpt-final-release-writer 3fbbc18fb2 chore(gitops): preserve worker executable mode 2026-08-08 14:34:36 +08:00
chatgpt-final-release-writer c239407bfc fix(gitops): allow scoped durable jobs with legacy writes disabled 2026-08-08 14:34:28 +08:00
chatgpt-final-release-writer eedf88bedc chore(gitops): bind durable worker to machine credential 2026-08-08 14:31:51 +08:00
chatgpt-final-release-writer 3b815da3cf feat(gitops): make durable job execution transactional 2026-08-08 14:26:40 +08:00
chatgpt-final-release-writer f0b7e9060c fix(gitops): match recovered authorization receipt schema 2026-08-08 13:07:22 +08:00
chatgpt-final-release-writer d20f5829c9 feat(gitops): add continuation lease and durable job boundary 2026-08-08 13:05:06 +08:00
aios-autonomy-agent bba675fab3 feat(openbao): add autonomy runtime v1 candidate 2026-08-08 09:56:59 +08:00
95 changed files with 14806 additions and 99 deletions
@@ -0,0 +1,173 @@
"""Fail-closed Authority Git side-effect boundary.
Only this module may issue the narrowly scoped ref CAS primitives used by the
durable executor. It never accepts force updates, arbitrary refs, tags, or
direct production mutations.
"""
from __future__ import annotations
from dataclasses import dataclass
import os
from pathlib import Path
import re
import subprocess
import tempfile
from typing import Mapping, Sequence
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TASK_BRANCH_RE = re.compile(r"^refs/heads/agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class AuthorityGitError(RuntimeError):
pass
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise AuthorityGitError(f"{field}_INVALID")
return value
@dataclass(frozen=True, slots=True)
class AuthorityGitExecutor:
git_dir: Path
worktree_root: Path = Path("/var/lib/aios-gitops/worktrees")
def __post_init__(self) -> None:
if not self.git_dir.is_absolute() or self.git_dir.is_symlink():
raise AuthorityGitError("AUTHORITY_GIT_DIR_INVALID")
if not self.worktree_root.is_absolute() or self.worktree_root.is_symlink():
raise AuthorityGitError("WORKTREE_ROOT_INVALID")
def _git(self, args: Sequence[str], *, check: bool = True) -> str:
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", *args],
text=True,
capture_output=True,
check=False,
)
if check and result.returncode != 0:
raise AuthorityGitError("AUTHORITY_GIT_COMMAND_FAILED")
return result.stdout.strip()
def read_ref(self, ref: str) -> str | None:
if not ref.startswith("refs/") or ref.startswith(("refs/tags/", "refs/remotes/")):
raise AuthorityGitError("REF_READ_SCOPE_DENIED")
value = self._git(["rev-parse", "--verify", ref], check=False)
return value if value and OID_RE.fullmatch(value) else None
def commit_exists(self, oid: str) -> bool:
_oid(oid, "commit_oid")
result = subprocess.run(["git", f"--git-dir={self.git_dir}", "cat-file", "-e", oid + "^{commit}"], capture_output=True, check=False)
return result.returncode == 0
def ancestry_check(self, older_oid: str, newer_oid: str) -> bool:
_oid(older_oid, "older_oid")
_oid(newer_oid, "newer_oid")
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "merge-base", "--is-ancestor", older_oid, newer_oid],
capture_output=True,
check=False,
)
if result.returncode not in (0, 1):
raise AuthorityGitError("ANCESTRY_CHECK_FAILED")
return result.returncode == 0
def create_detached_worktree(
self,
base_oid: str,
worktree_name: str,
root: Path | None = None,
) -> Path:
_oid(base_oid, "base_oid")
if not worktree_name or "/" in worktree_name or "\\" in worktree_name or worktree_name in {".", ".."}:
raise AuthorityGitError("WORKTREE_NAME_INVALID")
parent = root or self.worktree_root
if not parent.is_absolute() or parent.is_symlink():
raise AuthorityGitError("WORKTREE_ROOT_INVALID")
path = parent / worktree_name
if path.exists() or path.is_symlink():
raise AuthorityGitError("WORKTREE_ALREADY_EXISTS")
parent.mkdir(parents=True, exist_ok=True)
self._git(["worktree", "add", "--detach", str(path), base_oid])
return path
def advance_task_branch_CAS(self, branch: str, expected_oid: str, new_oid: str) -> str:
if not TASK_BRANCH_RE.fullmatch(branch):
raise AuthorityGitError("TASK_BRANCH_SCOPE_DENIED")
_oid(expected_oid, "expected_oid")
_oid(new_oid, "new_oid")
current = self.read_ref(branch)
if current != expected_oid:
if current == new_oid:
return new_oid
raise AuthorityGitError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
if not self.ancestry_check(expected_oid, new_oid):
raise AuthorityGitError("NEW_OID_NOT_DESCENDANT")
self._git(["update-ref", branch, new_oid, expected_oid])
readback = self.read_ref(branch)
if readback != new_oid:
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
return readback
def ff_CAS_main_after_verified_protected_merge(self, expected_oid: str, new_oid: str) -> str:
_oid(expected_oid, "expected_oid")
_oid(new_oid, "new_oid")
if not self.ancestry_check(expected_oid, new_oid):
raise AuthorityGitError("MAIN_FF_REQUIRED")
current = self.read_ref("refs/heads/main")
if current != expected_oid:
if current == new_oid:
return new_oid
raise AuthorityGitError("AUTHORITY_MAIN_EXPECTATION_MISMATCH")
self._git(["update-ref", "refs/heads/main", new_oid, expected_oid])
readback = self.read_ref("refs/heads/main")
if readback != new_oid:
raise AuthorityGitError("AUTHORITY_MAIN_CAS_READBACK_MISMATCH")
return readback
def commit_file_in_isolated_worktree(
self,
*,
base_oid: str,
files: Mapping[str, bytes],
message: str,
worktree_name: str,
) -> str:
_oid(base_oid, "base_oid")
if not worktree_name or "/" in worktree_name or "\\" in worktree_name or worktree_name in {".", ".."}:
raise AuthorityGitError("WORKTREE_NAME_INVALID")
root = self.worktree_root / worktree_name
if root.exists() or root.is_symlink():
raise AuthorityGitError("WORKTREE_ALREADY_EXISTS")
self.worktree_root.mkdir(parents=True, exist_ok=True)
self._git(["worktree", "add", "--detach", str(root), base_oid])
try:
for relative, content in files.items():
path = Path(relative)
if path.is_absolute() or ".." in path.parts or not path.parts or path.parts[0] == ".git":
raise AuthorityGitError("FILE_PATH_INVALID")
target = root / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
result = subprocess.run(
["git", "-C", str(root), "add", "--", *files.keys()],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise AuthorityGitError("WORKTREE_ADD_FAILED")
result = subprocess.run(
["git", "-C", str(root), "-c", "user.name=aios-durable-job", "-c", "user.email=aios-durable-job@localhost", "commit", "--no-verify", "-m", message],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise AuthorityGitError("WORKTREE_COMMIT_FAILED")
return subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip()
finally:
subprocess.run(["git", f"--git-dir={self.git_dir}", "worktree", "remove", "--force", str(root)], capture_output=True, check=False)
@@ -0,0 +1,104 @@
"""Read-only validation of recovered campaign authorization evidence."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import stat
from typing import Any
class RecoveryEvidenceError(RuntimeError):
pass
def _digest(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
h.update(block)
return h.hexdigest()
def _secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_MISSING")
info = path.stat(follow_symlinks=False)
if info.st_uid != 0 or stat.S_IMODE(info.st_mode) & 0o022:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_PERMISSIONS")
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_INVALID") from exc
if not isinstance(value, dict):
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_OBJECT_REQUIRED")
return value
def load_recovered_authorization(path: str | Path) -> dict[str, Any]:
"""Verify the recovery receipt, sidecar, and all referenced immutable files."""
receipt_path = Path(path)
receipt = _secure_json(receipt_path)
sidecar = receipt_path.with_suffix(receipt_path.suffix + ".sha256")
if sidecar.is_symlink() or not sidecar.is_file():
raise RecoveryEvidenceError("RECOVERY_DIGEST_SIDECAR_MISSING")
expected = sidecar.read_text(encoding="ascii").split()[0]
actual = _digest(receipt_path)
if expected != actual:
raise RecoveryEvidenceError("RECOVERY_DIGEST_MISMATCH")
required = {
"schema",
"campaign_id",
"original_event_id",
"original_authorization_digest",
"signed_grant_id",
"signed_grant_digest",
"task_id",
"repository",
"original_base_oid",
"branch",
"expected_branch_oid",
"recovered_from_existing_evidence",
"new_owner_interaction",
"recovery_state",
"production_side_effect_executed",
"global_writes_enabled",
"grant_consumed",
"owner_authorization_consumed",
"original_receipts_immutable",
"recovered_at",
"evidence_files",
}
if set(receipt) != required or receipt["schema"] != "aios.owner-authorization-recovery.v1":
raise RecoveryEvidenceError("RECOVERY_SCHEMA_INVALID")
if receipt["recovered_from_existing_evidence"] is not True or receipt["new_owner_interaction"] is not False:
raise RecoveryEvidenceError("RECOVERY_NOT_APPEND_ONLY")
if receipt["production_side_effect_executed"] is not False or receipt["global_writes_enabled"] is not False:
raise RecoveryEvidenceError("RECOVERY_SCOPE_INVALID")
if receipt["recovery_state"] != "RECOVERED_PENDING_CONTINUATION_LEASE":
raise RecoveryEvidenceError("RECOVERY_STATE_INVALID")
evidence = receipt["evidence_files"]
if not isinstance(evidence, list) or not evidence:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_LIST_INVALID")
for item in evidence:
if not isinstance(item, dict) or set(item) != {"path", "sha256"}:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_ENTRY_INVALID")
target = Path(item["path"])
if _digest(target) != item["sha256"]:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_DIGEST_MISMATCH")
return {
"schema": receipt["schema"],
"state": receipt["recovery_state"],
"campaign_id": receipt["campaign_id"],
"original_event_id": receipt["original_event_id"],
"original_authorization_digest": receipt["original_authorization_digest"],
"signed_grant_id": receipt["signed_grant_id"],
"task_id": receipt["task_id"],
"repository": receipt["repository"],
"original_base_oid": receipt["original_base_oid"],
"branch": receipt["branch"],
"expected_branch_oid": receipt["expected_branch_oid"],
"digest": actual,
"new_owner_interaction": False,
}
+117 -7
View File
@@ -1,8 +1,14 @@
#!/usr/bin/env python3
from __future__ import annotations
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, uuid
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, urllib.request, uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from version_registry import (
configured_authoritative_git_dir,
configured_repository_identity,
list_registry_versions,
list_registry_versions_report,
)
from typing import Any
try:
@@ -33,6 +39,11 @@ from silent_release_hardening import (
SignedReleasePlanApproval,
SilentReleaseActionService,
)
try:
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
except ModuleNotFoundError:
sys.path.insert(0, str(Path(__file__).resolve().parent))
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
try:
from authoritative_git import (
AuthoritativeGitError,
@@ -67,8 +78,8 @@ HOST = os.getenv('AIOS_GITOPS_HOST', '127.0.0.1')
PORT = int(os.getenv('AIOS_GITOPS_PORT', '18890'))
OPENAPI = WS / 'docs' / 'gitops' / 'openapi-chatgpt-actions.yaml'
BARE_REPO = Path(os.getenv('AIOS_GITOPS_BARE_REPO', str(ROOT / 'runtime' / 'gitea-bin' / 'data' / 'repositories' / 'aios' / 'aios.git')))
AUTHORITATIVE_GIT_DIR = Path(os.getenv('AIOS_GITOPS_AUTHORITATIVE_GIT_DIR')) if os.getenv('AIOS_GITOPS_AUTHORITATIVE_GIT_DIR') else None
REPOSITORY_IDENTITY = os.getenv('AIOS_GITOPS_REPOSITORY_IDENTITY', '')
AUTHORITATIVE_GIT_DIR = configured_authoritative_git_dir()
REPOSITORY_IDENTITY = configured_repository_identity()
SILENT_RELEASE_STATE_ROOT = Path(os.getenv(
'AIOS_SILENT_RELEASE_STATE_ROOT',
'/var/lib/aios-gitops/silent-release',
@@ -120,6 +131,12 @@ def reject_non_read_http(handler, method: str, path: str) -> bool:
return False
if path in SILENT_RELEASE_POST_PATHS:
return False
if (
method == 'POST'
and path in ('/api/file', '/api/commit-file')
and os.getenv('AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED', os.getenv('AIOS_GITOPS_JOB_CONTROLLER_ENABLED', 'false')).lower() in {'1', 'true', 'yes', 'on'}
):
return False
decision = evaluate_git_write(WS)
if decision.allowed:
return False
@@ -393,6 +410,51 @@ class H(BaseHTTPRequestHandler):
if not a['ok']: self.sendj({'error':'unauthorized','hint':'Bearer token required'},401); return None
if '*' not in a['scopes'] and scope not in a['scopes']: self.sendj({'error':'forbidden','required_scope':scope,'account':a['account']},403); return None
return a
def bridge_release(self, path, data):
route = {
'/createTagApprovalRequest': '/v2/tag/approval-request',
'/requestOrCreateTag': '/v2/tag/publish',
'/getTagPublicationStatus': '/v2/tag/status',
'/buildProductionArtifact': '/v2/release/artifact',
'/executeApprovedProductionRelease': '/v2/release/execute',
'/getReleaseConsistency': '/v2/release/consistency',
'/runReleaseAcceptance': '/v2/release/acceptance',
}.get(path)
if route is None:
raise ValueError('governed_release_route_invalid')
base = os.getenv('AIOS_BRIDGE_V2_URL', 'http://127.0.0.1:18892').rstrip('/')
request = urllib.request.Request(
base + route,
data=json.dumps(data, sort_keys=True, separators=(',', ':')).encode('utf-8'),
headers={'Content-Type': 'application/json'},
method='POST',
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.loads(response.read().decode('utf-8'))
return self.sendj(result, response.status)
except urllib.error.HTTPError as exc:
try:
body = json.loads(exc.read().decode('utf-8'))
except Exception:
body = {'error': 'bridge_http_error'}
return self.sendj(body, exc.code)
except (urllib.error.URLError, TimeoutError) as exc:
return self.sendj({'error': 'GOVERNED_BRIDGE_UNAVAILABLE', 'detail': type(exc).__name__}, 503)
def do_POST(self):
path = urllib.parse.urlparse(self.path).path
if path in {
'/createTagApprovalRequest', '/requestOrCreateTag', '/getTagPublicationStatus',
'/buildProductionArtifact', '/executeApprovedProductionRelease',
'/getReleaseConsistency', '/runReleaseAcceptance',
}:
actor = self.need('tag:create')
if not actor:
return
return self.bridge_release(path, self.body())
if reject_non_read_http(self, 'POST', path):
return
return self.sendj({'error': 'not_found', 'path': path}, 404)
def do_GET(self):
u=urllib.parse.urlparse(self.path); path=u.path.rstrip('/') if u.path != '/' else u.path
if path not in READ_ONLY_GET_PATHS: return self.sendj({'error':'not_found','path':path},404)
@@ -410,6 +472,14 @@ class H(BaseHTTPRequestHandler):
))
return self.sendj(data)
if path in ('/api/ver','/api/versions','/ver'):
registry_report = list_registry_versions_report(
WS,
baseline_file=Path(os.getenv('AIOS_GITOPS_BASELINE_AUTHORITY', '/var/lib/aios-gitops-runtime-anchor/baseline/current.json')),
# Do not invent an authoritative repository when authority
# discovery is disabled/omitted. Preserve the unavailable
# authority path (503) instead of turning it into a 404.
repo_git_dir=AUTHORITATIVE_GIT_DIR,
)
snapshot = repository_snapshot(
git_dir=AUTHORITATIVE_GIT_DIR,
repository_identity=REPOSITORY_IDENTITY,
@@ -418,7 +488,8 @@ class H(BaseHTTPRequestHandler):
return self.sendj({
'repository_available': False,
'query_error': snapshot['query_error'],
'registry_versions': [p.name for p in (WS/'versions').glob('*')],
'registry_versions': registry_report['registry_versions'],
**{key: registry_report[key] for key in ('effective_registry_versions', 'runtime_registry_versions', 'attested_projection_versions', 'attested_projection_version', 'source_registry_converged', 'runtime_registry_converged', 'runtime_bundle_contains_current_version', 'runtime_bundle_contains_version', 'registry_consistency_status')},
'local_authoritative_tags': [],
}, 503)
try:
@@ -427,10 +498,11 @@ class H(BaseHTTPRequestHandler):
return self.sendj({
'repository_available': False,
'query_error': str(exc),
'registry_versions': [p.name for p in (WS/'versions').glob('*')],
'registry_versions': registry_report['registry_versions'],
**{key: registry_report[key] for key in ('effective_registry_versions', 'runtime_registry_versions', 'attested_projection_versions', 'attested_projection_version', 'source_registry_converged', 'runtime_registry_converged', 'runtime_bundle_contains_current_version', 'runtime_bundle_contains_version', 'registry_consistency_status')},
'local_authoritative_tags': [],
}, 503)
registry = [p.name for p in (WS/'versions').glob('*')]
registry = registry_report['registry_versions']
return self.sendj({
'registry_versions': registry,
'local_authoritative_tags': tags,
@@ -440,6 +512,7 @@ class H(BaseHTTPRequestHandler):
'versions_dir': registry,
'head': snapshot['authoritative_head_oid'],
'authoritative_head_oid': snapshot['authoritative_head_oid'],
**{key: registry_report[key] for key in ('effective_registry_versions', 'runtime_registry_versions', 'attested_projection_versions', 'attested_projection_version', 'source_registry_converged', 'runtime_registry_converged', 'runtime_bundle_contains_current_version', 'runtime_bundle_contains_version', 'registry_consistency_status')},
})
if path in ('/api/diff','/diff'):
frm=(q.get('from') or [''])[0]
@@ -666,7 +739,44 @@ class H(BaseHTTPRequestHandler):
def file(self,data):
a=self.need('file:write');
if not a: return
b=checkout(data.get('branch'), data.get('base','main'), bool(data.get('create_branch'))); r=write_file(data.get('path'), data.get('content','')); c=commit([r], data.get('message') or f'chore(chatgpt): update {r}'); audit(a['account'],'file',r,{'branch':b,'commit':c}); self.sendj({'status':'committed','branch':b,'path':r,'commit':c})
if os.getenv('AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED', os.getenv('AIOS_GITOPS_JOB_CONTROLLER_ENABLED', 'false')).lower() not in {'1','true','yes','on'}:
return self.sendj({'error':'DURABLE_JOB_CONTROLLER_UNAVAILABLE','retryable':True},503)
try:
path = relpath(str(data.get('path') or ''))
branch = str(data.get('branch') or '')
expected = str(data.get('expected_branch_oid') or '')
campaign_id = str(data.get('campaign_id') or os.getenv('AIOS_GITOPS_CAMPAIGN_ID') or '')
supplied_digest = str(data.get('payload_digest') or '')
content = data.get('content')
payload_bytes = None
if content is not None:
payload_bytes = content.encode('utf-8') if isinstance(content, str) else json.dumps(content, sort_keys=True, ensure_ascii=False).encode('utf-8')
payload_digest = supplied_digest or hashlib.sha256(payload_bytes or b'').hexdigest()
if not campaign_id or not branch or not expected:
return self.sendj({'error':'JOB_BINDING_REQUIRED','retryable':False},409)
request = {
'campaign_id': campaign_id,
'repository': REPOSITORY_IDENTITY or 'aios/aios',
'operation': 'commit-file',
'branch': branch,
'expected_branch_oid': expected,
'payload_digest': payload_digest,
# The controller persists only the binding and digest. Raw
# content is never copied into durable state or logs.
}
receipt = GitOpsJobController(
JobStore(Path(os.getenv('AIOS_GITOPS_JOB_ROOT', '/var/lib/aios-gitops/jobs')))
).submit(request, payload=payload_bytes)
return self.sendj({
'job_id': receipt.job_id,
'state': receipt.state.value,
'side_effect_state': receipt.side_effect_state,
'retryable': receipt.http_status == 503,
}, receipt.http_status)
except ServiceUnavailable as exc:
return self.sendj({'error':str(exc),'layer':'durable_job','retryable':True,'side_effect_state':'UNKNOWN'},503)
except JobError as exc:
return self.sendj({'error':str(exc),'retryable':False},409)
def commit(self,data):
a=self.need('commit:create');
if not a: return
@@ -0,0 +1,170 @@
"""ChatGPT GitOps Bridge v2 contract and governed release operations."""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
from pathlib import Path
from typing import Any, Callable, Mapping
BRIDGE_SCHEMA_VERSION = "aios.chatgpt-gitops-bridge.v2"
BRIDGE_OPERATIONS = (
"getRuntimeStatus",
"getGovernedWriteCapabilities",
"createGovernedTask",
"acquireWriterLease",
"commitFileGoverned",
"commitFilesGoverned",
"publishExistingBranchGoverned",
"getGitOpsJob",
"resumeGitOpsJob",
"reconcileGitOpsJob",
"createTagApprovalRequest",
"requestOrCreateTag",
"getTagPublicationStatus",
"buildProductionArtifact",
"executeApprovedProductionRelease",
"getReleaseConsistency",
"runReleaseAcceptance",
"getControlPlaneSnapshot",
)
OPERATION_REQUIRED_FIELDS = {
"createGovernedTask": ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key"),
"acquireWriterLease": ("schema", "task_id", "repository", "branch_mode", "base_oid", "expected_branch_oid", "target_branch", "generation", "lease_seconds"),
"commitFileGoverned": ("task_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "path", "content_base64", "message"),
"commitFilesGoverned": ("task_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "files", "message"),
"publishExistingBranchGoverned": ("repository", "branch", "expected_branch_oid", "owner_approval_reference", "idempotency_key"),
"getGitOpsJob": ("job_id",),
"resumeGitOpsJob": ("job_id", "expected_revision"),
"reconcileGitOpsJob": ("job_id", "expected_revision"),
"createTagApprovalRequest": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "owner_approval", "execute"),
"requestOrCreateTag": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "execute"),
"getTagPublicationStatus": ("version",),
"buildProductionArtifact": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"executeApprovedProductionRelease": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "execute"),
"getReleaseConsistency": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"runReleaseAcceptance": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"getControlPlaneSnapshot": (),
}
class RuntimeDriftError(RuntimeError):
pass
class BridgeV2Error(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class ControlPlaneReleaseManifest:
control_plane_release_version: str
control_plane_target_oid: str
artifact_sha256: str
runtime_path: Path
bridge_schema_version: str
repository_identity: str
authoritative_git_dir: Path
deployment_state: str
def as_dict(self) -> dict[str, object]:
return {
"control_plane_release_version": self.control_plane_release_version,
"control_plane_target_oid": self.control_plane_target_oid,
"artifact_sha256": self.artifact_sha256,
"runtime_path": str(self.runtime_path),
"bridge_schema_version": self.bridge_schema_version,
"repository_identity": self.repository_identity,
"authoritative_git_dir": str(self.authoritative_git_dir),
"deployment_state": self.deployment_state,
}
def validate(self, artifact: Path, *, observed_oid: str, approved_root: Path = Path("/opt/aios/gitops-control-plane")) -> dict[str, object]:
try:
runtime = self.runtime_path.resolve(strict=True)
root = approved_root.resolve(strict=True)
artifact_path = artifact.resolve(strict=True)
runtime.relative_to(root)
artifact_path.relative_to(runtime)
except (OSError, ValueError):
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT") from None
mutable_markers = {"workspace", "workspaces", "worktree", "worktrees"}
if mutable_markers.intersection(part.lower() for part in runtime.parts):
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
valid = (
len(self.control_plane_target_oid) == 40
and observed_oid == self.control_plane_target_oid
and len(self.artifact_sha256) == 64
and hashlib.sha256(artifact_path.read_bytes()).hexdigest() == self.artifact_sha256
and self.bridge_schema_version == BRIDGE_SCHEMA_VERSION
and self.repository_identity == "aios/aios"
and self.authoritative_git_dir == Path("/data/aios-gitops/repo.git")
and self.authoritative_git_dir.is_absolute()
and self.deployment_state == "STAGING"
)
if not valid:
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
return {**self.as_dict(), "drift_status": "PASS"}
class BridgeV2Adapter:
"""Thin operation adapter; all mutating handlers are injected seams."""
def __init__(self, *, runtime_status: Mapping[str, Any], handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None = None) -> None:
self.runtime_status = dict(runtime_status)
self.handlers = dict(handlers or {})
def getRuntimeStatus(self) -> dict[str, Any]:
return {"bridge_schema_version": BRIDGE_SCHEMA_VERSION, **self.runtime_status}
def getGovernedWriteCapabilities(self) -> dict[str, Any]:
return {
"bridge_schema_version": BRIDGE_SCHEMA_VERSION,
"legacy_writes_enabled": False,
"governed_writes_enabled": True,
"operations": list(BRIDGE_OPERATIONS),
"force_push": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"production_write": False,
"approved_release_operations": True,
"approved_release_scope": {
"version": "V2R2C1",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"approval_plan_id": "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e",
},
}
@staticmethod
def legacy_write_disabled() -> dict[str, object]:
return {"error": "LEGACY_WRITE_DISABLED", "replacement": "createGovernedTask", "governed_writes_available": True}
def _call(self, operation: str, request: Mapping[str, Any]) -> Any:
required = OPERATION_REQUIRED_FIELDS[operation]
if any(key not in request for key in required):
raise BridgeV2Error("BRIDGE_REQUEST_FIELDS_MISSING")
handler = self.handlers.get(operation)
if handler is None:
raise BridgeV2Error("GOVERNED_HANDLER_UNAVAILABLE")
return handler(dict(request))
def createGovernedTask(self, request: Mapping[str, Any]) -> Any: return self._call("createGovernedTask", request)
def acquireWriterLease(self, request: Mapping[str, Any]) -> Any: return self._call("acquireWriterLease", request)
def commitFileGoverned(self, request: Mapping[str, Any]) -> Any: return self._call("commitFileGoverned", request)
def commitFilesGoverned(self, request: Mapping[str, Any]) -> Any: return self._call("commitFilesGoverned", request)
def publishExistingBranchGoverned(self, request: Mapping[str, Any]) -> Any: return self._call("publishExistingBranchGoverned", request)
def getGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("getGitOpsJob", request)
def resumeGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("resumeGitOpsJob", request)
def reconcileGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("reconcileGitOpsJob", request)
def createTagApprovalRequest(self, request: Mapping[str, Any]) -> Any: return self._call("createTagApprovalRequest", request)
def requestOrCreateTag(self, request: Mapping[str, Any]) -> Any: return self._call("requestOrCreateTag", request)
def getTagPublicationStatus(self, request: Mapping[str, Any]) -> Any: return self._call("getTagPublicationStatus", request)
def buildProductionArtifact(self, request: Mapping[str, Any]) -> Any: return self._call("buildProductionArtifact", request)
def executeApprovedProductionRelease(self, request: Mapping[str, Any]) -> Any: return self._call("executeApprovedProductionRelease", request)
def getReleaseConsistency(self, request: Mapping[str, Any]) -> Any: return self._call("getReleaseConsistency", request)
def runReleaseAcceptance(self, request: Mapping[str, Any]) -> Any: return self._call("runReleaseAcceptance", request)
def getControlPlaneSnapshot(self, request: Mapping[str, Any] | None = None) -> Any: return self._call("getControlPlaneSnapshot", request or {})
@@ -0,0 +1,464 @@
"""Loopback-only governed Bridge v2 HTTP gateway with durable handlers."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import base64
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import re
import sqlite3
from typing import Any, Mapping
from aios_authority_git_executor import AuthorityGitExecutor
from aios_chatgpt_bridge_v2 import BridgeV2Adapter, BridgeV2Error, OPERATION_REQUIRED_FIELDS
from aios_continuation_executor import AuthorityGitAdapter, ContinuationCommit, ContinuationExecutor, ContinuationPermissions, ContinuationRuntimeBinding, DetachedGitWorkspace, GiteaContinuationRemote, ServerSideCredentialProjection
from aios_continuation_lease import WriterLeaseRequestV2
from aios_control_plane_snapshot import build_control_plane_snapshot
from aios_gitops_job_controller import GitOpsJobController, JobStore
from aios_governed_task_bootstrap import BootstrapError, GovernedTaskBootstrap
from aios_existing_branch_publisher import ExistingBranchPublisher
from governed_production_release import build_executor_config
MAX_BODY = 2 * 1024 * 1024
ROUTES = {
"/v2/runtime": "getRuntimeStatus",
"/v2/capabilities": "getGovernedWriteCapabilities",
"/v2/governed-task": "createGovernedTask",
"/v2/writer-lease": "acquireWriterLease",
"/v2/commit-file": "commitFileGoverned",
"/v2/commit-files": "commitFilesGoverned",
"/v2/branch/publish": "publishExistingBranchGoverned",
"/v2/job": "getGitOpsJob",
"/v2/job/resume": "resumeGitOpsJob",
"/v2/job/reconcile": "reconcileGitOpsJob",
"/v2/tag/approval-request": "createTagApprovalRequest",
"/v2/tag/publish": "requestOrCreateTag",
"/v2/tag/status": "getTagPublicationStatus",
"/v2/release/artifact": "buildProductionArtifact",
"/v2/release/execute": "executeApprovedProductionRelease",
"/v2/release/consistency": "getReleaseConsistency",
"/v2/release/acceptance": "runReleaseAcceptance",
}
@dataclass(frozen=True, slots=True)
class RuntimeConfig:
target_oid: str
authoritative_git_dir: Path
state_root: Path
v2_job_root: Path
token_path: Path
ssh_key_path: Path
remote_name: str = "gitea-http"
repository: str = "aios/aios"
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
v2_task_id: str = "TASK-P0-CHATGPT-BRIDGE-V2-CONTROL-PLANE-CONVERGENCE-20260809"
v2_branch: str = "agent/p0-chatgpt-bridge-v2-control-plane-convergence-20260809"
target_version: str = "V2R2C1"
registry_root: Path = Path("/var/lib/aios-gitops/runtime/registry-projection")
baseline_path: Path = Path("/var/lib/aios-gitops-runtime-anchor/baseline/current.json")
health_url: str = "http://127.0.0.1:18927/health"
def _safe(value: str) -> str:
return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-")[:120] or "runtime"
class GovernedRuntime:
def __init__(self, config: RuntimeConfig) -> None:
self.config = config
config.state_root.mkdir(parents=True, exist_ok=True)
self.authority = AuthorityGitExecutor(config.authoritative_git_dir, config.state_root / "worktrees")
self.bootstrap = GovernedTaskBootstrap(self.authority, config.state_root, remote_name=config.remote_name, token_path=config.token_path, ssh_key_path=config.ssh_key_path, api_base=config.api_base)
self.runtimes: dict[tuple[str, int], tuple[Any, GitOpsJobController, ContinuationExecutor]] = {}
self.controllers: dict[str, GitOpsJobController] = {}
self.bindings: dict[str, dict[str, Any]] = {}
self.release = build_executor_config(config)
metadata_root = config.state_root / "task-bindings"
if metadata_root.is_dir():
for metadata in metadata_root.glob("*.json"):
try:
value = json.loads(metadata.read_text(encoding="utf-8"))
if isinstance(value, dict) and value.get("task_id"):
self.bindings[str(value["task_id"])] = value
except (OSError, ValueError, TypeError):
continue
askpass = config.state_root / "bridge-v2-askpass.sh"
if not askpass.exists():
askpass.write_text('#!/bin/sh\ncase "$1" in *Username*) printf "%s\\n" "$GITEA_MACHINE_ACTOR" ;; *) cat "$CREDENTIAL_FILE" ;; esac\n', encoding="utf-8")
askpass.chmod(0o700)
os.environ.setdefault("GIT_ASKPASS", str(askpass)); os.environ.setdefault("GIT_ASKPASS_REQUIRE", "force"); os.environ.setdefault("GIT_TERMINAL_PROMPT", "0"); os.environ.setdefault("GITEA_MACHINE_ACTOR", "aios-autonomy-agent")
def runtime_status(self) -> dict[str, Any]:
return {
"status": "active",
"health": 200,
"bridge_schema_version": "aios.chatgpt-gitops-bridge.v2",
"control_plane_release_version": self.config.target_version,
"control_plane_target_oid": self.config.target_oid,
"runtime_release_commit": self.config.target_oid,
"runtime_path": str(self.config.state_root),
"artifact_sha256": os.environ.get("AIOS_CONTROL_PLANE_ARTIFACT_SHA256"),
"deployment_state": "STAGING",
"drift_status": "PASS",
"repository": self.config.repository,
"repository_identity": self.config.repository,
"authoritative_git_dir": str(self.config.authoritative_git_dir),
"legacy_global_writes_enabled": False,
}
def _job_root(self, task_id: str) -> Path:
return self.config.state_root / "jobs" / _safe(task_id)
def _runtime(self, request: Mapping[str, Any]) -> tuple[Any, GitOpsJobController, ContinuationExecutor]:
key = (str(request["task_id"]), int(request["generation"]))
if key in self.runtimes: return self.runtimes[key]
lease = WriterLeaseRequestV2(task_id=str(request["task_id"]), repository=str(request["repository"]), branch_mode=str(request.get("branch_mode", "CONTINUE_EXISTING")), base_oid=str(request["base_oid"]), expected_branch_oid=str(request["expected_branch_oid"]), target_branch=str(request["target_branch"]), generation=int(request["generation"]), lease_seconds=int(request.get("lease_seconds", 900)))
lease.validate(AuthorityGitAdapter(self.authority))
worktree_path = self.config.state_root / "worktrees" / _safe(lease.task_id)
if worktree_path.exists():
workspace = DetachedGitWorkspace(worktree_path)
else:
self.authority.create_detached_worktree(lease.expected_branch_oid, _safe(lease.task_id), self.config.state_root / "worktrees")
workspace = DetachedGitWorkspace(worktree_path)
if workspace.head_oid() != lease.expected_branch_oid or not workspace.is_detached(): raise RuntimeError("WORKTREE_HEAD_MISMATCH")
job_root = self._job_root(lease.task_id); controller = GitOpsJobController(JobStore(job_root, payload_root=job_root / "payloads")); credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path); remote = GiteaContinuationRemote(workspace=worktree_path, credential=credential, remote_name=self.config.remote_name, repository=lease.repository, api_base=self.config.api_base); executor = ContinuationExecutor(controller, AuthorityGitAdapter(self.authority), workspace, remote, credential)
binding = ContinuationRuntimeBinding(request=lease, workspace_path=worktree_path, lease_not_before=datetime.now(timezone.utc) - timedelta(seconds=1), lease_expires_at=datetime.now(timezone.utc) + timedelta(seconds=lease.lease_seconds), original_receipt_valid=True, original_grant_consumed=True, permissions=ContinuationPermissions())
result = (binding, controller, executor); self.runtimes[key] = result; self.controllers[lease.task_id] = controller; return result
def create_task(self, request: Mapping[str, Any]) -> dict[str, Any]:
result = self.bootstrap.create(request); self.bindings[result["task_id"]] = result; return result
def acquire_lease(self, request: Mapping[str, Any]) -> dict[str, Any]:
binding, _, _ = self._runtime(request)
req = binding.request
return {"schema": WriterLeaseRequestV2.schema, "task_id": req.task_id, "repository": req.repository, "branch_mode": req.branch_mode, "base_oid": req.base_oid, "expected_branch_oid": req.expected_branch_oid, "target_branch": req.target_branch, "generation": req.generation, "lease_seconds": req.lease_seconds, "create_target_branch": req.branch_mode == "CREATE_NEW", "worktree_mode": "detached_at_base_oid" if req.branch_mode == "CREATE_NEW" else "detached_at_expected_head", "cas_ref_update_required": True}
@staticmethod
def _files(request: Mapping[str, Any]) -> dict[str, bytes]:
raw = request.get("files") or {request["path"]: request["content_base64"]}; pairs = raw.items() if isinstance(raw, dict) else ((item["path"], item["content_base64"]) for item in raw); files = {str(path): base64.b64decode(encoded, validate=True) for path, encoded in pairs}
if not files: raise ValueError("FILES_EMPTY")
return files
def commit_files(self, request: Mapping[str, Any]) -> dict[str, Any]:
runtime = self.runtimes.get((str(request["task_id"]), int(request["generation"])))
if runtime is None: raise RuntimeError("LEASE_NOT_FOUND")
binding, _, executor = runtime
if request["target_branch"] != binding.request.target_branch or request["expected_branch_oid"] != binding.request.expected_branch_oid: raise RuntimeError("LEASE_BINDING_CONFLICT")
commit = ContinuationCommit(idempotency_key=str(request["idempotency_key"]), files=self._files(request), message=str(request["message"]))
executor.workspace.materialize_files(commit.files)
return executor.execute(binding, commit)
def publish_existing_branch(self, request: Mapping[str, Any]) -> dict[str, Any]:
expected = str(request["expected_branch_oid"])
key = _safe("existing-branch-" + str(request["idempotency_key"]))
worktree_root = self.config.state_root / "worktrees"
worktree_path = worktree_root / key
if not worktree_path.exists():
self.authority.create_detached_worktree(expected, key, worktree_root)
workspace = DetachedGitWorkspace(worktree_path)
credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path)
remote = GiteaContinuationRemote(
workspace=worktree_path,
credential=credential,
remote_name=self.config.remote_name,
repository=self.config.repository,
api_base=self.config.api_base,
)
publisher = ExistingBranchPublisher(
self.authority,
remote,
self.config.state_root / "existing-branch-publications",
)
projected = False
try:
credential.project()
projected = True
return publisher.publish(request)
finally:
if projected:
credential.revoke()
def _controller_for_job(self, job_id: str) -> GitOpsJobController:
for controller in self.controllers.values():
try:
controller.store.read(job_id)
return controller
except Exception:
continue
jobs_root = self.config.state_root / "jobs"
if jobs_root.is_dir():
for root in jobs_root.iterdir():
if not root.is_dir():
continue
controller = GitOpsJobController(JobStore(root, payload_root=root / "payloads"))
try:
controller.store.read(job_id)
self.controllers[str(job_id)] = controller
return controller
except Exception:
continue
raise RuntimeError("JOB_NOT_FOUND")
@staticmethod
def _valid_oid(value: object) -> bool:
return isinstance(value, str) and re.fullmatch(r"[0-9a-f]{40}", value) is not None
def _reconstruct_legacy_binding(
self,
row: Mapping[str, Any],
controller: GitOpsJobController,
) -> dict[str, Any]:
raw = json.loads(row["request_json"])
binding = raw.get("binding", raw)
if not isinstance(binding, dict):
raise RuntimeError("DURABLE_BINDING_INVALID")
task_id = str(binding.get("task_id") or binding.get("campaign_id") or "").strip()
repository = str(binding.get("repository") or "").strip()
operation = str(binding.get("operation") or row.get("operation") or "").strip()
branch = str(binding.get("target_branch") or binding.get("branch") or row.get("branch") or "").strip()
expected = str(binding.get("expected_branch_oid") or row.get("expected_branch_oid") or "").strip()
base = binding.get("base_oid") or binding.get("lineage_anchor_oid")
if not base:
with controller.store._connect() as connection:
lease_row = connection.execute(
"SELECT lineage_anchor_oid FROM branch_leases WHERE job_id=? ORDER BY revision DESC LIMIT 1",
(str(row["job_id"]),),
).fetchone()
if lease_row is not None:
base = lease_row["lineage_anchor_oid"]
base = str(base or "").strip()
branch_mode = str(
binding.get("branch_mode")
or ("CREATE_NEW" if operation.endswith("_CREATE_NEW") else "CONTINUE_EXISTING")
).strip()
try:
generation = int(binding.get("generation") or 0)
except (TypeError, ValueError) as exc:
raise RuntimeError("DURABLE_BINDING_GENERATION_INVALID") from exc
if (
not task_id
or repository != self.config.repository
or not branch.startswith("agent/")
or branch in {"main", "master"}
or branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}
or generation < 1
or not self._valid_oid(base)
or not self._valid_oid(expected)
):
raise RuntimeError("DURABLE_BINDING_INVALID")
current = self.authority.read_ref("refs/heads/" + branch)
if (
current is None
or not self.authority.commit_exists(base)
or not self.authority.commit_exists(expected)
or not self.authority.ancestry_check(base, current)
or not self.authority.ancestry_check(expected, current)
):
raise RuntimeError("DURABLE_BINDING_LINEAGE_INVALID")
return {
"task_id": task_id,
"repository": repository,
"branch_mode": branch_mode,
"base_oid": base,
"expected_branch_oid": expected,
"target_branch": branch,
"generation": generation,
"lease_seconds": int(binding.get("lease_seconds") or 900),
}
def _executor_from_row(
self,
row: Mapping[str, Any],
controller: GitOpsJobController,
) -> ContinuationExecutor:
request = self._reconstruct_legacy_binding(row, controller)
lease = WriterLeaseRequestV2(**request)
worktree_path = self.config.state_root / "worktrees" / _safe(lease.task_id)
if not worktree_path.exists():
current = self.authority.read_ref("refs/heads/" + lease.target_branch)
if current is None:
raise RuntimeError("DURABLE_WORKTREE_BRANCH_MISSING")
self.authority.create_detached_worktree(
current,
_safe(lease.task_id),
self.config.state_root / "worktrees",
)
workspace = DetachedGitWorkspace(worktree_path)
credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path)
remote = GiteaContinuationRemote(
workspace=worktree_path,
credential=credential,
remote_name=self.config.remote_name,
repository=lease.repository,
api_base=self.config.api_base,
)
return ContinuationExecutor(
controller,
AuthorityGitAdapter(self.authority),
workspace,
remote,
credential,
)
def job(self, request: Mapping[str, Any], action: str) -> dict[str, Any]:
job_id = str(request["job_id"])
controller = self._controller_for_job(job_id)
row = controller.store.read(job_id)
if action == "get":
return controller.store._receipt(row, duplicate=True).as_dict()
if action == "resume":
return controller.resume(job_id).as_dict()
return self._executor_from_row(row, controller).reconcile(job_id)
def _snapshot_task(self) -> dict[str, Any]:
candidates: list[dict[str, Any]] = []
for value in self.bindings.values():
task_id = str(value.get("task_id") or "").strip()
branch = str(value.get("target_branch") or "").strip()
if not task_id or not branch.startswith("agent/"):
continue
authority = self.authority.read_ref("refs/heads/" + branch)
try:
generation = int(value.get("generation") or 0)
except (TypeError, ValueError):
generation = 0
reconciliation_state = "OBSERVATION_REQUIRED"
db = self.config.state_root / "jobs" / _safe(task_id) / "state.sqlite3"
if db.is_file():
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
try:
lease = connection.execute(
"SELECT MAX(generation) FROM branch_leases WHERE branch=?",
(branch,),
).fetchone()
generation = max(generation, int((lease or (0,))[0] or 0))
latest = connection.execute(
"SELECT state, side_effect_state FROM jobs WHERE branch=? ORDER BY updated_at DESC LIMIT 1",
(branch,),
).fetchone()
if latest is not None:
reconciliation_state = (
"COMPLETED"
if latest[0] == "COMPLETED"
else str(latest[1] or latest[0])
)
finally:
connection.close()
candidates.append(
{
"task_id": task_id,
"branch": branch,
"authority_branch_oid": authority,
"gitea_git_oid": None,
"gitea_api_oid": None,
"commit_visible": None,
"generation": generation,
"reconciliation_state": reconciliation_state,
}
)
if not candidates:
return {}
return max(
candidates,
key=lambda item: (int(item["generation"] or 0), bool(item["authority_branch_oid"])),
)
def snapshot(self) -> dict[str, Any]:
task = self._snapshot_task()
return build_control_plane_snapshot(target_version=self.config.target_version, runtime=self.runtime_status(), repository={"identity": self.config.repository, "authority_git_dir": str(self.config.authoritative_git_dir), "authority_main_oid": self.authority.read_ref("refs/heads/main")}, production={"baseline_version": "V2R1C7", "target_oid": "c7adece1cc8beffe7c718d53c775898b45d6c7f8", "deployment_state": "ACTIVE"}, task=task, capabilities={"legacy_writes_enabled": False, "governed_writes_enabled": True, "force_push": False, "main_write": False, "tag_write": False, "baseline_write": False, "production_write": False}, credential_broker={"status": "SERVER_SIDE_ONLY", "provider": "FileCredentialProvider"})
def handlers(self) -> dict[str, Any]:
return {
"createGovernedTask": self.create_task,
"acquireWriterLease": self.acquire_lease,
"commitFileGoverned": lambda request: self.commit_files({**request, "files": {request["path"]: request["content_base64"]}}),
"commitFilesGoverned": self.commit_files,
"publishExistingBranchGoverned": self.publish_existing_branch,
"getGitOpsJob": lambda request: self.job(request, "get"),
"resumeGitOpsJob": lambda request: self.job(request, "resume"),
"reconcileGitOpsJob": lambda request: self.job(request, "reconcile"),
"getControlPlaneSnapshot": lambda request: self.snapshot(),
"createTagApprovalRequest": self.release.approval_request,
"requestOrCreateTag": self.release.publish_tag,
"getTagPublicationStatus": self.release.tag_status,
"buildProductionArtifact": self.release.build_artifact,
"executeApprovedProductionRelease": self.release.execute_request,
"getReleaseConsistency": self.release.consistency,
"runReleaseAcceptance": self.release.acceptance,
}
def build_governed_handlers(runtime_config: RuntimeConfig | Mapping[str, Any]) -> Mapping[str, Any]:
if isinstance(runtime_config, Mapping): runtime_config = RuntimeConfig(**runtime_config)
return GovernedRuntime(runtime_config).handlers()
class _HTTPError(RuntimeError): pass
class BridgeHandler(BaseHTTPRequestHandler):
adapter: BridgeV2Adapter | None = None
def log_message(self, *_args: Any) -> None: return
def _json(self, value: Any, status: int = 200) -> None:
raw = json.dumps(value, sort_keys=True, separators=(",", ":")).encode(); self.send_response(status); self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(raw))); self.end_headers(); self.wfile.write(raw)
def _body(self) -> dict[str, Any]:
try: length = int(self.headers.get("Content-Length", "-1"))
except ValueError as exc: raise _HTTPError("CONTENT_LENGTH_INVALID") from exc
if length < 0 or length > MAX_BODY: raise _HTTPError("REQUEST_TOO_LARGE")
value = json.loads(self.rfile.read(length).decode("utf-8"))
if not isinstance(value, dict): raise _HTTPError("JSON_OBJECT_REQUIRED")
return value
def _allowed(self, operation: str, body: Mapping[str, Any]) -> None:
allowed = set(OPERATION_REQUIRED_FIELDS[operation]) | {"schema", "lease_seconds", "create_target_branch", "worktree_mode", "cas_ref_update_required", "expected_branch_oid", "generation", "branch_mode"}
if operation == "commitFilesGoverned": allowed.add("files")
allowed.update({"owner_approval", "execute"})
if set(body) - allowed: raise _HTTPError("UNKNOWN_FIELDS")
def do_GET(self) -> None:
if self.adapter is None: self._json({"error": "RUNTIME_UNAVAILABLE"}, 503); return
if self.path == "/v2/runtime": self._json(self.adapter.getRuntimeStatus()); return
if self.path == "/v2/capabilities": self._json(self.adapter.getGovernedWriteCapabilities()); return
if self.path == "/v2/control-plane-snapshot": self._json(self.adapter.getControlPlaneSnapshot({})); return
self._json({"error": "NOT_FOUND"}, 404)
def do_POST(self) -> None:
operation = ROUTES.get(self.path)
if operation is None or operation in ("getRuntimeStatus", "getGovernedWriteCapabilities") or self.adapter is None: self._json({"error": "NOT_FOUND"}, 404); return
try:
body = self._body(); self._allowed(operation, body); self._json(getattr(self.adapter, operation)(body))
except _HTTPError as exc: self._json({"error": str(exc)}, 413 if str(exc) == "REQUEST_TOO_LARGE" else 400)
except (BootstrapError, ValueError, BridgeV2Error) as exc: self._json({"error": str(exc)}, 409 if "CONFLICT" in str(exc) else 400)
except Exception as exc: self._json({"error": str(exc) or type(exc).__name__}, 409 if any(token in str(exc) for token in ("CONFLICT", "EXPECTATION", "MONOTONIC")) else 503)
def serve(adapter: BridgeV2Adapter, host: str = "127.0.0.1", port: int = 18892) -> None:
BridgeHandler.adapter = adapter; ThreadingHTTPServer((host, port), BridgeHandler).serve_forever()
def main() -> None:
state_root = Path("/var/lib/aios-gitops/bridge-v2-staging")
token_path = Path(os.environ.get("AIOS_GITEA_TOKEN_FILE", "/run/credentials/aios-chatgpt-bridge-v2-staging.service/gitea_token"))
ssh_key_path = Path(os.environ.get("AIOS_GITEA_SSH_KEY_PATH", "/home/aiman/.ssh/id_ed25519_codex_tmp"))
os.environ["CREDENTIAL_FILE"] = str(token_path)
os.environ.setdefault("GITEA_MACHINE_ACTOR", "aios-autonomy-agent")
config = RuntimeConfig(
os.environ.get("AIOS_CONTROL_PLANE_TARGET_OID", ""),
Path("/data/aios-gitops/repo.git"),
state_root,
state_root / "jobs",
token_path,
ssh_key_path,
remote_name=os.environ.get("AIOS_GITEA_REMOTE", "gitea-http"),
api_base=os.environ.get("AIOS_GITEA_API_BASE", "https://g.cnnetmkt.top/git/api/v1"),
target_version=os.environ.get("AIOS_CONTROL_PLANE_TARGET_VERSION", "V2R2C1"),
)
runtime = GovernedRuntime(config)
serve(
BridgeV2Adapter(runtime_status=runtime.runtime_status(), handlers=runtime.handlers()),
port=int(os.environ.get("AIOS_BRIDGE_V2_PORT", "18892")),
)
if __name__ == "__main__": main()
@@ -0,0 +1,594 @@
"""Governed continuation executor for exact existing task branches.
The executor composes the existing continuation lease and durable Job store.
All Git and Gitea side effects live behind injected narrow interfaces so the
state machine can prove Authority, Git-network, API-branch, and commit-object
agreement before it reports completion.
"""
from __future__ import annotations
import base64
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path, PurePosixPath
import re
import subprocess
from typing import Callable, Mapping, Protocol
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlsplit
from urllib.request import Request, urlopen
from aios_authority_git_executor import AuthorityGitExecutor
from aios_continuation_lease import WriterLeaseRequestV2
from aios_gitops_job_controller import GitOpsJobController, JobState
from aios_gitops_reconciliation_core import ReconciliationAction, RemoteObservation, decide_reconciliation
from authorized_task_writer_provisioning import validate_writer_lease_request_v2
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SECRET_PATTERNS = (
re.compile(rb"gh[pousr]_[A-Za-z0-9]{20,}"),
re.compile(rb"(?i)(?:token|password|secret|api[_-]?key)\s*[:=]\s*[\"'][^\"'\s]{12,}[\"']"),
re.compile(rb"-----BEGIN " + rb"(?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
)
class ContinuationExecutionError(RuntimeError):
"""Stable fail-closed continuation execution error."""
class DetachedWorkspace(Protocol):
def is_detached(self) -> bool: ...
def head_oid(self) -> str: ...
def commit_files(self, files: Mapping[str, bytes], message: str) -> str: ...
class AuthorityPort(Protocol):
def branch_exists(self, branch: str) -> bool: ...
def branch_head(self, branch: str) -> str: ...
def is_ancestor(self, older: str, newer: str) -> bool: ...
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
class RemotePort(Protocol):
def repository_identity(self) -> str: ...
def push_exact(self, branch: str, expected: str, new: str) -> None: ...
def read_git_ref(self, branch: str) -> str | None: ...
def read_api_branch(self, branch: str) -> str | None: ...
def commit_visible(self, oid: str) -> bool: ...
class CredentialProjection(Protocol):
def project(self) -> None: ...
def revoke(self) -> None: ...
def _run(argv: list[str], *, cwd: Path | None = None, environment: Mapping[str, str] | None = None) -> str:
result = subprocess.run(
argv,
cwd=cwd,
env=None if environment is None else dict(environment),
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise ContinuationExecutionError("SUBPROCESS_FAILED")
return result.stdout.strip()
@dataclass(slots=True)
class AuthorityGitAdapter:
"""Branch-name adapter over the existing narrow AuthorityGitExecutor."""
executor: AuthorityGitExecutor
@staticmethod
def _ref(branch: str) -> str:
return f"refs/heads/{branch}"
def branch_exists(self, branch: str) -> bool:
return self.executor.read_ref(self._ref(branch)) is not None
def branch_head(self, branch: str) -> str:
value = self.executor.read_ref(self._ref(branch))
if value is None:
raise ContinuationExecutionError("EXISTING_BRANCH_MISSING")
return value
def is_ancestor(self, older: str, newer: str) -> bool:
return self.executor.ancestry_check(older, newer)
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str:
return self.executor.advance_task_branch_CAS(self._ref(branch), expected, new)
@dataclass(slots=True)
class DetachedGitWorkspace:
path: Path
def __post_init__(self) -> None:
if not self.path.is_absolute() or self.path.is_symlink() or not self.path.is_dir():
raise ContinuationExecutionError("WORKTREE_PATH_INVALID")
top = Path(_run(["git", "rev-parse", "--show-toplevel"], cwd=self.path)).resolve()
if top != self.path.resolve():
raise ContinuationExecutionError("WORKTREE_PATH_INVALID")
def is_detached(self) -> bool:
result = subprocess.run(
["git", "symbolic-ref", "-q", "HEAD"],
cwd=self.path,
capture_output=True,
check=False,
)
return result.returncode == 1
def head_oid(self) -> str:
return _run(["git", "rev-parse", "HEAD"], cwd=self.path)
def _dirty_paths(self) -> set[str]:
command = subprocess.run(
["git", "status", "--porcelain=v1", "--untracked-files=all"],
cwd=self.path,
text=True,
capture_output=True,
check=False,
)
if command.returncode != 0:
raise ContinuationExecutionError("SUBPROCESS_FAILED")
output = command.stdout
result: set[str] = set()
for line in output.splitlines():
path = line[3:]
if " -> " in path:
path = path.split(" -> ", 1)[1]
result.add(path)
return result
def materialize_files(self, files: Mapping[str, bytes]) -> None:
dirty = self._dirty_paths()
expected_paths = set(files)
if dirty and dirty != expected_paths:
raise ContinuationExecutionError("WORKTREE_DIRTY_SCOPE_MISMATCH")
for relative, content in files.items():
target = (self.path / relative).resolve()
try:
target.relative_to(self.path.resolve())
except ValueError as exc:
raise ContinuationExecutionError("COMMIT_PATH_REJECTED") from exc
if target.is_symlink():
raise ContinuationExecutionError("COMMIT_PATH_REJECTED")
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
def commit_files(self, files: Mapping[str, bytes], message: str) -> str:
expected_paths = set(files)
if self._dirty_paths() != expected_paths:
raise ContinuationExecutionError("WORKTREE_DIRTY_SCOPE_MISMATCH")
for relative, content in files.items():
target = (self.path / relative).resolve()
try:
target.relative_to(self.path.resolve())
except ValueError as exc:
raise ContinuationExecutionError("COMMIT_PATH_REJECTED") from exc
if not target.is_file() or target.is_symlink() or target.read_bytes() != content:
raise ContinuationExecutionError("AUTHORING_PAYLOAD_MISMATCH")
_run(["git", "add", "--", *sorted(expected_paths)], cwd=self.path)
staged = set(_run(["git", "diff", "--cached", "--name-only"], cwd=self.path).splitlines())
if staged != expected_paths:
raise ContinuationExecutionError("STAGED_SCOPE_MISMATCH")
environment = os.environ.copy()
environment.update(
{
"GIT_AUTHOR_NAME": "AIOS Governed Continuation",
"GIT_AUTHOR_EMAIL": "aios-governed-continuation@localhost",
"GIT_COMMITTER_NAME": "AIOS Governed Continuation",
"GIT_COMMITTER_EMAIL": "aios-governed-continuation@localhost",
}
)
_run(["git", "commit", "--no-gpg-sign", "-m", message, "--", *sorted(expected_paths)], cwd=self.path, environment=environment)
if not self.is_detached():
raise ContinuationExecutionError("WORKTREE_NOT_DETACHED")
return self.head_oid()
@dataclass(slots=True)
class ServerSideCredentialProjection:
ssh_key_path: Path
api_token_path: Path
active: bool = False
def project(self) -> None:
for path in (self.ssh_key_path, self.api_token_path):
if path.is_symlink() or not path.is_file() or path.stat().st_size < 32:
raise ContinuationExecutionError("CREDENTIAL_PROJECTION_MISSING")
self.active = True
def revoke(self) -> None:
self.active = False
@dataclass(slots=True)
class GiteaContinuationRemote:
workspace: Path
credential: ServerSideCredentialProjection
remote_name: str = "gitea"
repository: str = "aios/aios"
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
expected_host: str = "g.cnnetmkt.top"
@staticmethod
def validate_remote_url(value: str, expected_host: str = "g.cnnetmkt.top") -> None:
if value.startswith(("/", "file:")) or value.endswith("/data/aios-gitops/repo.git"):
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
if "://" in value:
parsed = urlsplit(value)
host, path = parsed.hostname, parsed.path
else:
location = value.split("@", 1)[-1]
host, separator, path = location.partition(":")
if not separator:
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
path = "/" + path
if host != expected_host or path.rstrip("/") not in {"/aios/aios.git", "/git/aios/aios.git"}:
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
def repository_identity(self) -> str:
url = _run(["git", "remote", "get-url", self.remote_name], cwd=self.workspace)
self.validate_remote_url(url, self.expected_host)
if self.repository != "aios/aios":
raise ContinuationExecutionError("REMOTE_REPOSITORY_IDENTITY_MISMATCH")
return self.repository
def _require_projected(self) -> None:
if not self.credential.active:
raise ContinuationExecutionError("CREDENTIAL_PROJECTION_MISSING")
def read_git_ref(self, branch: str) -> str | None:
self._require_projected()
output = _run(["git", "ls-remote", self.remote_name, f"refs/heads/{branch}"], cwd=self.workspace)
if not output:
return None
oid, ref = output.split(maxsplit=1)
if ref != f"refs/heads/{branch}" or not OID_RE.fullmatch(oid):
raise ContinuationExecutionError("GITEA_GIT_READBACK_INVALID")
return oid
def push_exact(self, branch: str, expected: str | None, new: str) -> None:
self._require_projected()
self.repository_identity()
if self.read_git_ref(branch) != expected:
raise ContinuationExecutionError("GITEA_BRANCH_EXPECTATION_MISMATCH")
_run(["git", "push", self.remote_name, f"{new}:refs/heads/{branch}"], cwd=self.workspace)
def _api(self, path: str) -> object:
self._require_projected()
token = self.credential.api_token_path.read_text(encoding="utf-8").strip()
request = Request(
self.api_base.rstrip("/") + path,
headers={"Accept": "application/json", "Authorization": f"token {token}"},
)
try:
with urlopen(request, timeout=20) as response:
return json.loads(response.read().decode("utf-8"))
except HTTPError as exc:
if exc.code == 404:
return None
raise ContinuationExecutionError(f"GITEA_API_HTTP_{exc.code}") from exc
except (URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
raise ContinuationExecutionError("GITEA_API_UNAVAILABLE") from exc
def read_api_branch(self, branch: str) -> str | None:
owner, name = self.repository.split("/", 1)
page = 1
while page <= 100:
value = self._api(f"/repos/{quote(owner)}/{quote(name)}/branches?page={page}&limit=50")
if value is None:
return None
if not isinstance(value, list):
raise ContinuationExecutionError("GITEA_API_BRANCH_COLLECTION_INVALID")
for item in value:
if not isinstance(item, dict) or item.get("name") != branch:
continue
oid = item.get("commit", {}).get("id")
if not isinstance(oid, str) or not OID_RE.fullmatch(oid):
raise ContinuationExecutionError("GITEA_API_BRANCH_INVALID")
return oid
if len(value) < 50:
return None
page += 1
raise ContinuationExecutionError("GITEA_API_BRANCH_PAGINATION_LIMIT")
def commit_visible(self, oid: str) -> bool:
owner, name = self.repository.split("/", 1)
value = self._api(f"/repos/{quote(owner)}/{quote(name)}/git/commits/{oid}")
if not isinstance(value, dict):
return False
observed = value.get("sha") or value.get("id")
return observed == oid
@dataclass(frozen=True, slots=True)
class ContinuationPermissions:
create_target_branch: bool = False
force_push: bool = False
tag_write: bool = False
baseline_write: bool = False
production_write: bool = False
main_write: bool = False
def validate(self) -> None:
if any(asdict(self).values()):
raise ContinuationExecutionError("MUTATION_PERMISSION_DENIED")
@dataclass(frozen=True, slots=True)
class ContinuationRuntimeBinding:
request: WriterLeaseRequestV2
workspace_path: Path
lease_not_before: datetime
lease_expires_at: datetime
original_receipt_valid: bool
original_grant_consumed: bool
permissions: ContinuationPermissions
@dataclass(frozen=True, slots=True)
class ContinuationCommit:
idempotency_key: str
files: Mapping[str, bytes]
message: str
def payload(self) -> bytes:
if not self.idempotency_key or not self.message.strip() or not self.files:
raise ContinuationExecutionError("COMMIT_REQUEST_INVALID")
normalized: dict[str, str] = {}
for path, content in self.files.items():
pure = PurePosixPath(path)
if (
not isinstance(path, str)
or pure.is_absolute()
or ".." in pure.parts
or path.startswith((".git/", "baseline/", "production/"))
or not isinstance(content, bytes)
):
raise ContinuationExecutionError("COMMIT_PATH_REJECTED")
if any(pattern.search(content) for pattern in SECRET_PATTERNS):
raise ContinuationExecutionError("SECRET_LIKE_PAYLOAD_REJECTED")
normalized[path] = base64.b64encode(content).decode("ascii")
return json.dumps(
{"files_base64": normalized, "message": self.message},
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
class ContinuationExecutor:
def __init__(
self,
controller: GitOpsJobController,
authority: AuthorityPort,
workspace: DetachedWorkspace,
remote: RemotePort,
credential: CredentialProjection,
*,
now: Callable[[], datetime] = lambda: datetime.now(timezone.utc),
) -> None:
self.controller = controller
self.authority = authority
self.workspace = workspace
self.remote = remote
self.credential = credential
self.now = now
self._observed_generations: dict[str, int] = {}
def observe_generation(self, branch: str, generation: int) -> None:
self._observed_generations[branch] = max(generation, self._observed_generations.get(branch, 0))
def _max_generation(self, branch: str) -> int:
observed = self._observed_generations.get(branch, 0)
with self.controller.store._connect() as connection:
row = connection.execute(
"SELECT MAX(generation) AS generation FROM branch_leases WHERE branch=?",
(branch,),
).fetchone()
durable = int(row["generation"] or 0)
return max(observed, durable)
@staticmethod
def _request_mapping(request: WriterLeaseRequestV2) -> dict[str, object]:
return {
"schema": WriterLeaseRequestV2.schema,
"task_id": request.task_id,
"repository": request.repository,
"branch_mode": request.branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": request.expected_branch_oid,
"target_branch": request.target_branch,
"generation": request.generation,
"lease_seconds": request.lease_seconds,
}
def _validate_preflight(self, binding: ContinuationRuntimeBinding) -> None:
request = binding.request
normalized = validate_writer_lease_request_v2(self._request_mapping(request), self.authority)
if normalized != {**self._request_mapping(request), "create_target_branch": False, "worktree_mode": "detached_at_expected_head", "cas_ref_update_required": True}:
raise ContinuationExecutionError("CONTINUATION_NORMALIZATION_INVALID")
if not binding.original_receipt_valid or not binding.original_grant_consumed:
raise ContinuationExecutionError("ORIGINAL_GRANT_LINEAGE_INVALID")
binding.permissions.validate()
now = self.now()
if now < binding.lease_not_before:
raise ContinuationExecutionError("LEASE_NOT_ACTIVE")
if now >= binding.lease_expires_at:
raise ContinuationExecutionError("LEASE_EXPIRED")
if request.generation <= self._max_generation(request.target_branch):
raise ContinuationExecutionError("GENERATION_NOT_MONOTONIC")
if not self.workspace.is_detached():
raise ContinuationExecutionError("WORKTREE_NOT_DETACHED")
if self.workspace.head_oid() != request.expected_branch_oid:
raise ContinuationExecutionError("WORKTREE_HEAD_MISMATCH")
if self.remote.repository_identity() != request.repository:
raise ContinuationExecutionError("REMOTE_REPOSITORY_IDENTITY_MISMATCH")
def _job_request(self, binding: ContinuationRuntimeBinding, commit: ContinuationCommit, payload: bytes) -> dict[str, object]:
request = binding.request
return {
"idempotency_key": commit.idempotency_key,
"campaign_id": request.task_id,
"task_id": request.task_id,
"repository": request.repository,
"operation": "CONTINUATION_COMMIT_FILES",
"branch": request.target_branch,
"expected_branch_oid": request.expected_branch_oid,
"base_oid": request.base_oid,
"branch_mode": request.branch_mode,
"payload_digest": hashlib.sha256(payload).hexdigest(),
"lineage_anchor_oid": request.base_oid,
"generation": request.generation,
}
def execute(self, binding: ContinuationRuntimeBinding, commit: ContinuationCommit) -> dict[str, object]:
payload = commit.payload()
submitted = self.controller.submit(self._job_request(binding, commit, payload), payload=payload)
current = self.controller.store.read(submitted.job_id)
if JobState(current["state"]) == JobState.COMPLETED:
return self.controller.store._receipt(current, duplicate=True).as_dict()
self._validate_preflight(binding)
request = binding.request
projected = False
try:
self.credential.project()
projected = True
row = self.controller.store.read(submitted.job_id)
if JobState(row["state"]) == JobState.REQUESTED:
self.controller.advance(
submitted.job_id,
JobState.AUTHORIZED,
side_effect_state="AUTHORIZED",
receipt={"task_id": request.task_id, "original_grant_consumed": True},
)
row = self.controller.store.read(submitted.job_id)
if JobState(row["state"]) == JobState.AUTHORIZED:
self.controller.lease_job(
submitted.job_id,
lease_id=f"lease-{submitted.job_id}",
lineage_anchor_oid=request.base_oid,
current_head_oid=request.expected_branch_oid,
generation=request.generation,
lease_expires_at=binding.lease_expires_at.astimezone(timezone.utc).isoformat(),
expected_revision=int(row["revision"]),
)
self.controller.advance(submitted.job_id, JobState.WORKTREE_READY, side_effect_state="WORKTREE_READY")
self.controller.advance(submitted.job_id, JobState.EXECUTING, side_effect_state="IN_PROGRESS")
new_oid = self.workspace.commit_files(commit.files, commit.message)
if not OID_RE.fullmatch(new_oid) or not self.authority.is_ancestor(request.expected_branch_oid, new_oid):
raise ContinuationExecutionError("NON_FAST_FORWARD_COMMIT")
authority_oid = self.authority.compare_and_swap_branch(request.target_branch, request.expected_branch_oid, new_oid)
if authority_oid != new_oid:
raise ContinuationExecutionError("AUTHORITY_CAS_READBACK_MISMATCH")
self.controller.advance(
submitted.job_id,
JobState.LOCAL_VERIFIED,
side_effect_state="LOCAL_VERIFIED",
receipt={"authority_oid": authority_oid, "force_push": False},
)
try:
self.controller.advance(submitted.job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS")
self.remote.push_exact(request.target_branch, request.expected_branch_oid, new_oid)
except Exception:
return self._remote_pending(submitted.job_id, authority_oid, new_oid)
return self._finish_remote_verification(submitted.job_id, authority_oid, new_oid)
finally:
if projected:
self.credential.revoke()
def _observe_remote(self, branch: str, oid: str) -> tuple[str | None, str | None, bool]:
return self.remote.read_git_ref(branch), self.remote.read_api_branch(branch), self.remote.commit_visible(oid)
def _remote_pending(self, job_id: str, authority_oid: str, new_oid: str) -> dict[str, object]:
receipt = {
"authority_oid": authority_oid,
"desired_remote_oid": new_oid,
"remote_verified": False,
"force_push": False,
}
result = self.controller.advance(
job_id,
JobState.RECONCILIATION_REQUIRED,
side_effect_state="REMOTE_PUSH_PENDING",
receipt=receipt,
)
return result.as_dict()
def _finish_remote_verification(self, job_id: str, authority_oid: str, new_oid: str) -> dict[str, object]:
row = self.controller.store.read(job_id)
branch = row["branch"]
request = json.loads(row["request_json"])["binding"]
git_oid, api_oid, visible = self._observe_remote(branch, new_oid)
decision = decide_reconciliation(RemoteObservation("aios/aios", branch, new_oid, authority_oid, git_oid, api_oid, visible, datetime.now(timezone.utc).isoformat(), request.get("expected_branch_oid")))
if decision.action is not ReconciliationAction.MARK_REMOTE_VERIFIED:
return self._remote_pending(job_id, authority_oid, new_oid)
receipt = {
"authority_oid": authority_oid,
"git_remote_oid": git_oid,
"api_branch_oid": api_oid,
"commit_visible": True,
"remote_verified": True,
"reconciliation_decision": decision.action.value,
"force_push": False,
}
self.controller.advance(job_id, JobState.REMOTE_VERIFIED, side_effect_state="REMOTE_VERIFIED", receipt=receipt)
return self.controller.advance(job_id, JobState.COMPLETED, side_effect_state="REMOTE_VERIFIED", receipt=receipt).as_dict()
def reconcile(self, job_id: str) -> dict[str, object]:
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state == JobState.COMPLETED:
return self.controller.store._receipt(row, duplicate=True).as_dict()
if state != JobState.RECONCILIATION_REQUIRED:
raise ContinuationExecutionError("JOB_NOT_RECONCILABLE")
request = json.loads(row["request_json"])["binding"]
receipt = json.loads(row["receipt_json"])
new_oid = receipt["desired_remote_oid"]
authority_oid = self.authority.branch_head(row["branch"])
try:
generation = int(request.get("generation") or 0)
except (TypeError, ValueError) as exc:
raise ContinuationExecutionError("DURABLE_BINDING_GENERATION_INVALID") from exc
if generation > 0:
with self.controller.store._connect() as connection:
earlier = connection.execute(
"SELECT 1 FROM jobs WHERE branch=? AND state=? "
"AND CAST(json_extract(request_json, '$.binding.generation') AS INTEGER) < ? LIMIT 1",
(row["branch"], JobState.RECONCILIATION_REQUIRED.value, generation),
).fetchone()
if earlier is not None:
raise ContinuationExecutionError("EARLIER_RECONCILIATION_REQUIRED")
self.credential.project()
try:
git_oid, api_oid, visible = self._observe_remote(row["branch"], new_oid)
if authority_oid == git_oid == api_oid == new_oid and visible:
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST")
return self._finish_remote_verification(job_id, new_oid, new_oid)
expected_oid = request["expected_branch_oid"]
if (
self.authority.is_ancestor(new_oid, authority_oid)
and git_oid is not None
and git_oid == api_oid
and self.authority.is_ancestor(git_oid, new_oid)
):
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST_CHAIN")
self.remote.push_exact(row["branch"], git_oid, new_oid)
return self._finish_remote_verification(job_id, new_oid, new_oid)
if authority_oid == new_oid and git_oid == api_oid == expected_oid:
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST_RETRY")
self.remote.push_exact(row["branch"], expected_oid, new_oid)
return self._finish_remote_verification(job_id, new_oid, new_oid)
return self.controller.store._receipt(row, duplicate=True).as_dict()
finally:
self.credential.revoke()
@@ -0,0 +1,154 @@
"""Campaign-aware continuation leases for an existing Git branch.
This module is deliberately transport-neutral. It validates branch lineage
and hands ref mutation to an injected compare-and-swap broker; it does not run
Git, inspect credentials, or create a branch itself.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone
import re
from typing import Protocol
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TARGET_BRANCH = "agent/autonomous-release-campaign-v1-20260807"
LINEAGE_ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
CAMPAIGN_HEAD = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
class ContinuationError(RuntimeError):
"""Stable fail-closed error for lease or branch binding violations."""
class BranchProbe(Protocol):
def branch_exists(self, branch: str) -> bool: ...
def branch_head(self, branch: str) -> str: ...
def is_ancestor(self, older: str, newer: str) -> bool: ...
class RefCAS(Protocol):
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise ContinuationError(f"{field}_invalid")
return value
@dataclass(frozen=True, slots=True)
class WriterLeaseRequestV2:
task_id: str
repository: str
branch_mode: str
base_oid: str
expected_branch_oid: str
target_branch: str
generation: int
lease_seconds: int = 900
schema = "aios.task-writer-lease.v2"
accepted_schemas = frozenset({"aios.task-writer-lease.v2", "aios.task-writer-lease-request.v2"})
def validate(self, probe: BranchProbe) -> None:
if not self.task_id or self.repository != "aios/aios":
raise ContinuationError("request_identity_invalid")
if self.branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
raise ContinuationError("branch_mode_invalid")
_oid(self.base_oid, "base_oid")
_oid(self.expected_branch_oid, "expected_branch_oid")
if self.target_branch in {"main", "master"} or self.target_branch.startswith(("refs/", "tags/")):
raise ContinuationError("protected_branch_rejected")
if not isinstance(self.generation, int) or isinstance(self.generation, bool) or self.generation < 1:
raise ContinuationError("generation_invalid")
if not isinstance(self.lease_seconds, int) or not 1 <= self.lease_seconds <= 86_400:
raise ContinuationError("lease_duration_invalid")
if self.branch_mode == "CONTINUE_EXISTING":
if not probe.branch_exists(self.target_branch):
raise ContinuationError("EXISTING_BRANCH_MISSING")
actual = _oid(probe.branch_head(self.target_branch), "actual_branch_oid")
if actual != self.expected_branch_oid:
raise ContinuationError("EXISTING_BRANCH_OID_CONFLICT")
if not probe.is_ancestor(self.base_oid, actual):
raise ContinuationError("BASE_NOT_ANCESTOR_OF_BRANCH")
elif hasattr(probe, "commit_exists") and not probe.commit_exists(self.base_oid):
raise ContinuationError("BASE_COMMIT_NOT_FOUND")
elif self.branch_mode == "CREATE_NEW" and probe.branch_exists(self.target_branch):
raise ContinuationError("TARGET_BRANCH_ALREADY_EXISTS")
elif self.branch_mode == "CREATE_NEW" and self.base_oid != self.expected_branch_oid:
raise ContinuationError("CREATE_NEW_BASE_EXPECTATION_MISMATCH")
@dataclass(frozen=True, slots=True)
class CampaignWriterLease:
task_id: str
repository: str
branch: str
lineage_anchor_oid: str
current_head_oid: str
generation: int
lease_expires_at: datetime
append_only: bool = True
renewable: bool = True
multi_commit: bool = True
recoverable: bool = True
state: str = "ACTIVE"
branch_bound: bool = True
def __post_init__(self) -> None:
_oid(self.lineage_anchor_oid, "lineage_anchor_oid")
_oid(self.current_head_oid, "current_head_oid")
if not all((self.append_only, self.renewable, self.multi_commit, self.recoverable, self.branch_bound)):
raise ContinuationError("lease_capability_downgrade")
class ContinuationLeaseManager:
"""State machine for a multi-commit lease with CAS-only ref updates."""
def __init__(self, probe: BranchProbe, refs: RefCAS, *, now=datetime.now) -> None:
self.probe = probe
self.refs = refs
self.now = now
def open(self, request: WriterLeaseRequestV2) -> CampaignWriterLease:
request.validate(self.probe)
if request.branch_mode != "CONTINUE_EXISTING":
raise ContinuationError("CREATE_NEW_REQUIRES_SEPARATE_PROVISIONER")
return CampaignWriterLease(
task_id=request.task_id,
repository=request.repository,
branch=request.target_branch,
lineage_anchor_oid=request.base_oid,
current_head_oid=request.expected_branch_oid,
generation=request.generation,
lease_expires_at=self.now(timezone.utc) + timedelta(seconds=request.lease_seconds),
)
def renew(self, lease: CampaignWriterLease, seconds: int) -> CampaignWriterLease:
if lease.state != "ACTIVE" or not lease.renewable or seconds < 1:
raise ContinuationError("lease_not_renewable")
return replace(lease, lease_expires_at=self.now(timezone.utc) + timedelta(seconds=seconds))
def append_commit(self, lease: CampaignWriterLease, new_head_oid: str) -> CampaignWriterLease:
if lease.state != "ACTIVE":
raise ContinuationError("lease_not_active")
_oid(new_head_oid, "new_head_oid")
if not self.probe.is_ancestor(lease.current_head_oid, new_head_oid):
raise ContinuationError("NON_FAST_FORWARD_COMMIT")
try:
actual = self.refs.compare_and_swap_branch(
lease.branch, lease.current_head_oid, new_head_oid
)
except Exception as exc:
raise ContinuationError("BRANCH_LEASE_STALE") from exc
if actual != new_head_oid:
raise ContinuationError("BRANCH_LEASE_STALE")
return replace(lease, current_head_oid=new_head_oid)
def supersede(self, lease: CampaignWriterLease) -> CampaignWriterLease:
if lease.state not in {"ACTIVE", "RECOVERABLE_FAILURE", "RECONCILIATION_REQUIRED"}:
raise ContinuationError("lease_not_supersedable")
return replace(lease, generation=lease.generation + 1, state="SUPERSEDED")
@@ -0,0 +1,25 @@
"""Separate read, write, and release plane readiness evaluation."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Mapping
@dataclass(frozen=True, slots=True)
class RuntimeStatus:
read_plane: Mapping[str, bool]
write_plane: Mapping[str, bool]
release_plane: Mapping[str, bool]
@property
def release_control_plane_ready(self) -> bool:
return all(self.read_plane.values()) and all(self.write_plane.values()) and all(self.release_plane.values())
def as_dict(self) -> dict[str, object]:
return {
"READ_PLANE": dict(self.read_plane),
"WRITE_PLANE": dict(self.write_plane),
"RELEASE_PLANE": dict(self.release_plane),
"release_control_plane_ready": self.release_control_plane_ready,
}
@@ -0,0 +1,58 @@
"""Workspace-independent authority/network/Gitea ref sentinel."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import subprocess
from typing import Callable, Mapping
class SentinelError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class SentinelInputs:
authority_git_dir: Path
gitea_remote: str
gitea_api: str
ref: str
expected_oid: str
@dataclass(frozen=True, slots=True)
class SentinelResult:
authority_oid: str | None
network_oid: str | None
gitea_oid: str | None
equal: bool
error: str | None = None
def authority_oid(inputs: SentinelInputs) -> str:
result = subprocess.run(
["git", "--git-dir", str(inputs.authority_git_dir), "rev-parse", inputs.ref],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise SentinelError("AUTHORITY_REF_UNAVAILABLE")
return result.stdout.strip()
def evaluate(
inputs: SentinelInputs,
*,
network_reader: Callable[[str, str], str | None],
gitea_reader: Callable[[str, str], str | None],
) -> SentinelResult:
try:
local = authority_oid(inputs)
network = network_reader(inputs.gitea_remote, inputs.ref)
gitea = gitea_reader(inputs.gitea_api, inputs.ref)
equal = local == network == gitea == inputs.expected_oid
return SentinelResult(local, network, gitea, equal, None if equal else "OID_MISMATCH")
except SentinelError as exc:
return SentinelResult(None, None, None, False, str(exc))
@@ -0,0 +1,26 @@
"""Non-sensitive control-plane snapshot assembly."""
from __future__ import annotations
from typing import Any, Mapping
def _copy(value: Mapping[str, Any] | None, keys: tuple[str, ...]) -> dict[str, Any]:
value = value or {}
return {key: value.get(key) for key in keys}
def build_control_plane_snapshot(*, target_version: str, runtime: Mapping[str, Any] | None = None, repository: Mapping[str, Any] | None = None, production: Mapping[str, Any] | None = None, task: Mapping[str, Any] | None = None, capabilities: Mapping[str, Any] | None = None, credential_broker: Mapping[str, Any] | None = None) -> dict[str, Any]:
return {
"version": {"target_release": target_version},
"runtime": _copy(runtime, ("bridge_schema_version", "control_plane_release_version", "runtime_release_commit", "runtime_path", "artifact_sha256", "drift_status")),
"repository": _copy(repository, ("identity", "authority_git_dir", "authority_main_oid")),
"production": _copy(production, ("baseline_version", "target_oid", "deployment_state")),
"task": _copy(task, ("task_id", "branch", "authority_branch_oid", "gitea_git_oid", "gitea_api_oid", "commit_visible", "generation", "reconciliation_state")),
"capabilities": _copy(capabilities, ("legacy_writes_enabled", "governed_writes_enabled", "force_push", "main_write", "tag_write", "baseline_write", "production_write")),
"credential_broker": _copy(credential_broker, ("status", "provider")),
"secret_material_exposed": False,
}
def getControlPlaneSnapshot(**kwargs: Any) -> dict[str, Any]:
return build_control_plane_snapshot(**kwargs)
@@ -0,0 +1,78 @@
"""Metadata-only, server-side credential lease broker."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import os
from pathlib import Path
import secrets
from typing import Any, Callable, Mapping
@dataclass(frozen=True, slots=True)
class CredentialLease:
lease_id: str
task_id: str
repository: str
target_branch: str
generation: int
issued_at: str
expires_at: str
scopes: tuple[str, ...]
provider: str
state: str = "ACTIVE"
def as_dict(self) -> dict[str, Any]:
return {"lease_id": self.lease_id, "task_id": self.task_id, "repository": self.repository, "target_branch": self.target_branch, "generation": self.generation, "issued_at": self.issued_at, "expires_at": self.expires_at, "scopes": list(self.scopes), "provider": self.provider, "state": self.state}
class FileCredentialProvider:
def __init__(self, token_path: str | os.PathLike[str], *, actor: str = "aios-autonomy-agent") -> None:
self.token_path = Path(token_path)
self.actor = actor
def environment(self, lease: CredentialLease) -> dict[str, str]:
if not self.token_path.is_file():
raise RuntimeError("CREDENTIAL_PROVIDER_UNAVAILABLE")
return {"CREDENTIAL_FILE": str(self.token_path), "GITEA_MACHINE_ACTOR": self.actor}
class CredentialBroker:
def __init__(self, provider: Any, *, now: Callable[[], datetime] | None = None) -> None:
self.provider = provider
self.now = now or (lambda: datetime.now(timezone.utc))
self._leases: dict[str, CredentialLease] = {}
def issue(self, *, task_id: str, repository: str, target_branch: str, generation: int, scopes: tuple[str, ...] = ("git:push", "gitea:read"), lease_seconds: int = 900) -> dict[str, Any]:
if repository != "aios/aios" or not target_branch.startswith("agent/") or generation < 1 or not 1 <= lease_seconds <= 86400:
raise ValueError("CREDENTIAL_LEASE_BINDING_INVALID")
now = self.now().astimezone(timezone.utc)
lease = CredentialLease("lease-" + secrets.token_hex(16), task_id, repository, target_branch, generation, now.isoformat(), (now + timedelta(seconds=lease_seconds)).isoformat(), tuple(scopes), type(self.provider).__name__)
self._leases[lease.lease_id] = lease
return lease.as_dict()
def _get(self, lease_id: str) -> CredentialLease:
lease = self._leases.get(lease_id)
if lease is None:
raise KeyError("CREDENTIAL_LEASE_NOT_FOUND")
if lease.state == "ACTIVE" and self.now().astimezone(timezone.utc) >= datetime.fromisoformat(lease.expires_at):
lease = CredentialLease(**{**lease.as_dict(), "scopes": tuple(lease.scopes), "state": "EXPIRED"})
self._leases[lease_id] = lease
return lease
def environment(self, lease_id: str) -> Mapping[str, str]:
lease = self._get(lease_id)
if lease.state != "ACTIVE":
raise RuntimeError("CREDENTIAL_LEASE_NOT_ACTIVE")
return self.provider.environment(lease)
def status(self, lease_id: str) -> dict[str, Any]:
return self._get(lease_id).as_dict()
def revoke(self, lease_id: str) -> dict[str, Any]:
lease = self._get(lease_id)
if lease.state == "REVOKED":
return lease.as_dict()
updated = CredentialLease(**{**lease.as_dict(), "scopes": tuple(lease.scopes), "state": "REVOKED"})
self._leases[lease_id] = updated
return updated.as_dict()
@@ -0,0 +1,252 @@
"""Fail-closed governed publication of an existing Authority branch at an exact OID."""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import asdict, dataclass
from enum import Enum
import hashlib
import json
import os
import re
import tempfile
import time
from pathlib import Path
from typing import Any, Protocol
OID_RE = re.compile(r"^[0-9a-f]{40}$")
BRANCH_RE = re.compile(r"^(?!refs/)(?!tags/)[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
IDEMPOTENCY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$")
class ExistingBranchPublicationError(RuntimeError):
"""Stable, non-secret error for the governed existing-branch publisher."""
class ExistingBranchPublicationConflict(ExistingBranchPublicationError):
pass
class ExistingBranchPublicationState(str, Enum):
QUEUED = "QUEUED"
PUBLISHING = "PUBLISHING"
REMOTE_PUSH_PENDING = "REMOTE_PUSH_PENDING"
RECONCILIATION_REQUIRED = "RECONCILIATION_REQUIRED"
MANUAL_OWNER_REVIEW_REQUIRED = "MANUAL_OWNER_REVIEW_REQUIRED"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
@dataclass(frozen=True, slots=True)
class ExistingBranchPublicationRequest:
repository: str
branch: str
expected_branch_oid: str
owner_approval_reference: str
idempotency_key: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "ExistingBranchPublicationRequest":
required = ("repository", "branch", "expected_branch_oid", "owner_approval_reference", "idempotency_key")
if not isinstance(value, Mapping):
raise ExistingBranchPublicationConflict("REQUEST_OBJECT_REQUIRED")
if any(not isinstance(value.get(field), str) or not value[field].strip() for field in required):
raise ExistingBranchPublicationConflict("REQUEST_FIELDS_MISSING")
repository = value["repository"].strip()
branch = value["branch"].strip()
expected = value["expected_branch_oid"].strip().lower()
approval = value["owner_approval_reference"].strip()
idempotency = value["idempotency_key"].strip()
if repository != "aios/aios":
raise ExistingBranchPublicationConflict("REPOSITORY_IDENTITY_MISMATCH")
if not BRANCH_RE.fullmatch(branch) or branch in {"main", "master"}:
raise ExistingBranchPublicationConflict("BRANCH_NAMESPACE_REJECTED")
if not OID_RE.fullmatch(expected):
raise ExistingBranchPublicationConflict("EXPECTED_BRANCH_OID_INVALID")
if len(approval) > 512:
raise ExistingBranchPublicationConflict("OWNER_APPROVAL_REFERENCE_INVALID")
if not IDEMPOTENCY_RE.fullmatch(idempotency):
raise ExistingBranchPublicationConflict("IDEMPOTENCY_KEY_INVALID")
return cls(repository, branch, expected, approval, idempotency)
@property
def publication_job_id(self) -> str:
return "publication-" + hashlib.sha256(self.idempotency_key.encode()).hexdigest()[:32]
@property
def digest(self) -> str:
raw = json.dumps(asdict(self), sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(raw).hexdigest()
class ExistingBranchAuthority(Protocol):
def read_ref(self, ref: str) -> str | None: ...
class ExistingBranchRemote(Protocol):
def repository_identity(self) -> str: ...
def read_git_ref(self, branch: str) -> str | None: ...
def read_api_branch(self, branch: str) -> str | None: ...
def commit_visible(self, oid: str) -> bool: ...
def push_exact(self, branch: str, expected: str | None, new: str) -> None: ...
class ExistingBranchPublicationStore:
def __init__(self, root: Path):
self.root = Path(root)
self.root.mkdir(parents=True, exist_ok=True)
def _path(self, request: ExistingBranchPublicationRequest) -> Path:
return self.root / f"{request.publication_job_id}.json"
def load(self, request: ExistingBranchPublicationRequest) -> dict[str, Any]:
try:
value = json.loads(self._path(request).read_text(encoding="utf-8"))
except FileNotFoundError:
return {}
if not isinstance(value, dict):
raise ExistingBranchPublicationConflict("PUBLICATION_STATE_INVALID")
return value
def record(self, request: ExistingBranchPublicationRequest, state: ExistingBranchPublicationState, **evidence: Any) -> dict[str, Any]:
payload = {
"schema": "aios.existing-branch-publication.v1",
"publication_job_id": request.publication_job_id,
"repository": request.repository,
"branch": request.branch,
"expected_branch_oid": request.expected_branch_oid,
"owner_approval_reference": request.owner_approval_reference,
"idempotency_key": request.idempotency_key,
"request_digest": request.digest,
"publication_state": state.value,
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
**evidence,
}
path = self._path(request)
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(payload, handle, ensure_ascii=False, sort_keys=True)
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, path)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
return payload
class ExistingBranchPublisher:
"""Publish only the exact, already-authorized Authority branch tip."""
def __init__(self, authority: ExistingBranchAuthority, remote: ExistingBranchRemote, state_root: Path):
self.authority = authority
self.remote = remote
self.store = ExistingBranchPublicationStore(state_root)
@staticmethod
def _safe_error(exc: BaseException) -> str:
return type(exc).__name__
def _observe(self, request: ExistingBranchPublicationRequest) -> dict[str, Any]:
return {
"authority_branch_oid": self.authority.read_ref(f"refs/heads/{request.branch}"),
"gitea_git_branch_oid": self.remote.read_git_ref(request.branch),
"gitea_api_branch_oid": self.remote.read_api_branch(request.branch),
"commit_visible": bool(self.remote.commit_visible(request.expected_branch_oid)),
}
@staticmethod
def _has_remote_conflict(request: ExistingBranchPublicationRequest, observation: Mapping[str, Any]) -> bool:
return any(
observation.get(field) not in (None, request.expected_branch_oid)
for field in ("gitea_git_branch_oid", "gitea_api_branch_oid")
)
def _manual(self, request: ExistingBranchPublicationRequest, reason: str, observation: Mapping[str, Any]) -> dict[str, Any]:
return self.store.record(
request,
ExistingBranchPublicationState.MANUAL_OWNER_REVIEW_REQUIRED,
error_code=reason,
**dict(observation),
)
def _finish(self, request: ExistingBranchPublicationRequest) -> dict[str, Any]:
try:
observation = self._observe(request)
except Exception as exc:
return self.store.record(
request,
ExistingBranchPublicationState.RECONCILIATION_REQUIRED,
error_code=self._safe_error(exc),
)
if observation.get("authority_branch_oid") != request.expected_branch_oid:
return self._manual(request, "AUTHORITY_BRANCH_OID_CHANGED", observation)
if self._has_remote_conflict(request, observation):
return self._manual(request, "REMOTE_BRANCH_OID_CONFLICT", observation)
if (
observation.get("gitea_git_branch_oid") == request.expected_branch_oid
and observation.get("gitea_api_branch_oid") == request.expected_branch_oid
and observation.get("commit_visible") is True
):
return self.store.record(request, ExistingBranchPublicationState.COMPLETED, **dict(observation))
return self.store.record(
request,
ExistingBranchPublicationState.RECONCILIATION_REQUIRED,
error_code="REMOTE_READBACK_INCOMPLETE",
**dict(observation),
)
def publish(self, value: Mapping[str, Any]) -> dict[str, Any]:
request = ExistingBranchPublicationRequest.from_mapping(value)
previous = self.store.load(request)
if previous:
if previous.get("request_digest") != request.digest:
raise ExistingBranchPublicationConflict("IDEMPOTENCY_KEY_REUSED")
if previous.get("publication_state") == ExistingBranchPublicationState.COMPLETED.value:
return {**previous, "idempotent": True}
authority_oid = self.authority.read_ref(f"refs/heads/{request.branch}")
if authority_oid is None:
raise ExistingBranchPublicationConflict("AUTHORITY_BRANCH_MISSING")
if authority_oid != request.expected_branch_oid:
raise ExistingBranchPublicationConflict("AUTHORITY_BRANCH_OID_MISMATCH")
if self.remote.repository_identity() != request.repository:
raise ExistingBranchPublicationConflict("REMOTE_REPOSITORY_IDENTITY_MISMATCH")
self.store.record(request, ExistingBranchPublicationState.QUEUED)
try:
observation = self._observe(request)
except Exception as exc:
return self.store.record(
request,
ExistingBranchPublicationState.RECONCILIATION_REQUIRED,
error_code=self._safe_error(exc),
)
if observation.get("authority_branch_oid") != request.expected_branch_oid:
return self._manual(request, "AUTHORITY_BRANCH_OID_CHANGED", observation)
if self._has_remote_conflict(request, observation):
return self._manual(request, "REMOTE_BRANCH_OID_CONFLICT", observation)
if (
observation.get("gitea_git_branch_oid") == request.expected_branch_oid
and observation.get("gitea_api_branch_oid") == request.expected_branch_oid
and observation.get("commit_visible") is True
):
return self.store.record(request, ExistingBranchPublicationState.COMPLETED, **dict(observation))
if observation.get("gitea_git_branch_oid") is not None or observation.get("gitea_api_branch_oid") is not None:
return self.store.record(
request,
ExistingBranchPublicationState.RECONCILIATION_REQUIRED,
error_code="REMOTE_READBACK_INCOMPLETE",
**dict(observation),
)
self.store.record(request, ExistingBranchPublicationState.PUBLISHING, **dict(observation))
try:
self.remote.push_exact(request.branch, None, request.expected_branch_oid)
except Exception as exc:
return self.store.record(
request,
ExistingBranchPublicationState.REMOTE_PUSH_PENDING,
error_code=self._safe_error(exc),
desired_remote_oid=request.expected_branch_oid,
**dict(observation),
)
return self._finish(request)
@@ -0,0 +1,125 @@
"""Non-Owner Gitea machine capability and branch reconciliation boundary."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, Protocol
class MachineBrokerError(RuntimeError):
pass
class GiteaTransport(Protocol):
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str: ...
def create_or_update_pr(self, branch: str, target: str, title: str, body: str) -> Mapping[str, Any]: ...
def publish_status(self, branch: str, state: str, description: str) -> Mapping[str, Any]: ...
def protected_merge(self, pr_number: int) -> Mapping[str, Any]: ...
def read_branch(self, branch: str) -> str | None: ...
def whoami(self) -> Mapping[str, Any]: ...
def repository(self, owner: str, name: str) -> Mapping[str, Any]: ...
@dataclass(frozen=True, slots=True)
class MachineCredentialProjection:
"""Opaque systemd credential reference; the credential value is never accepted."""
reference: str
account: str
owner_account: str = "aiman"
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "MachineCredentialProjection":
if set(value) != {"scheme", "name", "account"}:
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
if value["scheme"] != "systemd-credential" or value["name"] != "gitea-machine-token":
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
return cls(f"{value['scheme']}:{value['name']}", str(value["account"]))
def __post_init__(self) -> None:
if self.reference != "systemd-credential:gitea-machine-token":
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
if not self.account or self.account == self.owner_account:
raise MachineBrokerError("OWNER_MACHINE_IDENTITY_REJECTED")
@dataclass(frozen=True, slots=True)
class CampaignCapabilityReceipt:
campaign_id: str
task_id: str
repository: str
branch: str
expected_oid: str
capabilities: frozenset[str]
denied: frozenset[str]
def require(self, capability: str) -> None:
if capability in self.denied or capability not in self.capabilities:
raise MachineBrokerError("CAPABILITY_DENIED")
if self.branch in {"main", "master"}:
raise MachineBrokerError("PROTECTED_BRANCH_DENIED")
class GiteaMachineBroker:
def __init__(self, transport: GiteaTransport, credential: MachineCredentialProjection, receipt: CampaignCapabilityReceipt) -> None:
self.transport = transport
self.credential = credential
self.receipt = receipt
def verify_identity(self) -> Mapping[str, Any]:
identity = dict(self.transport.whoami())
login = identity.get("login") or identity.get("username")
if not isinstance(login, str) or not login or login == self.credential.owner_account or login != self.credential.account:
raise MachineBrokerError("GITEA_MACHINE_IDENTITY_INVALID")
owner, name = self.receipt.repository.split("/", 1)
repo = dict(self.transport.repository(owner, name))
if repo.get("full_name") != self.receipt.repository:
raise MachineBrokerError("GITEA_REPOSITORY_MISMATCH")
return {"login": login, "repository": self.receipt.repository, "owner_account": self.credential.owner_account}
def push_branch(self, new_oid: str) -> str:
self.receipt.require("exact_task_branch_push")
return self.transport.compare_and_swap_branch(self.receipt.branch, self.receipt.expected_oid, new_oid)
def create_pr(self, title: str, body: str) -> Mapping[str, Any]:
self.receipt.require("pr_create_update")
return self.transport.create_or_update_pr(self.receipt.branch, "main", title, body)
def status(self, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
self.receipt.require("status_publish")
return self.transport.publish_status(self.receipt.branch, state, description, context)
def merge(self, pr_number: int) -> Mapping[str, Any]:
self.receipt.require("protected_merge")
return self.transport.protected_merge(pr_number)
def readback(self) -> str | None:
self.receipt.require("remote_readback")
return self.transport.read_branch(self.receipt.branch)
def reconcile_exact_branch(authority_oid: str, gitea_oid: str | None, expected_oid: str) -> str:
if authority_oid != expected_oid:
return "AUTHORITY_EXPECTATION_MISMATCH"
if gitea_oid is None:
return "CREATE_REQUIRED"
if gitea_oid == expected_oid:
return "ALREADY_IN_SYNC"
return "MANUAL_OWNER_REVIEW_REQUIRED"
def reconcile_fast_forward(
remote_oid: str | None,
expected_old_oid: str,
new_oid: str,
*,
old_is_ancestor_of_new: bool,
) -> str:
"""Return a bounded action for an active Job/lease branch scope."""
if remote_oid is None:
return "CREATE_REQUIRED"
if remote_oid == new_oid:
return "ALREADY_IN_SYNC"
if remote_oid == expected_old_oid and old_is_ancestor_of_new:
return "FAST_FORWARD_REQUIRED"
return "MANUAL_OWNER_REVIEW_REQUIRED"
@@ -0,0 +1,208 @@
"""Gitea HTTP/Git transport using only a projected systemd credential."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
import tempfile
from typing import Any, Mapping
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
class GiteaTransportError(RuntimeError):
pass
def _safe_oid(value: str, field: str) -> str:
if not isinstance(value, str) or len(value) != 40 or any(ch not in "0123456789abcdef" for ch in value):
raise GiteaTransportError(f"{field}_INVALID")
return value
@dataclass(slots=True)
class GiteaMachineTransport:
api_base: str
repository_id: str
remote_url: str
authority_git_dir: Path
credential_name: str = "gitea-machine-token"
actor: str = "aios-autonomy-agent"
timeout: int = 20
expected_merge_head: str | None = None
expected_merge_base: str | None = None
def __post_init__(self) -> None:
if self.credential_name != "gitea-machine-token":
raise GiteaTransportError("CREDENTIAL_NAME_INVALID")
if "/" not in self.repository_id or self.repository_id.startswith("/"):
raise GiteaTransportError("REPOSITORY_INVALID")
if not self.api_base.startswith("https://") and not self.api_base.startswith("http://127.0.0.1"):
raise GiteaTransportError("GITEA_API_SCOPE_INVALID")
if not self.remote_url.startswith("https://"):
raise GiteaTransportError("GITEA_REMOTE_SCOPE_INVALID")
@property
def _credential_path(self) -> Path:
directory = os.environ.get("CREDENTIALS_DIRECTORY", "")
if not directory:
raise GiteaTransportError("CREDENTIAL_PROJECTION_MISSING")
path = Path(directory) / self.credential_name
if path.is_symlink() or not path.is_file():
raise GiteaTransportError("CREDENTIAL_PROJECTION_MISSING")
return path
def _token(self) -> str:
value = self._credential_path.read_text(encoding="utf-8").strip()
if not value:
raise GiteaTransportError("CREDENTIAL_EMPTY")
return value
def _request(self, method: str, path: str, body: Mapping[str, Any] | None = None) -> tuple[int, Any]:
data = None if body is None else json.dumps(dict(body), separators=(",", ":")).encode()
headers = {"Accept": "application/json", "Authorization": f"token {self._token()}"}
if data is not None:
headers["Content-Type"] = "application/json"
request = Request(self.api_base.rstrip("/") + path, data=data, headers=headers, method=method)
try:
with urlopen(request, timeout=self.timeout) as response:
raw = response.read()
return response.status, json.loads(raw.decode()) if raw else {}
except HTTPError as exc:
# Never include response bodies: Gitea may echo sensitive request context.
raise GiteaTransportError(f"GITEA_HTTP_{exc.code}") from exc
except (URLError, TimeoutError, OSError) as exc:
raise GiteaTransportError("GITEA_NETWORK_ERROR") from exc
def whoami(self) -> Mapping[str, Any]:
return self._request("GET", "/user")[1]
def repository_info(self) -> Mapping[str, Any]:
owner, name = self.repository_id.split("/", 1)
return self._request("GET", f"/repos/{quote(owner)}/{quote(name)}")[1]
def repository(self, owner: str, name: str) -> Mapping[str, Any]:
if f"{owner}/{name}" != self.repository_id:
raise GiteaTransportError("REPOSITORY_SCOPE_DENIED")
return self.repository_info()
def read_branch(self, branch: str) -> str | None:
owner, name = self.repository_id.split("/", 1)
try:
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/branches/{quote(branch, safe='/')}")[1]
except GiteaTransportError as exc:
if str(exc) == "GITEA_HTTP_404":
return None
raise
sha = value.get("commit", {}).get("id") if isinstance(value, dict) else None
return _safe_oid(sha, "remote_branch_oid")
def _askpass(self) -> tuple[Path, dict[str, str]]:
credential = self._credential_path
fd, name = tempfile.mkstemp(prefix="aios-gitea-askpass-", dir="/tmp")
path = Path(name)
os.fchmod(fd, 0o700)
script = "#!/bin/sh\ncase \"$1\" in *Username*) printf '%s\\n' \"$GITEA_MACHINE_ACTOR\" ;; *) cat \"$CREDENTIAL_FILE\" ;; esac\n"
with os.fdopen(fd, "w", encoding="utf-8") as stream:
stream.write(script)
stream.flush()
os.fsync(stream.fileno())
return path, {
"GIT_ASKPASS": str(path),
"GIT_TERMINAL_PROMPT": "0",
"GITEA_MACHINE_ACTOR": self.actor,
"CREDENTIAL_FILE": str(credential),
}
def _push_branch_fast_forward(self, branch: str, new_oid: str) -> None:
askpass, additions = self._askpass()
environment = os.environ.copy()
environment.update(additions)
try:
result = subprocess.run(
["git", f"--git-dir={self.authority_git_dir}", "push", self.remote_url, f"{new_oid}:refs/heads/{branch}"],
text=True,
capture_output=True,
check=False,
env=environment,
timeout=self.timeout,
)
if result.returncode != 0:
raise GiteaTransportError("GITEA_BRANCH_CREATE_FAILED")
finally:
askpass.unlink(missing_ok=True)
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")
current = self.read_branch(branch)
if current == new_oid:
return new_oid
if current is None:
self._push_branch_fast_forward(branch, new_oid)
readback = self.read_branch(branch)
if readback != new_oid:
raise GiteaTransportError("GITEA_BRANCH_CREATE_READBACK_MISMATCH")
return readback
if current != expected_oid:
raise GiteaTransportError("GITEA_BRANCH_EXPECTATION_MISMATCH")
# The preflight read binds the expected old tip. The Git receive-pack
# then enforces the actual fast-forward CAS while transferring the new
# objects; no force/lease override or arbitrary ref is permitted.
self._push_branch_fast_forward(branch, new_oid)
readback = self.read_branch(branch)
if readback != new_oid:
raise GiteaTransportError("GITEA_BRANCH_CAS_READBACK_MISMATCH")
return readback
def _pulls(self) -> list[Mapping[str, Any]]:
owner, name = self.repository_id.split("/", 1)
query = urlencode({"state": "all", "base_branch": "main", "limit": 100})
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/pulls?{query}")[1]
return value if isinstance(value, list) else []
def create_or_update_pr(self, branch: str, target: str, title: str, body: str) -> Mapping[str, Any]:
if target != "main":
raise GiteaTransportError("PR_BASE_SCOPE_DENIED")
head = self.read_branch(branch)
if head is None:
raise GiteaTransportError("PR_HEAD_MISSING")
matches = [p for p in self._pulls() if (p.get("head") or {}).get("ref") == branch and (p.get("base") or {}).get("ref") == target]
open_matches = [p for p in matches if p.get("state") == "open"]
if len(open_matches) > 1:
raise GiteaTransportError("MULTIPLE_MATCHING_PRS")
if open_matches:
if (open_matches[0].get("head") or {}).get("sha") != head:
raise GiteaTransportError("PR_HEAD_CONFLICT")
return open_matches[0]
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/pulls", {"head": branch, "base": target, "title": title, "body": body, "allow_maintainer_edit": False})[1]
def publish_status(self, branch: str, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
oid = self.read_branch(branch)
if oid is None:
raise GiteaTransportError("STATUS_BRANCH_MISSING")
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/statuses/{oid}", {"state": state, "context": context, "description": description})[1]
def protected_merge(self, pr_number: int) -> Mapping[str, Any]:
if not isinstance(pr_number, int) or pr_number < 1:
raise GiteaTransportError("PR_NUMBER_INVALID")
owner, name = self.repository_id.split("/", 1)
pr = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/pulls/{pr_number}")[1]
if pr.get("state") != "open" or (pr.get("base") or {}).get("ref") != "main":
raise GiteaTransportError("PR_MERGE_BINDING_INVALID")
if pr.get("mergeable") is False:
raise GiteaTransportError("PR_NOT_MERGEABLE")
head_oid = (pr.get("head") or {}).get("sha")
base_oid = (pr.get("base") or {}).get("sha")
if self.expected_merge_head is not None and head_oid != self.expected_merge_head:
raise GiteaTransportError("PR_HEAD_EXPECTATION_MISMATCH")
if self.expected_merge_base is not None and base_oid != self.expected_merge_base:
raise GiteaTransportError("PR_BASE_EXPECTATION_MISMATCH")
body = {"do": "fast-forward-only", "force_merge": False, "merge_when_checks_succeed": False, "head_commit_id": head_oid}
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/pulls/{pr_number}/merge", body)[1]
@@ -0,0 +1,93 @@
"""Canonical Gitea transport and slash-branch verification."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
from typing import Any, Callable, Mapping, Optional
from urllib.parse import quote
from urllib.request import Request, urlopen
from aios_gitops_reconciliation_core import RemoteObservation
@dataclass(frozen=True, slots=True)
class GiteaPublisherConfig:
repository: str = "aios/aios"
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
remote_name: str = "gitea-http"
class GiteaPublisher:
def __init__(self, workspace: Path, *, config: GiteaPublisherConfig = GiteaPublisherConfig(), credential: Any = None, api_get: Callable[[str], Any] | None = None, git_ref: Callable[[str], Optional[str]] | None = None) -> None:
self.workspace = Path(workspace)
self.config = config
self.credential = credential
self._api_get = api_get
self._git_ref = git_ref
@staticmethod
def validate_repository(repository: str) -> None:
if repository != "aios/aios":
raise ValueError("REPOSITORY_IDENTITY_MISMATCH")
@staticmethod
def validate_branch(branch: str) -> None:
if not isinstance(branch, str) or not branch.startswith("agent/") or branch in {"main", "master"}:
raise ValueError("BRANCH_SCOPE_DENIED")
def repository_identity(self) -> str:
self.validate_repository(self.config.repository)
return self.config.repository
def _environment(self) -> dict[str, str]:
if self.credential is not None and hasattr(self.credential, "environment"):
return dict(self.credential.environment())
return {}
def read_git_ref(self, branch: str) -> Optional[str]:
self.validate_branch(branch)
if self._git_ref is not None:
return self._git_ref(branch)
result = subprocess.run(["git", "-C", str(self.workspace), "ls-remote", self.config.remote_name, f"refs/heads/{branch}"], text=True, capture_output=True, check=False, env={**os.environ, **self._environment(), "GIT_TERMINAL_PROMPT": "0"})
if result.returncode != 0 or not result.stdout.strip():
return None
return result.stdout.split()[0]
def _api(self, path: str) -> Any:
if self._api_get is not None:
return self._api_get(path)
headers = {"Accept": "application/json"}
env = self._environment()
if env.get("AIOS_GITEA_TOKEN"):
headers["Authorization"] = "token " + env["AIOS_GITEA_TOKEN"]
with urlopen(Request(self.config.api_base.rstrip("/") + path, headers=headers), timeout=20) as response:
return json.loads(response.read() or b"{}")
def read_api_branch(self, branch: str) -> Optional[str]:
self.validate_branch(branch)
for page in range(1, 101):
value = self._api(f"/repos/aios/aios/branches?page={page}&limit=50")
if not isinstance(value, list):
return None
for item in value:
if isinstance(item, Mapping) and item.get("name") == branch:
commit = item.get("commit")
return commit.get("id") if isinstance(commit, Mapping) else None
if len(value) < 50:
return None
raise RuntimeError("GITEA_BRANCH_PAGINATION_LIMIT")
def commit_visible(self, oid: str) -> bool:
if not isinstance(oid, str) or len(oid) != 40:
return False
try:
value = self._api(f"/repos/aios/aios/git/commits/{quote(oid, safe='')}")
return isinstance(value, Mapping) and value.get("sha", oid) == oid
except Exception:
return False
def observe(self, branch: str, desired_oid: str, authority_oid: Optional[str], *, expected_oid: Optional[str] = None, observed_at: str = "") -> RemoteObservation:
return RemoteObservation(self.repository_identity(), branch, desired_oid, authority_oid, self.read_git_ref(branch), self.read_api_branch(branch), self.commit_visible(desired_oid), observed_at, expected_oid)
@@ -0,0 +1,595 @@
"""SQLite-backed durable GitOps Job Controller.
The controller owns durable intent, idempotency, lease metadata, state
transitions, receipts, and payload references. A worker owns Git side effects
behind the campaign capability and branch-CAS boundaries.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import hashlib
import json
import os
from pathlib import Path
import sqlite3
import tempfile
from datetime import datetime, timezone
from typing import Any, Callable, Mapping
class JobError(RuntimeError):
pass
class ServiceUnavailable(JobError):
pass
class BindingConflict(JobError):
pass
class JobState(str, Enum):
REQUESTED = "REQUESTED"
AUTHORIZED = "AUTHORIZED"
LEASED = "LEASED"
WORKTREE_READY = "WORKTREE_READY"
EXECUTING = "EXECUTING"
LOCAL_VERIFIED = "LOCAL_VERIFIED"
REMOTE_SYNCING = "REMOTE_SYNCING"
REMOTE_VERIFIED = "REMOTE_VERIFIED"
COMPLETED = "COMPLETED"
RECOVERABLE_FAILURE = "RECOVERABLE_FAILURE"
RECONCILING = "RECONCILING"
RECONCILIATION_REQUIRED = "RECONCILIATION_REQUIRED"
MANUAL_OWNER_REVIEW_REQUIRED = "MANUAL_OWNER_REVIEW_REQUIRED"
_ORDER = {
JobState.REQUESTED: 0,
JobState.AUTHORIZED: 1,
JobState.LEASED: 2,
JobState.WORKTREE_READY: 3,
JobState.EXECUTING: 4,
JobState.LOCAL_VERIFIED: 5,
JobState.REMOTE_SYNCING: 6,
JobState.REMOTE_VERIFIED: 7,
JobState.COMPLETED: 8,
}
_RECOVERABLE = {JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED}
_TERMINAL = {JobState.COMPLETED, JobState.MANUAL_OWNER_REVIEW_REQUIRED}
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def canonical_json(value: Any) -> bytes:
return (
json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n"
).encode("utf-8")
def sha256_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value)).hexdigest()
class PayloadStore:
"""Content-addressed filesystem payloads; raw content never enters SQLite."""
def __init__(self, root: str | os.PathLike[str]) -> None:
self.root = Path(root)
if not self.root.is_absolute() or self.root.is_symlink():
raise JobError("PAYLOAD_ROOT_INVALID")
self.root.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chmod(self.root, 0o700)
def put(self, content: bytes, expected_digest: str) -> tuple[str, int]:
digest = hashlib.sha256(content).hexdigest()
if digest != expected_digest:
raise JobError("PAYLOAD_DIGEST_MISMATCH")
if len(expected_digest) != 64 or any(ch not in "0123456789abcdef" for ch in expected_digest):
raise JobError("PAYLOAD_DIGEST_INVALID")
target = self.root / expected_digest
if target.is_symlink():
raise JobError("PAYLOAD_PATH_SYMLINK")
if target.is_file():
if target.stat().st_size != len(content) or hashlib.sha256(target.read_bytes()).hexdigest() != digest:
raise JobError("PAYLOAD_EXISTING_MISMATCH")
return f"sha256/{digest}", len(content)
fd, temporary = tempfile.mkstemp(prefix=".payload-", dir=self.root)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "wb") as stream:
stream.write(content)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
os.chmod(target, 0o600)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
return f"sha256/{digest}", len(content)
def read_verified(self, reference: str, expected_digest: str, expected_size: int) -> bytes:
if reference != f"sha256/{expected_digest}":
raise JobError("PAYLOAD_REFERENCE_MISMATCH")
path = self.root / expected_digest
if path.is_symlink() or not path.is_file():
raise JobError("PAYLOAD_MISSING")
content = path.read_bytes()
if len(content) != expected_size or hashlib.sha256(content).hexdigest() != expected_digest:
raise JobError("PAYLOAD_VERIFY_FAILED")
return content
@dataclass(frozen=True, slots=True)
class JobReceipt:
job_id: str
idempotency_key: str
state: JobState
side_effect_state: str
http_status: int
revision: int
receipt: Mapping[str, Any] | None = None
def as_dict(self) -> dict[str, Any]:
return {
"job_id": self.job_id,
"idempotency_key": self.idempotency_key,
"state": self.state.value,
"side_effect_state": self.side_effect_state,
"http_status": self.http_status,
"revision": self.revision,
"receipt": dict(self.receipt) if self.receipt else None,
}
class JobStore:
SCHEMA_VERSION = 1
def __init__(self, root: str | os.PathLike[str], *, payload_root: str | os.PathLike[str] | None = None) -> None:
self.root = Path(root)
if not self.root.is_absolute() or self.root.is_symlink():
raise JobError("JOB_ROOT_INVALID")
self.root.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chmod(self.root, 0o700)
self.db_path = self.root / "state.sqlite3"
self.payloads = PayloadStore(payload_root or self.root.parent / "payloads" / "sha256")
self._initialize()
def _connect(self) -> sqlite3.Connection:
if self.db_path.is_symlink():
raise ServiceUnavailable("JOB_DB_SYMLINK")
connection = sqlite3.connect(self.db_path, timeout=30.0)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA journal_mode=WAL")
connection.execute("PRAGMA synchronous=FULL")
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
def _initialize(self) -> None:
try:
with self._connect() as connection:
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS jobs (
job_id TEXT PRIMARY KEY, idempotency_key TEXT NOT NULL UNIQUE,
campaign_id TEXT NOT NULL, repository TEXT NOT NULL, operation TEXT NOT NULL,
branch TEXT NOT NULL, expected_branch_oid TEXT NOT NULL,
payload_digest TEXT NOT NULL, payload_ref TEXT, payload_size INTEGER,
state TEXT NOT NULL, side_effect_state TEXT NOT NULL,
revision INTEGER NOT NULL, request_json TEXT NOT NULL,
receipt_json TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS idempotency_keys (
key TEXT PRIMARY KEY, job_id TEXT NOT NULL UNIQUE,
binding_json TEXT NOT NULL, created_at TEXT NOT NULL,
FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE TABLE IF NOT EXISTS branch_leases (
lease_id TEXT PRIMARY KEY, job_id TEXT NOT NULL UNIQUE,
branch TEXT NOT NULL, lineage_anchor_oid TEXT NOT NULL,
current_head_oid TEXT NOT NULL, generation INTEGER NOT NULL,
lease_expires_at TEXT NOT NULL, state TEXT NOT NULL,
revision INTEGER NOT NULL, FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE TABLE IF NOT EXISTS state_transitions (
sequence INTEGER PRIMARY KEY AUTOINCREMENT, job_id TEXT NOT NULL,
from_state TEXT, to_state TEXT NOT NULL, revision INTEGER NOT NULL,
side_effect_state TEXT NOT NULL, created_at TEXT NOT NULL,
FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE TABLE IF NOT EXISTS receipts (
receipt_id TEXT PRIMARY KEY, job_id TEXT NOT NULL,
kind TEXT NOT NULL, digest TEXT NOT NULL, payload_json TEXT NOT NULL,
created_at TEXT NOT NULL, FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
INSERT OR REPLACE INTO metadata(key, value) VALUES('schema_version', '1');
"""
)
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
def _mirror(self, job: Mapping[str, Any]) -> None:
directory = self.root / str(job["job_id"])
directory.mkdir(mode=0o700, exist_ok=True)
(directory / "receipts").mkdir(mode=0o700, exist_ok=True)
(directory / "logs-digest").mkdir(mode=0o700, exist_ok=True)
for name, value in (("request.json", job["request"]), ("state.json", job)):
target = directory / name
fd, temporary = tempfile.mkstemp(prefix=".mirror-", dir=directory)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "wb") as stream:
stream.write(canonical_json(value))
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def read(self, job_id: str) -> dict[str, Any]:
try:
with self._connect() as connection:
row = connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone()
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
if row is None:
raise JobError("JOB_NOT_FOUND")
return dict(row)
def _receipt(self, row: Mapping[str, Any], *, duplicate: bool) -> JobReceipt:
state = JobState(row["state"])
if state == JobState.COMPLETED:
status, side = 200, "COMPLETED"
elif state in _RECOVERABLE:
status = 202
side = row["side_effect_state"] if row["side_effect_state"] == "REMOTE_PUSH_PENDING" else "RESUME_SAME_JOB"
elif row["side_effect_state"] == "UNKNOWN":
status, side = 503, "UNKNOWN"
else:
status, side = 202, "SAME_JOB" if duplicate else "QUEUED"
receipt = json.loads(row["receipt_json"]) if row["receipt_json"] else None
return JobReceipt(row["job_id"], row["idempotency_key"], state, side, status, int(row["revision"]), receipt)
class GitOpsJobController:
def __init__(self, store: JobStore) -> None:
self.store = store
@staticmethod
def _binding(request: Mapping[str, Any]) -> dict[str, str]:
fields = ("campaign_id", "repository", "operation", "branch", "expected_branch_oid", "payload_digest")
if any(not isinstance(request.get(field), str) or not request[field].strip() for field in fields):
raise JobError("JOB_BINDING_INVALID")
oid = request["expected_branch_oid"]
digest = request["payload_digest"]
if len(oid) != 40 or any(ch not in "0123456789abcdef" for ch in oid):
raise JobError("EXPECTED_BRANCH_OID_INVALID")
if len(digest) != 64 or any(ch not in "0123456789abcdef" for ch in digest):
raise JobError("PAYLOAD_DIGEST_INVALID")
if request["branch"] in {"main", "master"} or request["branch"].startswith(("refs/", "tags/")):
raise JobError("PROTECTED_BRANCH_REJECTED")
binding = {field: str(request[field]) for field in fields}
# Operation-specific, non-secret binding inputs must survive the
# durable round trip and participate in idempotency. Arbitrary request
# fields are intentionally discarded rather than persisted.
optional = (
"idempotency_key",
"task_id",
"new_branch_oid",
"authorization_recovery_digest",
"lineage_anchor_oid",
"base_oid",
"branch_mode",
"generation",
"expected_main_oid",
"pr_number",
"target_branch",
"check_context",
"check_state",
"check_description",
)
for field in optional:
value = request.get(field)
if value is None:
continue
if isinstance(value, bool) or not isinstance(value, (str, int)):
raise JobError("JOB_BINDING_OPTION_INVALID")
text = str(value).strip()
if not text or len(text) > 512:
raise JobError("JOB_BINDING_OPTION_INVALID")
binding[field] = text
return binding
def submit(self, request: Mapping[str, Any], *, payload: bytes | None = None) -> JobReceipt:
binding = self._binding(request)
payload_ref = request.get("payload_ref")
payload_size = request.get("payload_size")
if payload is not None:
payload_ref, payload_size = self.store.payloads.put(payload, binding["payload_digest"])
if payload_ref is not None and payload_ref != f"sha256/{binding['payload_digest']}":
raise JobError("PAYLOAD_REFERENCE_MISMATCH")
if payload_ref is not None and (not isinstance(payload_size, int) or payload_size < 0):
raise JobError("PAYLOAD_SIZE_INVALID")
client_idempotency_key = binding.get("idempotency_key")
if client_idempotency_key is not None:
if len(client_idempotency_key) > 128 or not all(
character.isalnum() or character in "._:-" for character in client_idempotency_key
):
raise JobError("IDEMPOTENCY_KEY_INVALID")
key = sha256_json({"client_idempotency_key": client_idempotency_key})
else:
key = sha256_json(binding)
job_id = "job-" + key[:32]
durable_request = dict(binding)
if payload_ref is not None:
durable_request.update({"payload_ref": str(payload_ref), "payload_size": int(payload_size)})
now = utc_now()
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute("SELECT * FROM jobs WHERE idempotency_key=?", (key,)).fetchone()
if existing is not None:
if json.loads(existing["request_json"]).get("binding") != binding:
raise BindingConflict("BINDING_CONFLICT")
row = dict(existing)
connection.commit()
return self.store._receipt(row, duplicate=True)
connection.execute(
"INSERT INTO jobs(job_id,idempotency_key,campaign_id,repository,operation,branch,expected_branch_oid,payload_digest,payload_ref,payload_size,state,side_effect_state,revision,request_json,receipt_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(job_id, key, binding["campaign_id"], binding["repository"], binding["operation"], binding["branch"], binding["expected_branch_oid"], binding["payload_digest"], payload_ref, payload_size, JobState.REQUESTED.value, "NONE", 0, json.dumps({"binding": binding, **durable_request}, sort_keys=True), None, now, now),
)
connection.execute("INSERT INTO idempotency_keys(key,job_id,binding_json,created_at) VALUES(?,?,?,?)", (key, job_id, json.dumps(binding, sort_keys=True), now))
connection.execute("INSERT INTO state_transitions(job_id,from_state,to_state,revision,side_effect_state,created_at) VALUES(?,?,?,?,?,?)", (job_id, None, JobState.REQUESTED.value, 0, "NONE", now))
row = dict(connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone())
connection.commit()
except BindingConflict:
raise
except sqlite3.IntegrityError:
row = self.store.read(job_id)
return self.store._receipt(row, duplicate=True)
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
row["request"] = json.loads(row["request_json"])
self.store._mirror(row)
return self.store._receipt(row, duplicate=False)
def lease_job(
self,
job_id: str,
*,
lease_id: str,
lineage_anchor_oid: str,
current_head_oid: str,
generation: int,
lease_expires_at: str,
expected_revision: int,
) -> JobReceipt:
"""Atomically create a branch lease and move its Job to LEASED."""
if generation < 1 or any(len(oid) != 40 for oid in (lineage_anchor_oid, current_head_oid)):
raise JobError("LEASE_BINDING_INVALID")
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone()
if row is None:
raise JobError("JOB_NOT_FOUND")
if int(row["revision"]) != expected_revision:
raise JobError("REVISION_CAS_CONFLICT")
if row["branch"] in {"main", "master"}:
raise JobError("PROTECTED_BRANCH_REJECTED")
now = utc_now()
connection.execute("INSERT INTO branch_leases(lease_id,job_id,branch,lineage_anchor_oid,current_head_oid,generation,lease_expires_at,state,revision) VALUES(?,?,?,?,?,?,?,?,?)", (lease_id, job_id, row["branch"], lineage_anchor_oid, current_head_oid, generation, lease_expires_at, "ACTIVE", 0))
next_revision = expected_revision + 1
updated = connection.execute("UPDATE jobs SET state=?,side_effect_state=?,revision=?,updated_at=? WHERE job_id=? AND revision=?", (JobState.LEASED.value, "LEASED", next_revision, now, job_id, expected_revision))
if updated.rowcount != 1:
raise JobError("REVISION_CAS_CONFLICT")
connection.execute("INSERT INTO state_transitions(job_id,from_state,to_state,revision,side_effect_state,created_at) VALUES(?,?,?,?,?,?)", (job_id, row["state"], JobState.LEASED.value, next_revision, "LEASED", now))
result = dict(connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone())
connection.commit()
except sqlite3.IntegrityError as exc:
raise JobError("LEASE_CONFLICT") from exc
except (JobError, BindingConflict):
raise
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
result["request"] = json.loads(result["request_json"])
self.store._mirror(result)
return self.store._receipt(result, duplicate=True)
def update_lease(self, lease_id: str, *, expected_revision: int, current_head_oid: str, generation: int, state: str = "ACTIVE") -> None:
if len(current_head_oid) != 40 or generation < 1:
raise JobError("LEASE_BINDING_INVALID")
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
updated = connection.execute("UPDATE branch_leases SET current_head_oid=?,generation=?,state=?,revision=revision+1 WHERE lease_id=? AND revision=?", (current_head_oid, generation, state, lease_id, expected_revision))
if updated.rowcount != 1:
raise JobError("LEASE_REVISION_CAS_CONFLICT")
connection.commit()
except JobError:
raise
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
def advance(self, job_id: str, state: JobState, *, side_effect_state: str, receipt: Mapping[str, Any] | None = None, expected_revision: int | None = None) -> JobReceipt:
return self._advance(
job_id,
state,
side_effect_state=side_effect_state,
receipt=receipt,
expected_revision=expected_revision,
)
def _advance(
self,
job_id: str,
state: JobState,
*,
side_effect_state: str,
receipt: Mapping[str, Any] | None = None,
expected_revision: int | None = None,
_allow_verified_external_recovery: bool = False,
) -> JobReceipt:
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone()
if row is None:
raise JobError("JOB_NOT_FOUND")
previous = JobState(row["state"])
revision = int(row["revision"])
if expected_revision is not None and expected_revision != revision:
raise JobError("REVISION_CAS_CONFLICT")
recovery_transition = (
_allow_verified_external_recovery
and previous == JobState.MANUAL_OWNER_REVIEW_REQUIRED
and state == JobState.COMPLETED
)
if previous in _TERMINAL and state != previous and not recovery_transition:
raise JobError("JOB_TERMINAL")
if state not in _RECOVERABLE | {JobState.RECONCILING, JobState.MANUAL_OWNER_REVIEW_REQUIRED}:
if previous in _RECOVERABLE:
raise JobError("RECONCILE_REQUIRED")
if previous != JobState.RECONCILING and _ORDER.get(state, -1) < _ORDER.get(previous, -1):
raise JobError("JOB_STATE_REGRESSION")
next_revision = revision + 1
receipt_json = json.dumps(dict(receipt), sort_keys=True) if receipt is not None else row["receipt_json"]
now = utc_now()
updated = connection.execute("UPDATE jobs SET state=?,side_effect_state=?,revision=?,receipt_json=?,updated_at=? WHERE job_id=? AND revision=?", (state.value, side_effect_state, next_revision, receipt_json, now, job_id, revision))
if updated.rowcount != 1:
raise JobError("REVISION_CAS_CONFLICT")
connection.execute("INSERT INTO state_transitions(job_id,from_state,to_state,revision,side_effect_state,created_at) VALUES(?,?,?,?,?,?)", (job_id, previous.value, state.value, next_revision, side_effect_state, now))
if receipt is not None:
digest = hashlib.sha256(canonical_json(receipt)).hexdigest()
connection.execute("INSERT INTO receipts(receipt_id,job_id,kind,digest,payload_json,created_at) VALUES(?,?,?,?,?,?)", (f"receipt-{job_id}-{next_revision}", job_id, state.value, digest, json.dumps(dict(receipt), sort_keys=True), now))
result = dict(connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone())
connection.commit()
except (JobError, BindingConflict):
raise
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
result["request"] = json.loads(result["request_json"])
self.store._mirror(result)
return self.store._receipt(result, duplicate=True)
@staticmethod
def _validate_verified_external_merge_proof(
job_id: str,
binding: Mapping[str, Any],
proof: Mapping[str, Any],
) -> dict[str, Any]:
if not isinstance(proof, Mapping):
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_INVALID")
def require_oid(field: str) -> str:
value = proof.get(field)
if not isinstance(value, str) or len(value) != 40 or any(ch not in "0123456789abcdef" for ch in value):
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_INVALID")
return value
if proof.get("proof_schema") != "aios.job-recovery.v1":
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_INVALID")
if proof.get("job_id") != job_id:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("operation") != "PROTECTED_MERGE" or binding.get("operation") != "PROTECTED_MERGE":
raise JobError("EXTERNAL_SIDE_EFFECT_OPERATION_REJECTED")
if proof.get("pr_number") != 53 or str(binding.get("pr_number")) != "53":
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("pr_merged") is not True or proof.get("protected_merge") is not True:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("force_push") is not False:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("machine_actor") != "aios-autonomy-agent":
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
expected_head = str(binding.get("expected_branch_oid", ""))
expected_base = str(binding.get("expected_main_oid", ""))
head_oid = require_oid("head_oid")
base_oid = require_oid("base_oid")
merged_main = require_oid("main_oid_after_protected_merge")
gitea_merged_main = require_oid("gitea_main_oid_after_protected_merge")
authority_merged_main = require_oid("authority_main_oid_after_protected_merge")
current_gitea = require_oid("current_gitea_main_oid")
current_authority = require_oid("current_authority_main_oid")
if head_oid != expected_head or base_oid != expected_base:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if not (merged_main == gitea_merged_main == authority_merged_main == head_oid):
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if current_gitea != current_authority or proof.get("current_main_descends_from_protected_merge") is not True:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
return dict(proof)
def complete_verified_external_side_effect(
self,
job_id: str,
proof: Mapping[str, Any],
*,
expected_revision: int | None = None,
) -> JobReceipt:
"""Close one historical manual job from independently verified proof.
This is deliberately narrower than advance: it is only valid for the
known protected PR53 recovery shape and preserves the old receipt.
The actual state change still uses the normal revision-CAS path.
"""
current = self.store.read(job_id)
revision = int(current["revision"])
if expected_revision is not None and expected_revision != revision:
raise JobError("REVISION_CAS_CONFLICT")
request = json.loads(current["request_json"])
binding = request.get("binding", request)
normalized_proof = self._validate_verified_external_merge_proof(job_id, binding, proof)
existing_receipt = json.loads(current["receipt_json"]) if current["receipt_json"] else None
if JobState(current["state"]) == JobState.COMPLETED:
if (
existing_receipt
and existing_receipt.get("recovered_from_historical_manual") is True
and existing_receipt.get("proof") == normalized_proof
):
return self.store._receipt(current, duplicate=True)
raise JobError("JOB_TERMINAL")
if JobState(current["state"]) != JobState.MANUAL_OWNER_REVIEW_REQUIRED:
raise JobError("EXTERNAL_SIDE_EFFECT_RECOVERY_STATE_INVALID")
recovery_receipt = {
"recovered_from_historical_manual": True,
"recovery_kind": "VERIFIED_EXTERNAL_SIDE_EFFECT",
"historical_receipt": existing_receipt,
"proof": normalized_proof,
}
return self._advance(
job_id,
JobState.COMPLETED,
side_effect_state="REMOTE_VERIFIED",
receipt=recovery_receipt,
expected_revision=revision,
_allow_verified_external_recovery=True,
)
def reconcile(self, job_id: str, observer: Callable[[Mapping[str, Any]], Mapping[str, Any]]) -> JobReceipt:
current = self.store.read(job_id)
if JobState(current["state"]) not in _RECOVERABLE:
return self.store._receipt(current, duplicate=True)
self.advance(job_id, JobState.RECONCILING, side_effect_state="IN_PROGRESS")
try:
observed = dict(observer(current))
stable = JobState(observed.get("state", JobState.REQUESTED.value))
if stable in {JobState.RECOVERABLE_FAILURE, JobState.RECONCILING}:
raise JobError("RECONCILIATION_UNSTABLE")
return self.advance(job_id, stable, side_effect_state=str(observed.get("side_effect_state", "NONE")), receipt=observed.get("receipt"))
except Exception as exc:
self.advance(job_id, JobState.RECONCILIATION_REQUIRED, side_effect_state="UNKNOWN", receipt={"error_code": type(exc).__name__})
raise JobError("RECONCILIATION_REQUIRED") from exc
def resume(self, job_id: str) -> JobReceipt:
current = self.store.read(job_id)
if JobState(current["state"]) in _TERMINAL:
raise JobError("JOB_TERMINAL")
return self.reconcile(job_id, lambda _current: {"state": JobState.REQUESTED.value, "side_effect_state": "RESUME_SAME_JOB"})
@@ -0,0 +1,279 @@
"""Durable Job side-effect executor for the AIOS campaign control plane."""
from __future__ import annotations
import argparse
import base64
from dataclasses import dataclass, replace
import json
from pathlib import Path
from typing import Any, Mapping
from aios_authority_git_executor import AuthorityGitExecutor, AuthorityGitError
from aios_campaign_recovery import RecoveryEvidenceError, load_recovered_authorization
from aios_gitea_machine_broker import CampaignCapabilityReceipt, GiteaMachineBroker, MachineBrokerError
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore
TASK_ID = "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1"
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
REPOSITORY = "aios/aios"
BRANCH = "agent/autonomous-release-campaign-v1-20260807"
EXPECTED_BRANCH_OID = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
AUTHORIZATION_DIGEST = "6b0bf550eb702126e967eeff2d561fa808444b924d8a71f5399accad508ed3b2"
LINEAGE_ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
class JobExecutionError(RuntimeError):
pass
@dataclass(slots=True)
class DurableJobExecutor:
controller: GitOpsJobController
authority: AuthorityGitExecutor
broker: GiteaMachineBroker
recovery_path: Path
def _request(self, job_id: str) -> tuple[dict[str, Any], dict[str, Any]]:
row = self.controller.store.read(job_id)
try:
request = json.loads(row["request_json"])
except (KeyError, TypeError, json.JSONDecodeError) as exc:
raise JobExecutionError("JOB_REQUEST_INVALID") from exc
return row, dict(request.get("binding") or {})
def _validate_campaign(self, request: Mapping[str, Any]) -> str:
operation = str(request.get("operation", ""))
expected_branch_oid = request.get("expected_branch_oid")
required = {
"task_id": TASK_ID,
"campaign_id": CAMPAIGN_ID,
"repository": REPOSITORY,
"branch": BRANCH,
"authorization_recovery_digest": AUTHORIZATION_DIGEST,
}
for key, expected in required.items():
if request.get(key) != expected:
raise JobExecutionError(f"JOB_{key.upper()}_MISMATCH")
new_oid = request.get("new_branch_oid")
if not isinstance(new_oid, str) or len(new_oid) != 40 or any(ch not in "0123456789abcdef" for ch in new_oid):
raise JobExecutionError("NEW_BRANCH_OID_INVALID")
if not isinstance(expected_branch_oid, str) or len(expected_branch_oid) != 40 or any(ch not in "0123456789abcdef" for ch in expected_branch_oid):
raise JobExecutionError("EXPECTED_BRANCH_OID_INVALID")
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH" and expected_branch_oid == new_oid:
raise JobExecutionError("EXPECTED_BRANCH_OID_INVALID")
if operation != "ADVANCE_EXISTING_CAMPAIGN_BRANCH" and expected_branch_oid != new_oid:
raise JobExecutionError("POST_ADVANCE_HEAD_BINDING_INVALID")
try:
recovery = load_recovered_authorization(self.recovery_path)
except RecoveryEvidenceError as exc:
raise JobExecutionError(str(exc)) from exc
if recovery["digest"] != AUTHORIZATION_DIGEST:
raise JobExecutionError("RECOVERY_DIGEST_MISMATCH")
for key, expected in required.items():
if key == "campaign_id":
actual = recovery.get(key)
elif key == "authorization_recovery_digest":
continue
else:
actual = recovery.get({"expected_branch_oid": "expected_branch_oid"}.get(key, key))
if actual is not None and actual != expected:
raise JobExecutionError("RECOVERY_BINDING_MISMATCH")
if recovery.get("expected_branch_oid") != EXPECTED_BRANCH_OID:
raise JobExecutionError("RECOVERY_BRANCH_OID_MISMATCH")
authority_head = self.authority.read_ref(f"refs/heads/{BRANCH}")
if authority_head not in {expected_branch_oid, new_oid}:
raise JobExecutionError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
if not self.authority.ancestry_check(expected_branch_oid, new_oid):
raise JobExecutionError("NEW_OID_NOT_DESCENDANT")
if not self.authority.ancestry_check(EXPECTED_BRANCH_OID, new_oid):
raise JobExecutionError("NEW_OID_OUTSIDE_AUTHORIZED_LINEAGE")
identity = self.broker.verify_identity()
if identity.get("repository") != REPOSITORY or identity.get("login") == "aiman":
raise JobExecutionError("MACHINE_IDENTITY_SCOPE_INVALID")
return new_oid
def dry_run(self, job_id: str) -> dict[str, Any]:
_row, request = self._request(job_id)
new_oid = self._validate_campaign(request)
remote = self.broker.readback()
if remote is None:
action = "CREATE_REQUIRED"
elif remote == new_oid:
action = "ALREADY_IN_SYNC"
elif remote == request["expected_branch_oid"]:
action = "FAST_FORWARD_REQUIRED"
else:
action = "MANUAL_OWNER_REVIEW_REQUIRED"
if action == "MANUAL_OWNER_REVIEW_REQUIRED":
raise JobExecutionError("GITEA_BRANCH_CONFLICT")
return {
"decision": "SAFE_TO_ADVANCE",
"authority_action": "CAS_REQUIRED",
"gitea_action": action,
"previous_branch_oid": request["expected_branch_oid"],
"final_branch_oid": new_oid,
"force_push": False,
}
def _recover_requested(self, job_id: str) -> dict[str, Any]:
row = self.controller.store.read(job_id)
if JobState(row["state"]) in {JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED}:
self.controller.resume(job_id)
row = self.controller.store.read(job_id)
return row
def execute(self, job_id: str, *, dry_run: bool = False) -> dict[str, Any]:
try:
return self._execute_unprotected(job_id, dry_run=dry_run)
except Exception as exc:
try:
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state not in {JobState.COMPLETED, JobState.MANUAL_OWNER_REVIEW_REQUIRED, JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED}:
self.controller.advance(job_id, JobState.RECOVERABLE_FAILURE, side_effect_state="UNKNOWN", receipt={"error_code": type(exc).__name__})
except Exception:
pass
raise
def _execute_unprotected(self, job_id: str, *, dry_run: bool = False) -> dict[str, Any]:
row, request = self._request(job_id)
operation = request.get("operation")
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH":
if dry_run:
return self.dry_run(job_id)
return self._advance_campaign(job_id)
if operation == "COMMIT_FILE":
return self._commit_file(job_id, request)
if operation == "SYNC_TASK_BRANCH_TO_GITEA":
new_oid = self._validate_campaign(request)
final = self.broker.push_branch(new_oid)
return {"operation": operation, "remote_verified": final == new_oid, "final_network_oid": final}
if operation == "CREATE_PR":
self._validate_campaign(request)
return dict(self.broker.create_pr("AIOS autonomous release control plane", "Durable campaign continuation; exact head and recovery evidence are bound in the Job."))
if operation == "PUBLISH_CHECK":
self._validate_campaign(request)
context = request.get("check_context", "aios/durable-release")
state = request.get("check_state", "success")
description = request.get("check_description", "Durable campaign control-plane check passed")
return dict(self.broker.status(str(state), str(description), str(context)))
if operation == "PROTECTED_MERGE":
self._validate_campaign(request)
number = int(request.get("pr_number", "0"))
self.broker.transport.expected_merge_head = request["expected_branch_oid"]
expected_main = request.get("expected_main_oid")
if expected_main is not None:
self.broker.transport.expected_merge_base = expected_main
return dict(self.broker.merge(number))
if operation == "RECONCILE_AUTHORITY_MAIN":
self._validate_campaign(request)
old = request.get("expected_main_oid")
new = request.get("new_branch_oid")
if not isinstance(old, str) or not isinstance(new, str):
raise JobExecutionError("MAIN_RECONCILIATION_BINDING_INVALID")
gitea_main = self.broker.transport.read_branch("main")
if gitea_main != new:
raise JobExecutionError("GITEA_MAIN_EXPECTATION_MISMATCH")
if not self.authority.ancestry_check(old, new):
raise JobExecutionError("MAIN_OID_NOT_DESCENDANT")
return {"authority_main": self.authority.ff_CAS_main_after_verified_protected_merge(old, new)}
raise JobExecutionError("OPERATION_NOT_ALLOWED")
def _advance_campaign(self, job_id: str) -> dict[str, Any]:
self._recover_requested(job_id)
row, request = self._request(job_id)
new_oid = self._validate_campaign(request)
expected_oid = request["expected_branch_oid"]
state = JobState(row["state"])
if state == JobState.REQUESTED:
receipt = self.controller.advance(job_id, JobState.AUTHORIZED, side_effect_state="AUTHORIZED", receipt={"authorization_digest": AUTHORIZATION_DIGEST, "task_id": TASK_ID})
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.AUTHORIZED:
self.controller.lease_job(job_id, lease_id=f"lease-{job_id}", lineage_anchor_oid=LINEAGE_ANCHOR, current_head_oid=expected_oid, generation=1, lease_expires_at="2099-01-01T00:00:00Z", expected_revision=int(row["revision"]))
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.LEASED:
self.controller.advance(job_id, JobState.WORKTREE_READY, side_effect_state="WORKTREE_READY", receipt={"branch": BRANCH, "expected_oid": expected_oid})
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.WORKTREE_READY:
self.controller.advance(job_id, JobState.EXECUTING, side_effect_state="IN_PROGRESS")
row = self.controller.store.read(job_id)
authority_before = self.authority.read_ref(f"refs/heads/{BRANCH}")
if authority_before == expected_oid:
authority_oid = self.authority.advance_task_branch_CAS(f"refs/heads/{BRANCH}", expected_oid, new_oid)
elif authority_before == new_oid:
authority_oid = new_oid
else:
raise JobExecutionError("AUTHORITY_BRANCH_CONFLICT")
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.EXECUTING:
self.controller.advance(job_id, JobState.LOCAL_VERIFIED, side_effect_state="LOCAL_VERIFIED", receipt={"authority": {"branch": BRANCH, "previous_oid": expected_oid, "new_oid": new_oid, "CAS_success": True, "readback_oid": authority_oid}})
row = self.controller.store.read(job_id)
remote_before = self.broker.readback()
if remote_before != new_oid:
if remote_before not in {None, expected_oid}:
raise JobExecutionError("GITEA_BRANCH_CONFLICT")
if JobState(row["state"]) == JobState.LOCAL_VERIFIED:
if hasattr(self.broker, "receipt"):
self.broker.receipt = replace(self.broker.receipt, expected_oid=expected_oid)
self.controller.advance(job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS", receipt={"gitea": {"previous_oid": remote_before, "desired_oid": new_oid, "force_push": False}})
final_network = self.broker.push_branch(new_oid)
else:
final_network = new_oid
final_api = self.broker.readback()
if final_api != new_oid:
raise JobExecutionError("GITEA_BRANCH_READBACK_MISMATCH")
row = self.controller.store.read(job_id)
if JobState(row["state"]) in {JobState.LOCAL_VERIFIED, JobState.REMOTE_SYNCING}:
self.controller.advance(job_id, JobState.REMOTE_VERIFIED, side_effect_state="REMOTE_VERIFIED", receipt={"gitea": {"machine_actor": "aios-autonomy-agent", "previous_oid": remote_before, "desired_oid": new_oid, "final_network_oid": final_network, "final_API_oid": final_api, "force_push": False}})
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.REMOTE_VERIFIED:
self.controller.advance(job_id, JobState.COMPLETED, side_effect_state="REMOTE_VERIFIED", receipt={"authority_oid": authority_oid, "network_oid": final_network, "gitea_api_oid": final_api, "force_push": False})
return self.controller.store._receipt(self.controller.store.read(job_id), duplicate=True).as_dict()
def _commit_file(self, job_id: str, request: Mapping[str, Any]) -> dict[str, Any]:
row = self.controller.store.read(job_id)
payload_ref = request.get("payload_ref")
if not payload_ref:
raise JobExecutionError("PAYLOAD_REQUIRED")
payload = self.controller.store.payloads.read_verified(payload_ref, row["payload_digest"], int(request["payload_size"]))
try:
spec = json.loads(payload.decode())
path = str(spec["path"])
content = base64.b64decode(spec["content_base64"], validate=True)
except Exception as exc:
raise JobExecutionError("COMMIT_FILE_PAYLOAD_INVALID") from exc
new_oid = self.authority.commit_file_in_isolated_worktree(base_oid=row["expected_branch_oid"], files={path: content}, message="chore(gitops): durable commit-file", worktree_name=job_id)
return {"operation": "COMMIT_FILE", "new_oid": new_oid}
def build_from_environment(job_root: Path) -> DurableJobExecutor:
from aios_gitea_machine_transport import GiteaMachineTransport
from aios_gitea_machine_broker import MachineCredentialProjection
account = __import__("os").environ.get("AIOS_GITEA_MACHINE_ACCOUNT", "aios-autonomy-agent")
api = __import__("os").environ.get("AIOS_GITEA_API", "https://g.cnnetmkt.top/git/api/v1")
remote = __import__("os").environ.get("AIOS_GITEA_REMOTE", "https://g.cnnetmkt.top/git/aios/aios.git")
authority_dir = Path(__import__("os").environ.get("AIOS_GITOPS_AUTHORITY_GIT_DIR", "/data/aios-gitops/repo.git"))
worktree_root = Path(__import__("os").environ.get("AIOS_GITOPS_WORKTREE_ROOT", "/var/lib/aios-gitops/worktrees"))
recovery = Path(__import__("os").environ.get("AIOS_RELEASE_RECOVERY_RECEIPT", "/var/lib/aios-gitops/release-campaigns/AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807/authorization-recovery.json"))
transport = GiteaMachineTransport(api, REPOSITORY, remote, authority_dir)
credential = MachineCredentialProjection.from_mapping({"scheme": "systemd-credential", "name": "gitea-machine-token", "account": account})
receipt = CampaignCapabilityReceipt(CAMPAIGN_ID, TASK_ID, REPOSITORY, BRANCH, EXPECTED_BRANCH_OID, frozenset({"exact_task_branch_push", "pr_create_update", "status_publish", "protected_merge", "remote_readback"}), frozenset({"direct_main_push", "force_push", "tag_update", "tag_delete", "baseline_write", "production_deploy"}))
return DurableJobExecutor(GitOpsJobController(JobStore(job_root)), AuthorityGitExecutor(authority_dir, worktree_root), GiteaMachineBroker(transport, credential, receipt), recovery)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--job-root", required=True, type=Path)
parser.add_argument("--job-id", required=True)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(argv)
result = build_from_environment(args.job_root).execute(args.job_id, dry_run=args.dry_run)
print(json.dumps(result, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,80 @@
"""Pure, observation-first decisions for governed GitOps reconciliation."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import re
from typing import Optional
OID_RE = re.compile(r"^[0-9a-f]{40}$")
class ReconciliationAction(str, Enum):
NOOP = "NOOP"
PUSH_EXACT = "PUSH_EXACT"
VERIFY_REMOTE = "VERIFY_REMOTE"
MARK_REMOTE_VERIFIED = "MARK_REMOTE_VERIFIED"
MARK_RECONCILIATION_REQUIRED = "MARK_RECONCILIATION_REQUIRED"
FAIL_CONFLICT = "FAIL_CONFLICT"
@dataclass(frozen=True, slots=True)
class RemoteObservation:
repository_identity: str
branch: str
desired_oid: str
authority_oid: Optional[str]
remote_git_oid: Optional[str]
remote_api_oid: Optional[str]
commit_visible: bool
observed_at: str
expected_oid: Optional[str] = None
@property
def all_equal(self) -> bool:
values = (self.authority_oid, self.remote_git_oid, self.remote_api_oid, self.desired_oid)
return all(isinstance(value, str) for value in values) and len(set(values)) == 1
@dataclass(frozen=True, slots=True)
class ReconciliationDecision:
action: ReconciliationAction
state: str
reason: str
retry_safe: bool = False
def _valid_oid(value: Optional[str]) -> bool:
return value is None or bool(OID_RE.fullmatch(value))
def decide_reconciliation(observation: RemoteObservation) -> ReconciliationDecision:
"""Decide from a fresh observation; never complete on a missing remote."""
if observation.repository_identity != "aios/aios":
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "REPOSITORY_IDENTITY_MISMATCH")
if not observation.branch.startswith("agent/"):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "BRANCH_SCOPE_DENIED")
if not _valid_oid(observation.desired_oid) or not _valid_oid(observation.expected_oid):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "OID_INVALID")
if observation.authority_oid is None:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "AUTHORITY_UNAVAILABLE")
if observation.authority_oid != observation.desired_oid:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "AUTHORITY_CONFLICT")
present = [oid for oid in (observation.remote_git_oid, observation.remote_api_oid) if oid is not None]
if any(oid not in {observation.desired_oid, observation.expected_oid} for oid in present):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "REMOTE_OID_CONFLICT")
if observation.all_equal and observation.commit_visible:
return ReconciliationDecision(ReconciliationAction.MARK_REMOTE_VERIFIED, "REMOTE_VERIFIED", "ALL_OIDS_EQUAL_AND_VISIBLE")
if observation.remote_git_oid is None and observation.remote_api_oid is None:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "REMOTE_PUSH_PENDING", "AUTHORITY_DESIRED_REMOTE_MISSING")
if observation.remote_git_oid == observation.desired_oid or observation.remote_api_oid == observation.desired_oid:
return ReconciliationDecision(ReconciliationAction.VERIFY_REMOTE, "REMOTE_VERIFY_PENDING", "REMOTE_PROOF_PARTIAL")
if observation.expected_oid is not None and observation.remote_git_oid == observation.expected_oid and observation.remote_api_oid == observation.expected_oid:
return ReconciliationDecision(ReconciliationAction.PUSH_EXACT, "REMOTE_PUSH_PENDING", "SAFE_EXACT_FAST_FORWARD_RETRY", retry_safe=True)
if not observation.commit_visible:
return ReconciliationDecision(ReconciliationAction.VERIFY_REMOTE, "COMMIT_VISIBILITY_PENDING", "COMMIT_VISIBILITY_LAG")
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "PARTIAL_REMOTE_OBSERVATION")
def reconcile_observation(**kwargs: object) -> ReconciliationDecision:
return decide_reconciliation(RemoteObservation(**kwargs))
+100
View File
@@ -0,0 +1,100 @@
"""Single supervised socket-activated worker for durable GitOps Jobs.
The worker consumes durable requests and reconciliation queries. Repository
mutation is a separate injected executor and remains impossible while the
legacy global write gate is false.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import signal
import socket
import threading
from typing import Any, Mapping
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore, ServiceUnavailable
def runtime_policy(environment: Mapping[str, str] | None = None) -> dict[str, bool]:
env = environment or os.environ
legacy = env.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
durable = env.get("AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
# Legacy direct Git mutation remains disabled. Authorized durable Jobs use
# their own campaign/task capability and may mutate only through the
# worker's lease/CAS path while the legacy gate stays false.
return {"legacy_global_write": legacy, "durable_job_execution": durable, "repository_mutation_allowed": durable}
def handle_request(controller: GitOpsJobController, request: Mapping[str, Any], executor: Any | None = None) -> dict[str, Any]:
job_id = str(request.get("job_id") or "")
if not job_id:
raise JobError("JOB_ID_REQUIRED")
if request.get("action") == "execute":
if executor is None:
raise JobError("JOB_EXECUTOR_UNAVAILABLE")
result = executor.execute(job_id, dry_run=bool(request.get("dry_run", False)))
receipt = controller.store._receipt(controller.store.read(job_id), duplicate=True)
return {"job": result, **receipt.as_dict()}
if request.get("action") == "resume":
receipt = controller.resume(job_id)
else:
row = controller.store.read(job_id)
receipt = controller.store._receipt(row, duplicate=True)
return receipt.as_dict()
def serve_socket(sock: socket.socket, controller: GitOpsJobController, stopping: threading.Event, executor: Any | None = None) -> None:
sock.listen(32)
sock.settimeout(1.0)
while not stopping.is_set():
try:
connection, _ = sock.accept()
except socket.timeout:
continue
with connection:
try:
raw = connection.makefile("rb").readline(1_048_576)
request = json.loads(raw.decode("utf-8"))
response = {"status": "ok", **handle_request(controller, request, executor)}
except ServiceUnavailable as exc:
response = {"status": "error", "error": str(exc), "retryable": True, "side_effect_state": "UNKNOWN"}
except Exception as exc:
response = {"status": "error", "error": type(exc).__name__, "retryable": False}
connection.sendall((json.dumps(response, sort_keys=True) + "\n").encode("utf-8"))
def socket_from_systemd() -> socket.socket:
if os.environ.get("LISTEN_FDS") != "1":
raise RuntimeError("SOCKET_ACTIVATION_REQUIRED")
return socket.fromfd(3, socket.AF_UNIX, socket.SOCK_STREAM)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--job-root", required=True, type=Path)
args = parser.parse_args(argv)
policy = runtime_policy()
if policy["legacy_global_write"]:
raise RuntimeError("LEGACY_GLOBAL_WRITES_MUST_REMAIN_FALSE")
if not policy["durable_job_execution"]:
raise RuntimeError("DURABLE_JOB_EXECUTION_GATE_CLOSED")
controller = GitOpsJobController(JobStore(args.job_root))
from aios_gitops_job_executor import build_from_environment
executor = build_from_environment(args.job_root)
stopping = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stopping.set())
signal.signal(signal.SIGINT, lambda *_: stopping.set())
sock = socket_from_systemd()
try:
serve_socket(sock, controller, stopping, executor)
finally:
sock.close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,59 @@
"""Central fail-closed policy seam for governed task and continuation requests."""
from __future__ import annotations
from dataclasses import dataclass
import re
from typing import Any, Mapping
OID_RE = re.compile(r"^[0-9a-f]{40}$")
@dataclass(frozen=True, slots=True)
class GovernedPolicyInput:
identity: str
task_id: str
repository: str
operation: str
base_oid: str
expected_branch_oid: str
target_branch: str
generation: int
risk: str = "engineering"
requested_capabilities: tuple[str, ...] = ()
previous_generation: int | None = None
@dataclass(frozen=True, slots=True)
class PolicyDecision:
decision: str
reason: str
def as_dict(self) -> dict[str, str]:
return {"decision": self.decision, "reason": self.reason}
def evaluate_policy(value: GovernedPolicyInput | Mapping[str, Any]) -> PolicyDecision:
if isinstance(value, Mapping):
value = GovernedPolicyInput(**value)
if value.repository != "aios/aios":
return PolicyDecision("DENY", "REPOSITORY_IDENTITY_MISMATCH")
if not OID_RE.fullmatch(value.base_oid) or not OID_RE.fullmatch(value.expected_branch_oid):
return PolicyDecision("DENY", "OID_INVALID")
if not value.target_branch.startswith("agent/") or value.target_branch in {"main", "master"}:
return PolicyDecision("DENY", "BRANCH_SCOPE_DENIED")
if "force_push" in value.requested_capabilities or "force" in value.operation.lower():
return PolicyDecision("DENY", "FORCE_PUSH_DENIED")
if {"main_write", "tag_write", "baseline_write", "production_write", "production_deploy", "tag_mutation", "baseline_mutation"}.intersection(value.requested_capabilities):
return PolicyDecision("DENY", "MUTATION_SCOPE_DENIED")
if not isinstance(value.generation, int) or isinstance(value.generation, bool) or value.generation < 1:
return PolicyDecision("DENY", "GENERATION_INVALID")
if value.previous_generation is not None and value.generation <= value.previous_generation:
return PolicyDecision("MANUAL_OWNER_REVIEW_REQUIRED", "GENERATION_NOT_MONOTONIC")
if not value.task_id:
return PolicyDecision("DENY", "TASK_ID_INVALID")
return PolicyDecision("ALLOW", "GOVERNED_ENGINEERING_SCOPE")
class GovernedPolicy:
def decide(self, request: GovernedPolicyInput | Mapping[str, Any]) -> PolicyDecision:
return evaluate_policy(request)
@@ -0,0 +1,84 @@
"""Durable governed task binding for CREATE_NEW and CONTINUE_EXISTING."""
from __future__ import annotations
from dataclasses import dataclass, asdict
import hashlib
import json
from pathlib import Path
import re
from typing import Any, Mapping
from aios_gitops_job_controller import GitOpsJobController, JobStore
OID_RE = re.compile(r"^[0-9a-f]{40}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class BootstrapError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class GovernedTaskBinding:
task_id: str
repository: str
base_oid: str
target_branch: str
risk: str
description: str
idempotency_key: str
expected_branch_oid: str
branch_mode: str
generation: int
job_id: str | None = None
def response(self, *, idempotent: bool = False) -> dict[str, Any]:
return {"task_id": self.task_id, "repository": self.repository, "base_oid": self.base_oid, "target_branch": self.target_branch, "expected_branch_oid": self.expected_branch_oid, "generation": self.generation, "branch_mode": self.branch_mode, "create_target_branch": self.branch_mode == "CREATE_NEW", "worktree_mode": "detached_at_base_oid" if self.branch_mode == "CREATE_NEW" else "detached_at_expected_head", "cas_ref_update_required": True, "idempotent": idempotent, "state": "BOUND", "job_id": self.job_id}
def _safe(value: str) -> str:
return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-")[:120] or "task"
class GovernedTaskBootstrap:
def __init__(self, authority: Any, root: Path, *, remote_name: str, token_path: Path, ssh_key_path: Path, api_base: str = "https://g.cnnetmkt.top/git/api/v1") -> None:
self.authority = authority; self.root = Path(root); self.remote_name = remote_name; self.token_path = Path(token_path); self.ssh_key_path = Path(ssh_key_path); self.api_base = api_base; self.task_meta_root = self.root / "task-bindings"; self.job_root = self.root / "jobs"; self.task_meta_root.mkdir(parents=True, exist_ok=True); self.job_root.mkdir(parents=True, exist_ok=True)
def _store(self, task_id: str) -> tuple[JobStore, GitOpsJobController]:
root = self.job_root / _safe(task_id); store = JobStore(root, payload_root=root / "payloads"); return store, GitOpsJobController(store)
def _metadata(self, task_id: str) -> Path:
return self.task_meta_root / (_safe(task_id) + ".json")
def _read_existing(self, task_id: str) -> GovernedTaskBinding | None:
path = self._metadata(task_id)
if not path.is_file() or path.is_symlink(): return None
return GovernedTaskBinding(**json.loads(path.read_text(encoding="utf-8")))
def _write(self, binding: GovernedTaskBinding) -> None:
path = self._metadata(binding.task_id); temporary = path.with_suffix(".tmp"); temporary.write_text(json.dumps(asdict(binding), sort_keys=True), encoding="utf-8"); temporary.replace(path)
def create(self, request: Mapping[str, Any]) -> dict[str, Any]:
values = {key: str(request[key]) for key in ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key")}
if values["repository"] != "aios/aios" or not OID_RE.fullmatch(values["base_oid"]): raise BootstrapError("TASK_BINDING_INVALID")
if not BRANCH_RE.fullmatch(values["target_branch"]): raise BootstrapError("TARGET_BRANCH_INVALID")
existing = self._read_existing(values["task_id"])
if existing is not None:
if any(getattr(existing, key) != values[key] for key in ("repository", "base_oid", "target_branch", "risk", "description")): raise BootstrapError("TASK_BINDING_CONFLICT")
return existing.response(idempotent=True)
if not self.authority.commit_exists(values["base_oid"]): raise BootstrapError("BASE_COMMIT_NOT_FOUND")
current = self.authority.read_ref("refs/heads/" + values["target_branch"])
expected = str(request.get("expected_branch_oid") or values["base_oid"])
if current is None:
mode, generation = "CREATE_NEW", 1
if expected != values["base_oid"]: raise BootstrapError("EXPECTED_BASE_CONFLICT")
else:
if expected != current: raise BootstrapError("EXISTING_BRANCH_OID_CONFLICT")
mode, generation = "CONTINUE_EXISTING", int(request.get("generation", 2))
if generation < 1: raise BootstrapError("GENERATION_INVALID")
payload = json.dumps(values, sort_keys=True, separators=(",", ":")).encode()
_, controller = self._store(values["task_id"])
receipt = controller.submit({"idempotency_key": values["idempotency_key"], "campaign_id": values["task_id"], "task_id": values["task_id"], "repository": values["repository"], "operation": "GOVERNED_TASK_CREATE", "branch": values["target_branch"], "expected_branch_oid": expected, "payload_digest": hashlib.sha256(payload).hexdigest(), "lineage_anchor_oid": values["base_oid"]}, payload=payload)
binding = GovernedTaskBinding(**values, expected_branch_oid=expected, branch_mode=mode, generation=generation, job_id=receipt.job_id)
self._write(binding)
return binding.response()
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
"""Narrow, non-root client contract for the privileged signer broker."""
from __future__ import annotations
import base64
import hashlib
import json
from pathlib import Path
import re
import socket
from typing import Any, Protocol
DEFAULT_SIGNER_BROKER_SOCKET = Path("/run/aios/signer-broker.sock")
MAX_SIGNING_PAYLOAD = 1024 * 1024
CODE_RE = re.compile(r"^[A-Z0-9_.:-]{3,96}$")
class SignerBrokerError(RuntimeError):
def __init__(self, code: str):
self.code = code
super().__init__(code)
class SignerBroker(Protocol):
def self_test(self) -> None: ...
def sign(self, payload_kind: str, payload: bytes) -> tuple[bytes, str]: ...
class UnixSignerBroker:
"""AF_UNIX request client; it never accepts commands, paths, or shell text."""
def __init__(self, socket_path: Path = DEFAULT_SIGNER_BROKER_SOCKET, timeout: float = 15.0):
path = Path(socket_path)
if path != DEFAULT_SIGNER_BROKER_SOCKET or not path.is_absolute():
raise SignerBrokerError("SIGNER_BROKER_SOCKET_INVALID")
self.socket_path = path
self.timeout = timeout
def _request(self, operation: str, **payload: Any) -> dict[str, Any]:
if operation not in {"self_test", "sign"}:
raise SignerBrokerError("SIGNER_OPERATION_REJECTED")
request = {
"schema": "aios.signer-broker.request.v1",
"operation": operation,
**payload,
}
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as channel:
channel.settimeout(self.timeout)
channel.connect(str(self.socket_path))
channel.sendall((json.dumps(request, sort_keys=True, separators=(",", ":")) + "\n").encode())
raw = channel.makefile("rb").readline()
except (OSError, TimeoutError) as exc:
raise SignerBrokerError("SIGNER_BROKER_UNAVAILABLE") from exc
try:
response = json.loads(raw.decode())
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise SignerBrokerError("SIGNER_BROKER_RESPONSE_INVALID") from exc
if not isinstance(response, dict) or response.get("schema") != "aios.signer-broker.response.v1":
raise SignerBrokerError("SIGNER_BROKER_RESPONSE_INVALID")
if response.get("status") != "PASS":
code = str(response.get("error_code") or "SIGNER_BROKER_REJECTED")
raise SignerBrokerError(code if CODE_RE.fullmatch(code) else "SIGNER_BROKER_REJECTED")
return response
def self_test(self) -> None:
self._request("self_test")
def sign(self, payload_kind: str, payload: bytes) -> tuple[bytes, str]:
if payload_kind not in {"approval_plan", "grant_payload"}:
raise SignerBrokerError("SIGNER_PAYLOAD_KIND_REJECTED")
if not isinstance(payload, bytes) or not payload or len(payload) > MAX_SIGNING_PAYLOAD:
raise SignerBrokerError("SIGNER_PAYLOAD_INVALID")
response = self._request(
"sign",
payload_kind=payload_kind,
payload_sha256=hashlib.sha256(payload).hexdigest(),
payload_base64=base64.b64encode(payload).decode("ascii"),
)
try:
signature = base64.b64decode(str(response["signature_base64"]), validate=True)
key_id = str(response["key_id"]).strip()
except (KeyError, ValueError) as exc:
raise SignerBrokerError("SIGNER_BROKER_RESPONSE_INVALID") from exc
if not signature or not key_id or len(key_id) > 256:
raise SignerBrokerError("SIGNER_BROKER_RESPONSE_INVALID")
return signature, key_id
@@ -0,0 +1,289 @@
"""Privileged, fixed-function signer broker service."""
from __future__ import annotations
import argparse
import base64
import binascii
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import socket
import socketserver
import stat
import struct
import subprocess
import tempfile
import threading
from typing import Any, Callable, Mapping, Protocol, Sequence
REQUEST_SCHEMA = "aios.signer-broker.request.v1"
RESPONSE_SCHEMA = "aios.signer-broker.response.v1"
AUDIT_SCHEMA = "aios.signer-broker.audit.v1"
DEFAULT_SOCKET_PATH = Path("/run/aios/signer-broker.sock")
DEFAULT_SIGNER_PATH = Path("/usr/local/libexec/aios-grant-signer-adapter")
DEFAULT_AUDIT_PATH = Path("/var/lib/aios-gitops/audit/signer-broker.jsonl")
DEFAULT_STATE_ROOT = Path("/var/lib/aios-gitops/signer-broker")
ALLOWED_OPERATIONS = frozenset({"self_test", "sign"})
ALLOWED_PAYLOAD_KINDS = frozenset({"approval_plan", "grant_payload"})
MAX_SIGNING_PAYLOAD = 1024 * 1024
MAX_REQUEST_BYTES = 2 * MAX_SIGNING_PAYLOAD + 8192
CODE_RE = re.compile(r"^[A-Z0-9_.:-]{3,96}$")
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
class SignerBrokerServiceError(RuntimeError):
def __init__(self, code: str):
self.code = code if CODE_RE.fullmatch(code) else "SIGNER_INTERNAL_ERROR"
super().__init__(self.code)
class SignerRunner(Protocol):
def self_test(self) -> None: ...
def sign(self, payload_kind: str, payload: bytes) -> tuple[bytes, str]: ...
@dataclass(frozen=True, slots=True)
class SignerBrokerPolicy:
socket_path: Path = DEFAULT_SOCKET_PATH
signer_path: Path = DEFAULT_SIGNER_PATH
audit_path: Path = DEFAULT_AUDIT_PATH
state_root: Path = DEFAULT_STATE_ROOT
allowed_uids: frozenset[int] = frozenset()
signer_timeout: float = 60.0
max_payload_bytes: int = MAX_SIGNING_PAYLOAD
def __post_init__(self) -> None:
for name in ("socket_path", "signer_path", "audit_path", "state_root"):
if not Path(getattr(self, name)).is_absolute():
raise SignerBrokerServiceError("SIGNER_POLICY_PATH_INVALID")
if not self.allowed_uids or any(not isinstance(uid, int) or uid < 0 for uid in self.allowed_uids):
raise SignerBrokerServiceError("SIGNER_POLICY_CALLER_INVALID")
if not 0 < self.signer_timeout <= 300:
raise SignerBrokerServiceError("SIGNER_POLICY_TIMEOUT_INVALID")
if not 0 < self.max_payload_bytes <= MAX_SIGNING_PAYLOAD:
raise SignerBrokerServiceError("SIGNER_POLICY_PAYLOAD_LIMIT_INVALID")
class FixedSignerRunner:
"""Use one configured signer executable and broker-owned temporary paths."""
def __init__(self, policy: SignerBrokerPolicy):
self.policy = policy
def _run(self, argv: Sequence[str]) -> None:
try:
result = subprocess.run(list(argv), capture_output=True, text=True, check=False, timeout=self.policy.signer_timeout)
except FileNotFoundError as exc:
raise SignerBrokerServiceError("SIGNER_UNAVAILABLE") from exc
except subprocess.TimeoutExpired as exc:
raise SignerBrokerServiceError("SIGNER_TIMEOUT") from exc
except OSError as exc:
raise SignerBrokerServiceError("SIGNER_UNAVAILABLE") from exc
if result.returncode != 0:
raise SignerBrokerServiceError("SIGNER_FAILED")
def self_test(self) -> None:
self._run((str(self.policy.signer_path), "self-test"))
def sign(self, payload_kind: str, payload: bytes) -> tuple[bytes, str]:
if payload_kind not in ALLOWED_PAYLOAD_KINDS:
raise SignerBrokerServiceError("SIGNER_PAYLOAD_KIND_REJECTED")
try:
self.policy.state_root.mkdir(parents=True, exist_ok=True)
os.chmod(self.policy.state_root, 0o700)
with tempfile.TemporaryDirectory(prefix="request-", dir=self.policy.state_root) as directory:
root = Path(directory)
input_path = root / "payload.canonical"
signature_path = root / "payload.sig"
key_id_path = root / "payload.key-id"
input_path.write_bytes(payload)
os.chmod(input_path, 0o600)
self._run((str(self.policy.signer_path), "sign", "--namespace", payload_kind, "--input", str(input_path), "--signature-out", str(signature_path), "--key-id-out", str(key_id_path)))
signature = signature_path.read_bytes()
key_id = key_id_path.read_text(encoding="utf-8").strip()
except SignerBrokerServiceError:
raise
except (OSError, UnicodeError) as exc:
raise SignerBrokerServiceError("SIGNER_OUTPUT_INVALID") from exc
if not signature or not key_id or len(key_id) > 256 or "\n" in key_id or "\r" in key_id:
raise SignerBrokerServiceError("SIGNER_OUTPUT_INVALID")
return signature, key_id
class SignerBrokerService:
def __init__(self, policy: SignerBrokerPolicy, runner: SignerRunner | None = None, *, now: Callable[[], datetime] | None = None) -> None:
self.policy = policy
self.runner = runner or FixedSignerRunner(policy)
self.now = now or (lambda: datetime.now(timezone.utc))
self._audit_lock = threading.Lock()
@staticmethod
def _operation(value: Any) -> str:
return value if isinstance(value, str) and re.fullmatch(r"[A-Za-z0-9_.:-]{1,64}", value) else "INVALID"
def _audit(self, *, peer_uid: int, operation: str, status: str, error_code: str | None = None, payload_sha256: str | None = None) -> None:
event: dict[str, Any] = {"schema": AUDIT_SCHEMA, "timestamp": self.now().astimezone(timezone.utc).isoformat(), "peer_uid": peer_uid, "operation": self._operation(operation), "status": status}
if isinstance(payload_sha256, str) and SHA256_RE.fullmatch(payload_sha256):
event["payload_sha256"] = payload_sha256
if error_code:
event["error_code"] = error_code if CODE_RE.fullmatch(error_code) else "SIGNER_INTERNAL_ERROR"
try:
with self._audit_lock:
self.policy.audit_path.parent.mkdir(parents=True, exist_ok=True)
with self.policy.audit_path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n")
handle.flush()
os.fsync(handle.fileno())
os.chmod(self.policy.audit_path, 0o600)
except OSError as exc:
raise SignerBrokerServiceError("SIGNER_AUDIT_UNAVAILABLE") from exc
@staticmethod
def _failure(code: str) -> dict[str, Any]:
return {"schema": RESPONSE_SCHEMA, "status": "FAIL", "error_code": code if CODE_RE.fullmatch(code) else "SIGNER_INTERNAL_ERROR"}
def _dispatch(self, request: Mapping[str, Any], peer_uid: int) -> tuple[dict[str, Any], str, str | None]:
operation = self._operation(request.get("operation"))
if peer_uid not in self.policy.allowed_uids:
raise SignerBrokerServiceError("CALLER_UNAUTHORIZED")
if request.get("schema") != REQUEST_SCHEMA:
raise SignerBrokerServiceError("REQUEST_SCHEMA_INVALID")
if operation not in ALLOWED_OPERATIONS:
raise SignerBrokerServiceError("SIGNER_OPERATION_REJECTED")
if operation == "self_test":
if set(request) != {"schema", "operation"}:
raise SignerBrokerServiceError("REQUEST_FIELDS_REJECTED")
self.runner.self_test()
return {"schema": RESPONSE_SCHEMA, "status": "PASS", "operation": operation}, operation, None
if set(request) != {"schema", "operation", "payload_kind", "payload_sha256", "payload_base64"}:
raise SignerBrokerServiceError("REQUEST_FIELDS_REJECTED")
payload_kind = request.get("payload_kind")
encoded = request.get("payload_base64")
claimed_hash = request.get("payload_sha256")
if payload_kind not in ALLOWED_PAYLOAD_KINDS or not isinstance(encoded, str) or not isinstance(claimed_hash, str) or not SHA256_RE.fullmatch(claimed_hash):
raise SignerBrokerServiceError("SIGNER_PAYLOAD_INVALID")
if len(encoded) > self.policy.max_payload_bytes * 2 + 8:
raise SignerBrokerServiceError("SIGNER_PAYLOAD_INVALID")
try:
payload = base64.b64decode(encoded.encode("ascii"), validate=True)
except (ValueError, UnicodeError, binascii.Error) as exc:
raise SignerBrokerServiceError("SIGNER_PAYLOAD_INVALID") from exc
if not payload or len(payload) > self.policy.max_payload_bytes:
raise SignerBrokerServiceError("SIGNER_PAYLOAD_INVALID")
actual_hash = hashlib.sha256(payload).hexdigest()
if actual_hash != claimed_hash:
raise SignerBrokerServiceError("SIGNER_PAYLOAD_HASH_MISMATCH")
signature, key_id = self.runner.sign(payload_kind, payload)
if not isinstance(signature, bytes) or not signature or not isinstance(key_id, str) or not key_id.strip() or len(key_id) > 256:
raise SignerBrokerServiceError("SIGNER_OUTPUT_INVALID")
return {"schema": RESPONSE_SCHEMA, "status": "PASS", "operation": operation, "signature_base64": base64.b64encode(signature).decode("ascii"), "key_id": key_id.strip()}, operation, actual_hash
def handle_request(self, request: Mapping[str, Any], peer_uid: int) -> dict[str, Any]:
operation = self._operation(request.get("operation")) if isinstance(request, Mapping) else "INVALID"
payload_hash = request.get("payload_sha256") if isinstance(request, Mapping) else None
status = "FAIL"
error_code: str | None = None
try:
if not isinstance(request, Mapping):
raise SignerBrokerServiceError("REQUEST_OBJECT_REQUIRED")
response, operation, payload_hash = self._dispatch(request, peer_uid)
status = "PASS"
except SignerBrokerServiceError as exc:
error_code = exc.code
response = self._failure(exc.code)
except Exception:
error_code = "SIGNER_INTERNAL_ERROR"
response = self._failure(error_code)
try:
self._audit(peer_uid=peer_uid, operation=operation, status=status, error_code=error_code, payload_sha256=payload_hash if isinstance(payload_hash, str) else None)
except SignerBrokerServiceError as exc:
response = self._failure(exc.code)
return response
def handle_json_line(self, raw: bytes, peer_uid: int) -> dict[str, Any]:
if len(raw) > MAX_REQUEST_BYTES:
return self.handle_request({"operation": "invalid"}, peer_uid) | {"error_code": "REQUEST_TOO_LARGE", "status": "FAIL"}
try:
request = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
return self.handle_request({"operation": "invalid"}, peer_uid) | {"error_code": "REQUEST_JSON_INVALID", "status": "FAIL"}
return self.handle_request(request, peer_uid)
class _UnixRequestHandler(socketserver.StreamRequestHandler):
def handle(self) -> None:
service: SignerBrokerService = self.server.service # type: ignore[attr-defined]
response = service.handle_json_line(self.rfile.readline(MAX_REQUEST_BYTES + 1), _peer_uid(self.request))
self.wfile.write((json.dumps(response, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8"))
self.wfile.flush()
class SignerBrokerUnixServer(socketserver.ThreadingMixIn, socketserver.UnixStreamServer):
daemon_threads = True
allow_reuse_address = True
def __init__(self, socket_path: Path, service: SignerBrokerService):
self.socket_path = Path(socket_path)
self.service = service
super().__init__(str(self.socket_path), _UnixRequestHandler)
def server_close(self) -> None:
super().server_close()
try:
if self.socket_path.exists() and stat.S_ISSOCK(self.socket_path.stat().st_mode):
self.socket_path.unlink()
except OSError:
pass
def _peer_uid(channel: socket.socket) -> int:
if not hasattr(socket, "SO_PEERCRED"):
return -1
try:
_pid, uid, _gid = struct.unpack("3i", channel.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i")))
return int(uid)
except (OSError, struct.error):
return -1
def build_server(policy: SignerBrokerPolicy, runner: SignerRunner | None = None) -> SignerBrokerUnixServer:
socket_path = policy.socket_path
socket_path.parent.mkdir(parents=True, exist_ok=True)
if socket_path.exists():
if not stat.S_ISSOCK(socket_path.stat().st_mode):
raise SignerBrokerServiceError("SIGNER_BROKER_SOCKET_CONFLICT")
socket_path.unlink()
server = SignerBrokerUnixServer(socket_path, SignerBrokerService(policy, runner))
os.chmod(socket_path, 0o660)
return server
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="AIOS fixed-function signer broker")
parser.add_argument("command", choices=("serve",))
parser.add_argument("--socket", default=str(DEFAULT_SOCKET_PATH))
parser.add_argument("--signer", default=str(DEFAULT_SIGNER_PATH))
parser.add_argument("--audit", default=str(DEFAULT_AUDIT_PATH))
parser.add_argument("--state-root", default=str(DEFAULT_STATE_ROOT))
parser.add_argument("--allowed-uid", action="append", type=int, required=True)
parser.add_argument("--timeout", type=float, default=60.0)
return parser
def main(argv: Sequence[str] | None = None) -> int:
args = _parser().parse_args(argv)
policy = SignerBrokerPolicy(socket_path=Path(args.socket), signer_path=Path(args.signer), audit_path=Path(args.audit), state_root=Path(args.state_root), allowed_uids=frozenset(args.allowed_uid), signer_timeout=args.timeout)
server = build_server(policy)
try:
server.serve_forever()
finally:
server.server_close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
+445 -39
View File
@@ -31,6 +31,8 @@ import time
from pathlib import Path
from typing import Any, Mapping, Sequence
from version_registry import configured_authoritative_git_dir, verify_authoritative_repository
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
PLAN_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
@@ -49,6 +51,11 @@ REQUIRED_V2R1C7_MEMBERS = {
"payload/ARTIFACT-MANIFEST.json",
"payload/ROLLBACK-METADATA.json",
}
REQUIRED_V2R2C1_MEMBERS = {
"payload/versions/V2R2C1.json",
}
V2R2C1_TAG_OID = "07fc2746b688f5f243f29b39de39f32edc3dd9d2"
DEFAULT_AUTHORITATIVE_GIT_DIR = Path("/data/aios-gitops/repo.git")
class ReleaseBlocked(RuntimeError):
pass
@@ -72,6 +79,13 @@ class Binding:
production_service: str
production_port: int
repository_identity: str = "aios/aios"
# Legacy V2R2C1 receipts intentionally omit tag_oid. Future release
# bindings must provide it explicitly and may use a versioned schema.
tag_oid: str | None = None
binding_schema: str | None = None
source_registry_main_oid: str | None = None
source_registry_path: str | None = None
source_registry_sha256: str | None = None
def __post_init__(self) -> None:
if not self.version or not OID_RE.fullmatch(self.target_oid):
@@ -90,6 +104,32 @@ class Binding:
raise ValueError("invalid production port")
if self.repository_identity != "aios/aios":
raise ValueError("unexpected repository identity")
if self.tag_oid is not None and not OID_RE.fullmatch(self.tag_oid):
raise ValueError("invalid annotated tag object OID")
if self.binding_schema is not None and not self.binding_schema.startswith(
"aios.release-binding."
):
raise ValueError("invalid release binding schema")
provenance = (
self.source_registry_main_oid,
self.source_registry_sha256,
)
if any(item is not None for item in provenance):
if not all(item is not None for item in provenance):
raise ValueError("source registry provenance must be complete")
if not OID_RE.fullmatch(str(self.source_registry_main_oid)):
raise ValueError("invalid source registry main OID")
if not SHA_RE.fullmatch(str(self.source_registry_sha256)):
raise ValueError("invalid source registry SHA-256")
path = self.source_registry_path or f"versions/{self.version}.json"
candidate = Path(path)
if (
candidate.is_absolute()
or ".." in candidate.parts
or "\\" in path
or not path.endswith(".json")
):
raise ValueError("invalid source registry path")
@property
def tag(self) -> str:
@@ -108,10 +148,22 @@ class Binding:
"production_port": self.production_port,
"repository_identity": self.repository_identity,
}
if self.tag_oid is not None:
payload["tag_oid"] = self.tag_oid
payload["binding_schema"] = self.binding_schema or "aios.release-binding.v2"
if self.source_registry_main_oid is not None:
payload.update(
{
"source_registry_main_oid": self.source_registry_main_oid,
"source_registry_path": self.source_registry_path
or f"versions/{self.version}.json",
"source_registry_sha256": self.source_registry_sha256,
}
)
return hashlib.sha256(canonical_json(payload)).hexdigest()
def request(self, operation: str) -> dict[str, Any]:
return {
request = {
"schema": "aios.production-executor-request.v1",
"operation": operation,
"binding_fingerprint": self.fingerprint,
@@ -127,6 +179,19 @@ class Binding:
"production_port": self.production_port,
"repository_identity": self.repository_identity,
}
if self.tag_oid is not None:
request["tag_oid"] = self.tag_oid
request["binding_schema"] = self.binding_schema or "aios.release-binding.v2"
if self.source_registry_main_oid is not None:
request.update(
{
"source_registry_main_oid": self.source_registry_main_oid,
"source_registry_path": self.source_registry_path
or f"versions/{self.version}.json",
"source_registry_sha256": self.source_registry_sha256,
}
)
return request
def canonical_json(value: object) -> bytes:
@@ -168,6 +233,23 @@ def run_git(git_dir: Path, *args: str, check: bool = True) -> subprocess.Complet
)
def run_git_bytes(
git_dir: Path,
*args: str,
check: bool = True,
) -> subprocess.CompletedProcess[bytes]:
completed = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
check=False,
capture_output=True,
timeout=30,
env={"PATH": os.environ.get("PATH", "")},
)
if check and completed.returncode != 0:
raise ReleaseBlocked(completed.stderr.decode("utf-8", "replace").strip())
return completed
def verify_git_lineage(git_dir: Path, binding: Binding) -> dict[str, Any]:
authority_main = run_git(git_dir, "rev-parse", "refs/heads/main").stdout.strip()
if not OID_RE.fullmatch(authority_main):
@@ -209,7 +291,45 @@ def verify_git_lineage(git_dir: Path, binding: Binding) -> dict[str, Any]:
if not target_in_evidence:
raise ReleaseBlocked("approved target is not an ancestor of evidence closure")
return {
source_main = None
if binding.source_registry_main_oid is not None:
source_main = binding.source_registry_main_oid
source_type = run_git(git_dir, "cat-file", "-t", source_main).stdout.strip()
if source_type != "commit":
raise ReleaseBlocked("source_registry_main_oid is not a commit")
target_in_source_main = run_git(
git_dir,
"merge-base",
"--is-ancestor",
binding.target_oid,
source_main,
check=False,
).returncode == 0
if not target_in_source_main:
raise ManualOwnerReviewRequired(
"approved target is not an ancestor of source registry main"
)
expected_tag_oid = binding.tag_oid
if expected_tag_oid is None and binding.version == "V2R2C1":
expected_tag_oid = V2R2C1_TAG_OID
if expected_tag_oid is not None:
local_tag = run_git(
git_dir,
"rev-parse",
"--verify",
f"refs/tags/{binding.version}",
).stdout.strip()
if local_tag != expected_tag_oid:
raise ManualOwnerReviewRequired(
"future release binding tag object differs from local tag"
)
if run_git(git_dir, "cat-file", "-t", local_tag).stdout.strip() != "tag":
raise ReleaseBlocked("release tag is not annotated")
peeled = run_git(git_dir, "rev-parse", "--verify", f"{local_tag}^{{commit}}").stdout.strip()
if peeled != binding.target_oid:
raise ManualOwnerReviewRequired("release tag peeled target mismatch")
result = {
"authority_main_oid": authority_main,
"target_oid": binding.target_oid,
"target_is_ancestor_of_main": True,
@@ -217,9 +337,231 @@ def verify_git_lineage(git_dir: Path, binding: Binding) -> dict[str, Any]:
"evidence_object_type": evidence_type,
"target_is_ancestor_of_evidence": True,
}
if source_main is not None:
result["source_registry_main_oid"] = source_main
result["target_is_ancestor_of_source_registry_main"] = True
return result
def verify_artifact(binding: Binding) -> dict[str, Any]:
def _source_registry_git_provenance(
git_dir: Path,
binding: Binding,
) -> dict[str, Any] | None:
"""Read M:path and C:path independently of the artifact metadata."""
if binding.source_registry_main_oid is None:
return None
verify_authoritative_repository(git_dir)
source_main = binding.source_registry_main_oid
source_path = binding.source_registry_path or f"versions/{binding.version}.json"
authority_main = run_git(git_dir, "rev-parse", "--verify", "refs/heads/main").stdout.strip()
if run_git(git_dir, "cat-file", "-t", source_main).stdout.strip() != "commit":
raise ReleaseBlocked("source_registry_main_oid is not a commit")
if run_git(git_dir, "cat-file", "-t", authority_main).stdout.strip() != "commit":
raise ReleaseBlocked("Authority main OID is not a commit")
if run_git(
git_dir,
"merge-base",
"--is-ancestor",
source_main,
authority_main,
check=False,
).returncode != 0:
raise ManualOwnerReviewRequired(
"source registry main is not an ancestor of current Authority main"
)
source_blob = run_git_bytes(git_dir, "show", f"{source_main}:{source_path}").stdout
current_blob = run_git_bytes(git_dir, "show", f"{authority_main}:{source_path}").stdout
if source_blob != current_blob:
raise ManualOwnerReviewRequired(
"current Authority main source registry entry differs from pinned M"
)
try:
source = json.loads(source_blob.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ReleaseBlocked("Git source registry entry is invalid") from exc
expected_tag_oid = binding.tag_oid
if expected_tag_oid is None and binding.version == "V2R2C1":
expected_tag_oid = V2R2C1_TAG_OID
expected = {
"version": binding.version,
"repository": "aios/aios",
"tag_oid": expected_tag_oid,
"target_oid": binding.target_oid,
"peeled_target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"approval_plan_id": binding.approval_plan_id,
}
if not isinstance(source, dict) or source.get("schema") not in {
"aios.source-registry.v1",
"aios.production-registry.v2",
}:
raise ReleaseBlocked("Git source registry schema is invalid")
if expected_tag_oid is None or any(source.get(key) != value for key, value in expected.items()):
raise ReleaseBlocked("Git source registry identity mismatch")
if source.get("tag_object") != expected_tag_oid:
raise ReleaseBlocked("Git source registry tag object mismatch")
digest = hashlib.sha256(source_blob).hexdigest()
if digest != binding.source_registry_sha256:
raise ReleaseBlocked("Git source registry SHA-256 mismatch")
return {
"source_registry_main_oid": source_main,
"current_main_oid": authority_main,
"source_registry_path": source_path,
"source_registry_bytes": source_blob,
"source_registry_sha256": digest,
"source_registry_identity_verified": True,
"source_registry_main_converged": True,
}
def _verify_artifact_members(
path: Path,
required: set[str],
binding: Binding | None = None,
git_provenance: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
"""Verify archive safety and independently verify packaged release data."""
# Python tarfile may not have zstd support on the target host, so
# decompress into a private temporary tar and inspect it there.
with tempfile.TemporaryDirectory(prefix="aios-artifact-verify-") as temp_dir:
tar_path = Path(temp_dir) / "release.tar"
with tar_path.open("wb") as output:
completed = subprocess.run(
["zstd", "-dc", str(path)],
check=False,
stdout=output,
stderr=subprocess.PIPE,
timeout=60,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise ReleaseBlocked("artifact zstd decompression failed")
import tarfile
with tarfile.open(tar_path, "r:") as archive:
members = archive.getmembers()
names: set[str] = set()
for member in members:
candidate = Path(member.name)
if (
candidate.is_absolute()
or ".." in candidate.parts
or member.issym()
or member.islnk()
or not (member.isfile() or member.isdir())
):
raise ReleaseBlocked(f"unsafe artifact member: {member.name}")
if member.name in names:
raise ReleaseBlocked(f"duplicate artifact member: {member.name}")
names.add(member.name)
source_path = None
if binding is not None and binding.version != "V2R1C7":
source_path = binding.source_registry_path or f"versions/{binding.version}.json"
required_members = set(required)
if binding is not None and binding.source_registry_main_oid is not None:
required_members.add("payload/BUILD-METADATA.json")
if source_path is not None and binding is not None:
required_members.add(f"payload/{source_path}")
missing = sorted(required_members - names)
if missing:
raise ReleaseBlocked(
"artifact missing required members: " + ",".join(missing)
)
verification: dict[str, Any] = {}
if binding is not None and source_path is not None:
member_name = f"payload/{source_path}"
member = archive.getmember(member_name)
stream = archive.extractfile(member)
if stream is None:
raise ReleaseBlocked("source registry member is unreadable")
raw = stream.read()
try:
source = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ReleaseBlocked("source registry member is invalid") from exc
canonical_source = (
json.dumps(source, ensure_ascii=False, sort_keys=True, indent=2) + "\n"
).encode("utf-8")
if raw != canonical_source:
raise ReleaseBlocked("source registry member is not canonical JSON")
expected_tag_oid = binding.tag_oid
if expected_tag_oid is None and binding.version == "V2R2C1":
expected_tag_oid = V2R2C1_TAG_OID
expected = {
"version": binding.version,
"repository": "aios/aios",
"tag_oid": expected_tag_oid,
"target_oid": binding.target_oid,
"peeled_target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"approval_plan_id": binding.approval_plan_id,
}
if expected_tag_oid is None:
raise ReleaseBlocked("future release binding tag object is required")
if not isinstance(source, dict) or any(
source.get(key) != value for key, value in expected.items()
):
raise ReleaseBlocked("packaged source registry identity mismatch")
if source.get("tag_object") != expected_tag_oid:
raise ReleaseBlocked("packaged source registry tag object mismatch")
source_digest = hashlib.sha256(raw).hexdigest()
if git_provenance is not None:
if raw != git_provenance["source_registry_bytes"]:
raise ReleaseBlocked(
"packaged source registry differs from Git M:path"
)
if source_digest != git_provenance["source_registry_sha256"]:
raise ReleaseBlocked(
"packaged source registry SHA-256 differs from Git M:path"
)
verification.update(
{
"source_registry_path": source_path,
"source_registry_sha256": source_digest,
"source_registry_identity_verified": True,
}
)
if binding.source_registry_sha256 is not None:
if source_digest != binding.source_registry_sha256:
raise ReleaseBlocked("packaged source registry SHA-256 mismatch")
metadata_member = archive.getmember("payload/BUILD-METADATA.json")
metadata_stream = archive.extractfile(metadata_member)
if metadata_stream is None:
raise ReleaseBlocked("BUILD-METADATA.json is unreadable")
try:
metadata = json.loads(metadata_stream.read().decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ReleaseBlocked("BUILD-METADATA.json is invalid") from exc
metadata_expected = {
"release_target_oid": binding.target_oid,
"source_registry_main_oid": binding.source_registry_main_oid,
"source_registry_path": source_path,
"source_registry_sha256": binding.source_registry_sha256,
}
if not isinstance(metadata, dict) or any(
metadata.get(key) != value
for key, value in metadata_expected.items()
):
raise ReleaseBlocked("BUILD-METADATA source provenance mismatch")
verification["build_metadata_provenance_verified"] = True
if git_provenance is not None:
verification.update(
{
"git_source_registry_member_verified": True,
"git_source_registry_main_converged": True,
"git_source_registry_sha256": git_provenance[
"source_registry_sha256"
],
}
)
return verification
def verify_artifact(
binding: Binding,
*,
repository: Path | str | None = None,
) -> dict[str, Any]:
path = Path(binding.artifact_path)
if path.stat().st_size != binding.artifact_size:
raise ReleaseBlocked("artifact size mismatch")
@@ -227,46 +569,32 @@ def verify_artifact(binding: Binding) -> dict[str, Any]:
if actual != binding.artifact_sha256:
raise ReleaseBlocked("artifact SHA-256 mismatch")
if binding.version == "V2R1C7":
# Python tarfile may not have zstd support on the target host, so
# decompress into a private temporary tar and inspect it there.
with tempfile.TemporaryDirectory(prefix="aios-artifact-verify-") as temp_dir:
tar_path = Path(temp_dir) / "release.tar"
with tar_path.open("wb") as output:
completed = subprocess.run(
["zstd", "-dc", str(path)],
check=False,
stdout=output,
stderr=subprocess.PIPE,
timeout=60,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise ReleaseBlocked("artifact zstd decompression failed")
import tarfile
with tarfile.open(tar_path, "r:") as archive:
names: set[str] = set()
for member in archive.getmembers():
candidate = Path(member.name)
if (
candidate.is_absolute()
or ".." in candidate.parts
or member.issym()
or member.islnk()
or not (member.isfile() or member.isdir())
):
raise ReleaseBlocked(f"unsafe artifact member: {member.name}")
names.add(member.name)
missing = sorted(REQUIRED_V2R1C7_MEMBERS - names)
if missing:
raise ReleaseBlocked(
"artifact missing required members: " + ",".join(missing)
)
required = {
"V2R1C7": REQUIRED_V2R1C7_MEMBERS,
"V2R2C1": REQUIRED_V2R2C1_MEMBERS,
}.get(binding.version)
member_verification: dict[str, Any] = {}
git_provenance = None
if binding.source_registry_main_oid is not None:
git_provenance = _source_registry_git_provenance(
Path(repository) if repository is not None else Path(
configured_authoritative_git_dir()
),
binding,
)
if required or binding.source_registry_main_oid is not None:
member_verification = _verify_artifact_members(
path,
required or set(),
binding,
git_provenance,
)
return {
"artifact_path": str(path),
"artifact_sha256": actual,
"artifact_size": binding.artifact_size,
"verified": True,
**member_verification,
}
@@ -282,6 +610,18 @@ def verify_authorization(receipt: Mapping[str, Any], binding: Binding) -> dict[s
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
}
if binding.tag_oid is not None:
expected["tag_oid"] = binding.tag_oid
expected["binding_schema"] = binding.binding_schema or "aios.release-binding.v2"
if binding.source_registry_main_oid is not None:
expected.update(
{
"source_registry_main_oid": binding.source_registry_main_oid,
"source_registry_path": binding.source_registry_path
or f"versions/{binding.version}.json",
"source_registry_sha256": binding.source_registry_sha256,
}
)
for key, value in expected.items():
if receipt.get(key) != value:
raise ManualOwnerReviewRequired(f"Owner authorization mismatch: {key}")
@@ -433,13 +773,29 @@ def tag_step(command: Sequence[str], binding: Binding) -> dict[str, Any]:
tag_oid = probe.get("tag_oid")
if not isinstance(tag_oid, str) or not OID_RE.fullmatch(tag_oid):
raise ReleaseBlocked("verified annotated tag object OID missing")
if binding.tag_oid is not None and tag_oid != binding.tag_oid:
raise ManualOwnerReviewRequired(
"same-name tag object differs from the future release binding"
)
return dict(probe)
def deploy_step(command: Sequence[str], binding: Binding) -> dict[str, Any]:
def require_runtime_registry(result: Mapping[str, Any]) -> None:
if binding.source_registry_main_oid is None:
return
if (
result.get("runtime_bundle_contains_current_version") is not True
or result.get("runtime_registry_converged") is not True
):
raise ReconciliationRequired(
"runtime bundle/runtime registry convergence was not independently verified"
)
probe = run_adapter(command, binding.request("probe"))
require_exact_response(probe, binding)
if probe.get("status") == "ACTIVE_VERIFIED":
require_runtime_registry(probe)
return dict(probe)
if probe.get("status") == "CONFLICT":
raise ManualOwnerReviewRequired("production runtime has conflicting binding")
@@ -458,9 +814,30 @@ def deploy_step(command: Sequence[str], binding: Binding) -> dict[str, Any]:
or probe.get("production_deployed") is not True
):
raise ReconciliationRequired("production health contract is not ACTIVE")
require_runtime_registry(probe)
return dict(probe)
def verify_post_deploy_source_registry(
repository: Path,
binding: Binding,
) -> dict[str, Any]:
"""Re-read authoritative Git after runtime deployment and before closure."""
if binding.source_registry_main_oid is None:
return {
"source_registry_converged": "not_required",
"source_registry_check": "not_required_for_legacy_binding",
}
provenance = _source_registry_git_provenance(repository, binding)
if provenance is None:
raise ReconciliationRequired("authoritative source registry was not checked")
return {
**provenance,
"source_registry_converged": True,
"source_registry_check": "post_deploy_authoritative_git_read",
}
def finalize_step(
command: Sequence[str],
binding: Binding,
@@ -501,7 +878,7 @@ def execute(
) -> dict[str, Any]:
authorization = verify_authorization(secure_json(authorization_path), binding)
lineage = verify_git_lineage(repository, binding)
artifact = verify_artifact(binding)
artifact = verify_artifact(binding, repository=repository)
state = DurableState(state_root, binding)
with FileLock(state_root / "executor.lock"):
state.record(
@@ -518,6 +895,13 @@ def execute(
runtime = deploy_step(deploy_command, binding)
state.record("RUNTIME_VERIFIED", "runtime", runtime)
# The runtime adapter proves only runtime facts. Re-read M:path
# and C:path from authoritative Git immediately before Registry
# and Baseline closure so a post-preflight main change cannot be
# mistaken for source convergence.
source_registry = verify_post_deploy_source_registry(repository, binding)
state.record("SOURCE_REGISTRY_VERIFIED", "source_registry", source_registry)
registry = finalize_step(
registry_command,
binding,
@@ -547,10 +931,21 @@ def execute(
require_exact_response(value, binding)
if final_runtime.get("status") != "ACTIVE_VERIFIED":
raise ReconciliationRequired("final runtime readback failed")
if binding.source_registry_main_oid is not None and not all(
final_runtime.get(name) is True
for name in (
"runtime_bundle_contains_current_version",
"runtime_registry_converged",
)
):
raise ReconciliationRequired(
"final runtime registry convergence readback failed"
)
if final_registry.get("status") != "VERIFIED":
raise ReconciliationRequired("final Registry readback failed")
if final_baseline.get("status") != "VERIFIED":
raise ReconciliationRequired("final Baseline readback failed")
final_source_registry = verify_post_deploy_source_registry(repository, binding)
final = {
"status": "PRODUCTION_ACCEPTED",
"version": binding.version,
@@ -561,6 +956,7 @@ def execute(
"tag_oid": tag["tag_oid"],
"tag": final_tag,
"runtime": final_runtime,
"source_registry": final_source_registry,
"registry": final_registry,
"baseline": final_baseline,
"force_push_used": False,
@@ -600,6 +996,11 @@ def parser() -> argparse.ArgumentParser:
p.add_argument("--evidence-closure-oid", required=True)
p.add_argument("--artifact-sha256", required=True)
p.add_argument("--approval-plan-id", required=True)
p.add_argument("--tag-oid")
p.add_argument("--binding-schema")
p.add_argument("--source-registry-main-oid")
p.add_argument("--source-registry-path")
p.add_argument("--source-registry-sha256")
p.add_argument("--production-service", required=True)
p.add_argument("--production-port", required=True, type=int)
p.add_argument("--repository-identity", default="aios/aios")
@@ -623,6 +1024,11 @@ def main() -> int:
production_service=args.production_service,
production_port=args.production_port,
repository_identity=args.repository_identity,
tag_oid=args.tag_oid,
binding_schema=args.binding_schema,
source_registry_main_oid=args.source_registry_main_oid,
source_registry_path=args.source_registry_path,
source_registry_sha256=args.source_registry_sha256,
)
try:
result = execute(
@@ -951,3 +951,51 @@ class AuthorizedTaskWriterProvisioner:
return {**public_result, "prestart": prestart}
finally:
shutil.rmtree(temporary_root, ignore_errors=True)
# v2 continuation validation is intentionally separate from the legacy Owner
# bootstrap path above. It consumes a recovered campaign authorization and
# never creates a branch or asks the Owner to authorize again.
from aios_continuation_lease import WriterLeaseRequestV2
def validate_writer_lease_request_v2(raw: Mapping[str, Any], probe: Any) -> dict[str, Any]:
"""Validate a CONTINUE_EXISTING request against the live exact branch head."""
required = {
"schema",
"task_id",
"repository",
"branch_mode",
"base_oid",
"expected_branch_oid",
"target_branch",
"generation",
"lease_seconds",
}
if set(raw) != required or raw.get("schema") not in WriterLeaseRequestV2.accepted_schemas:
raise ProvisioningError("LEASE_REQUEST_V2_FIELDS_REJECTED", 409)
request = WriterLeaseRequestV2(
task_id=str(raw["task_id"]),
repository=str(raw["repository"]),
branch_mode=str(raw["branch_mode"]),
base_oid=str(raw["base_oid"]),
expected_branch_oid=str(raw["expected_branch_oid"]),
target_branch=str(raw["target_branch"]),
generation=raw["generation"],
lease_seconds=raw["lease_seconds"],
)
request.validate(probe)
return {
"schema": WriterLeaseRequestV2.schema,
"task_id": request.task_id,
"repository": request.repository,
"branch_mode": request.branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": request.expected_branch_oid,
"target_branch": request.target_branch,
"generation": request.generation,
"lease_seconds": request.lease_seconds,
"create_target_branch": request.branch_mode == "CREATE_NEW",
"worktree_mode": "detached_at_base_oid" if request.branch_mode == "CREATE_NEW" else "detached_at_expected_head",
"cas_ref_update_required": True,
}
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Single canonical Production Baseline authority for the GitOps reader.
The API and the one-shot writer import this module. Workspace and release
copies are projections only; they are never selected as a fallback source.
"""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
from typing import Any
AUTHORITY_PATH = Path(os.getenv(
"AIOS_GITOPS_BASELINE_AUTHORITY",
"/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
))
AUTHORITY_SHA_PATH = AUTHORITY_PATH.with_suffix(".sha256")
class BaselineAuthorityError(RuntimeError):
"""Fail-closed error raised for an unavailable or inconsistent authority."""
class BaselineAuthority:
def __init__(self, path: Path | str = AUTHORITY_PATH):
self.path = Path(path)
self.sha_path = self.path.with_suffix(".sha256")
def _raw(self) -> bytes:
try:
raw = self.path.read_bytes()
except OSError as exc:
raise BaselineAuthorityError(f"authority_unavailable:{exc}") from exc
if not raw:
raise BaselineAuthorityError("authority_empty")
return raw
def read(self) -> dict[str, Any]:
raw = self._raw()
digest = hashlib.sha256(raw).hexdigest()
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BaselineAuthorityError("authority_invalid_json") from exc
if not isinstance(value, dict):
raise BaselineAuthorityError("authority_not_object")
expected = str(value.get("authority_sha256", ""))
if expected and expected != digest:
raise BaselineAuthorityError("authority_embedded_digest_mismatch")
if self.sha_path.exists():
sidecar = self.sha_path.read_text(encoding="utf-8").strip().split()[0]
if sidecar != digest:
raise BaselineAuthorityError("authority_sidecar_digest_mismatch")
required = {
"current_baseline", "previous_baseline", "target_oid", "tag_oid",
"evidence_closure_oid", "approval_plan_id", "status", "generation",
"source_identity",
}
missing = sorted(required.difference(value))
if missing:
raise BaselineAuthorityError("authority_missing:" + ",".join(missing))
value = dict(value)
value["authority_sha256"] = digest
value["authority_path"] = str(self.path)
return value
def read_consistent(self, policy: dict[str, Any] | None = None) -> dict[str, Any]:
value = self.read()
if policy:
expected = policy.get("release_binding", policy)
for key, field in (("version", "current_baseline"),
("previous_version", "previous_baseline"),
("target_oid", "target_oid"),
("tag_oid", "tag_oid"),
("evidence_closure_oid", "evidence_closure_oid"),
("approval_plan_id", "approval_plan_id")):
wanted = expected.get(key)
if wanted is not None and value.get(field) != wanted:
raise BaselineAuthorityError(f"authority_binding_mismatch:{field}")
return value
+108
View File
@@ -0,0 +1,108 @@
"""Atomic, predicate-bound activation of the durable release campaign."""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import re
import tempfile
from typing import Any, Mapping
from release_campaign_authorization import CampaignError, validate_policy
OID_RE = re.compile(r"^[0-9a-f]{40}$")
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
class CampaignActivationError(CampaignError):
pass
def canonical_json(value: Any) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n").encode("utf-8")
def digest_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def build_active_policy(current: Mapping[str, Any], facts: Mapping[str, Any]) -> dict[str, Any]:
policy = validate_policy(dict(current))
required = {
"campaign_id",
"repository",
"source_main_oid",
"gitea_main_oid",
"authority_main_oid",
"runtime_source_oid",
"durable_job_state",
"writer_ready",
"gitea_machine_authenticated",
"governance_executor_ready",
"preapproval_publisher_ready",
"runtime_exact_main",
"main_lineage_verified",
"campaign_revoked",
}
if set(facts) != required:
raise CampaignActivationError("ACTIVATION_FACTS_SCHEMA_INVALID")
if policy["activation_state"] != "PENDING_CONTROL_PLANE_MERGE" or policy["active"] is not False:
raise CampaignActivationError("CAMPAIGN_NOT_PENDING")
if policy["campaign_id"] != CAMPAIGN_ID or facts["campaign_id"] != CAMPAIGN_ID:
raise CampaignActivationError("CAMPAIGN_ID_MISMATCH")
if facts["repository"] != "aios/aios" or policy["repository"] != facts["repository"]:
raise CampaignActivationError("REPOSITORY_MISMATCH")
for name in ("source_main_oid", "gitea_main_oid", "authority_main_oid", "runtime_source_oid"):
if not isinstance(facts[name], str) or not OID_RE.fullmatch(facts[name]):
raise CampaignActivationError("ACTIVATION_OID_INVALID")
if not (
facts["source_main_oid"]
== facts["gitea_main_oid"]
== facts["authority_main_oid"]
== facts["runtime_source_oid"]
):
raise CampaignActivationError("ACTIVATION_MAIN_DIVERGENCE")
if facts["durable_job_state"] != "COMPLETED":
raise CampaignActivationError("DURABLE_JOB_NOT_COMPLETED")
boolean_fields = (
"writer_ready",
"gitea_machine_authenticated",
"governance_executor_ready",
"preapproval_publisher_ready",
"runtime_exact_main",
"main_lineage_verified",
)
if any(facts[name] is not True for name in boolean_fields) or facts["campaign_revoked"] is not False:
raise CampaignActivationError("ACTIVATION_PREDICATE_FAILED")
activated = dict(policy)
activated["activation_state"] = "ACTIVE"
activated["active"] = True
return validate_policy(activated)
def atomic_write_policy(path: Path, sidecar: Path, receipt_path: Path, policy: Mapping[str, Any], receipt: Mapping[str, Any]) -> str:
if path.is_symlink() or sidecar.is_symlink() or receipt_path.exists() or receipt_path.is_symlink():
raise CampaignActivationError("ACTIVATION_PATH_INVALID")
path.parent.mkdir(parents=True, exist_ok=True)
raw = canonical_json(dict(policy))
digest = digest_bytes(raw)
for target, data, mode in (
(path, raw, "wb"),
(sidecar, f"{digest} {path.name}\n".encode("ascii"), "wb"),
(receipt_path, canonical_json(dict(receipt)), "wb"),
):
fd, temporary = tempfile.mkstemp(prefix=f".{target.name}.", dir=target.parent)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, mode) as stream:
stream.write(data)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
os.chmod(target, 0o600)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
return digest
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Machine campaign approval gate for protected engineering PRs."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import subprocess
from typing import Any, Mapping
from release_campaign_authorization import CampaignAuthorization, CampaignError
class GateError(RuntimeError):
pass
def git_is_ancestor(repository: Path, ancestor: str, descendant: str) -> bool:
result = subprocess.run(
[
"git",
f"--git-dir={repository}",
"merge-base",
"--is-ancestor",
ancestor,
descendant,
],
check=False,
capture_output=True,
)
if result.returncode == 0:
return True
if result.returncode == 1:
return False
raise GateError("git_ancestor_check_failed")
def evaluate(
campaign: CampaignAuthorization,
facts: Mapping[str, Any],
repository: Path,
) -> dict[str, Any]:
required = {
"repository",
"pr_number",
"base_ref",
"base_oid",
"head_oid",
"required_checks_all_success",
"required_checks_same_head",
"unresolved_conversations",
"mergeable",
"protected_target",
}
if set(facts) != required:
raise GateError("pr_facts_fields_invalid")
if facts["repository"] != "aios/aios":
raise GateError("repository_mismatch")
if facts["base_ref"] != "refs/heads/main" or facts["protected_target"] != "main":
raise GateError("protected_main_required")
if facts["required_checks_all_success"] is not True:
raise GateError("required_checks_not_success")
if facts["required_checks_same_head"] is not True:
raise GateError("required_checks_stale")
if facts["unresolved_conversations"] != 0:
raise GateError("unresolved_conversations")
if facts["mergeable"] is not True:
raise GateError("pr_not_mergeable")
if not git_is_ancestor(repository, campaign.anchor_oid, str(facts["base_oid"])):
raise GateError("base_outside_campaign_lineage")
if not git_is_ancestor(repository, str(facts["base_oid"]), str(facts["head_oid"])):
raise GateError("head_outside_pr_lineage")
campaign.assert_operation("protected_merge")
return {
"schema": "aios.campaign-approval-gate-result.v1",
"status": "SUCCESS",
"campaign_id": campaign.campaign_id,
"pr_number": facts["pr_number"],
"head_oid": facts["head_oid"],
"owner_ui_interaction_required": False,
}
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--campaign", type=Path, required=True)
p.add_argument("--revocation", type=Path)
p.add_argument("--pr-facts", type=Path, required=True)
p.add_argument("--repository", type=Path, required=True)
a = p.parse_args()
campaign = CampaignAuthorization.load(
a.campaign,
revocation=a.revocation,
require_active=True,
)
facts = json.loads(a.pr_facts.read_text(encoding="utf-8"))
print(json.dumps(evaluate(campaign, facts, a.repository), sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignError, GateError) as exc:
print(json.dumps({"status": "FAILURE", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""Automatically accept an exact release binding under an ACTIVE campaign."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import stat
import tempfile
from typing import Any, Mapping
from release_campaign_authorization import CampaignAuthorization, CampaignError
class AcceptanceError(RuntimeError):
pass
def secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise AcceptanceError(f"invalid_file:{path}")
st = path.lstat()
if st.st_uid != 0 or stat.S_IMODE(st.st_mode) & 0o022:
raise AcceptanceError(f"unsafe_file:{path}")
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise AcceptanceError(f"json_object_required:{path}")
return value
def atomic_new(path: Path, value: Mapping[str, Any]) -> None:
if path.exists() or path.is_symlink():
raise AcceptanceError("acceptance_receipt_exists")
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
if os.geteuid() == 0:
os.fchown(fd, 0, 0)
with os.fdopen(fd, "w", encoding="utf-8", closefd=True) as stream:
json.dump(dict(value), stream, sort_keys=True, indent=2)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
os.chmod(path, 0o600)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
def evaluate(
campaign: CampaignAuthorization,
binding: Mapping[str, Any],
evidence: Mapping[str, Any],
artifact: Mapping[str, Any],
staging: Mapping[str, Any],
main: Mapping[str, Any],
tag: Mapping[str, Any],
) -> dict[str, Any]:
required_binding = {
"version",
"target_oid",
"evidence_closure_oid",
"artifact_sha256",
"artifact_size",
"approval_plan_id",
}
if set(binding) != required_binding:
raise AcceptanceError("binding_fields_invalid")
if main.get("repository") != "aios/aios":
raise AcceptanceError("repository_identity_mismatch")
if main.get("target_in_main_lineage") is not True:
raise AcceptanceError("target_outside_main_lineage")
if evidence.get("object_type") != "commit":
raise AcceptanceError("evidence_not_commit")
if evidence.get("target_is_ancestor") is not True:
raise AcceptanceError("target_not_evidence_ancestor")
if evidence.get("closure_oid") != binding["evidence_closure_oid"]:
raise AcceptanceError("evidence_oid_mismatch")
if artifact.get("sha256") != binding["artifact_sha256"]:
raise AcceptanceError("artifact_digest_mismatch")
if artifact.get("size") != binding["artifact_size"]:
raise AcceptanceError("artifact_size_mismatch")
if artifact.get("retained") is not True:
raise AcceptanceError("artifact_not_retained")
if staging.get("install") != "PASS":
raise AcceptanceError("staging_install_failed")
if staging.get("health") != "PASS":
raise AcceptanceError("staging_health_failed")
if staging.get("restart") != "PASS":
raise AcceptanceError("staging_restart_failed")
if staging.get("rollback_readiness") != "PASS":
raise AcceptanceError("rollback_readiness_failed")
if tag.get("conflict") is not False:
raise AcceptanceError("tag_conflict")
campaign.assert_operation("derive_campaign_acceptance")
return {
"schema": "aios.campaign-release-acceptance.v1",
"accepted": True,
"binding_replaced": False,
"campaign_id": campaign.campaign_id,
"campaign_generation": campaign.data["generation"],
**dict(binding),
"owner_interaction_required": False,
}
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--campaign", type=Path, required=True)
p.add_argument("--revocation", type=Path)
p.add_argument("--binding", type=Path, required=True)
p.add_argument("--evidence", type=Path, required=True)
p.add_argument("--artifact", type=Path, required=True)
p.add_argument("--staging", type=Path, required=True)
p.add_argument("--main-facts", type=Path, required=True)
p.add_argument("--tag-facts", type=Path, required=True)
p.add_argument("--output", type=Path, required=True)
a = p.parse_args()
campaign = CampaignAuthorization.load(
a.campaign,
revocation=a.revocation,
require_active=True,
)
receipt = evaluate(
campaign,
secure_json(a.binding),
secure_json(a.evidence),
secure_json(a.artifact),
secure_json(a.staging),
secure_json(a.main_facts),
secure_json(a.tag_facts),
)
atomic_new(a.output, receipt)
print(json.dumps(receipt, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignError, AcceptanceError) as exc:
print(json.dumps({"status": "BLOCKED", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
+115 -14
View File
@@ -64,6 +64,40 @@ class PublicationError(RuntimeError):
pass
class PublicationValueError(PublicationError, ValueError):
def __init__(self, code: str, message: str):
self.code = code
super().__init__(message)
class InvalidReleaseVersion(PublicationValueError):
def __init__(self):
super().__init__("INVALID_RELEASE_VERSION", "invalid release version")
class GiteaConfigurationError(PublicationValueError):
def __init__(self, code: str):
super().__init__(code, code)
class GiteaUnavailableError(PublicationError):
def __init__(self, code: str = "GITEA_UNAVAILABLE"):
self.code = code
super().__init__(code)
class GiteaTagNotFound(PublicationError):
def __init__(self):
self.code = "TAG_NOT_FOUND"
super().__init__(self.code)
class TagTargetMismatch(PublicationError):
def __init__(self):
self.code = "TAG_TARGET_MISMATCH"
super().__init__(self.code)
class PublicationConflict(PublicationError):
pass
@@ -85,7 +119,7 @@ class PublicationManualOwnerReviewRequired(
def validate_version(version: str) -> str:
value = str(version or "")
if not VERSION_RE.fullmatch(value):
raise ValueError("invalid release version")
raise InvalidReleaseVersion()
return value
@@ -402,6 +436,40 @@ class GitTagBackend:
)
@dataclass(frozen=True)
class GiteaClientConfig:
api_url: str
repository: str
token_file: Path
remote: str = "gitea"
token_line: int = 1
@classmethod
def from_authoritative_environment(cls, environ: dict[str, str] | None = None) -> "GiteaClientConfig":
values = os.environ if environ is None else environ
api_url = str(values.get("AIOS_GITOPS_GITEA_API_URL", "")).strip()
repository = str(values.get("AIOS_GITOPS_GITEA_REPOSITORY", "")).strip()
token_ref = str(values.get("AIOS_GITOPS_GITEA_TOKEN_FILE", "")).strip()
if not api_url:
raise GiteaConfigurationError("GITEA_API_URL_MISSING")
if not api_url.startswith(("https://", "http://")):
raise GiteaConfigurationError("GITEA_API_URL_INVALID")
if not repository:
raise GiteaConfigurationError("GITEA_REPOSITORY_MISSING")
if repository.count("/") != 1 or repository.startswith("/"):
raise GiteaConfigurationError("GITEA_REPOSITORY_INVALID")
if not token_ref:
raise GiteaConfigurationError("GITEA_TOKEN_REFERENCE_MISSING")
token_file = Path(token_ref)
if not token_file.is_absolute():
raise GiteaConfigurationError("GITEA_TOKEN_REFERENCE_INVALID")
remote = str(values.get("AIOS_GITOPS_GITEA_REMOTE", "gitea")).strip() or "gitea"
token_line = int(str(values.get("AIOS_GITOPS_GITEA_TOKEN_LINE", "1")))
if token_line < 1:
raise GiteaConfigurationError("GITEA_TOKEN_REFERENCE_INVALID")
return cls(api_url, repository, token_file, remote, token_line)
class GiteaTagClient:
def __init__(
self,
@@ -411,23 +479,37 @@ class GiteaTagClient:
timeout: float = 15,
token_line: int = 1,
):
self.api_url = api_url.rstrip("/")
self.repository = repository.strip("/")
self.token_file = token_file
self.api_url = str(api_url or "").rstrip("/")
self.repository = str(repository or "").strip("/")
self.token_file = Path(token_file)
self.timeout = timeout
self.token_line = token_line
if not self.api_url:
raise GiteaConfigurationError("GITEA_API_URL_MISSING")
if not self.api_url.startswith(("https://", "http://")):
raise ValueError("Gitea API URL must be HTTP(S)")
raise GiteaConfigurationError("GITEA_API_URL_INVALID")
if not self.repository:
raise GiteaConfigurationError("GITEA_REPOSITORY_MISSING")
if self.repository.count("/") != 1:
raise ValueError("Gitea repository must be owner/name")
raise GiteaConfigurationError("GITEA_REPOSITORY_INVALID")
if not str(token_file) or not self.token_file.is_absolute():
raise GiteaConfigurationError("GITEA_TOKEN_REFERENCE_MISSING")
@classmethod
def from_authoritative_environment(cls, environ: dict[str, str] | None = None) -> "GiteaTagClient":
config = GiteaClientConfig.from_authoritative_environment(environ)
return cls(config.api_url, config.repository, config.token_file, token_line=config.token_line)
def _get(self, path: str) -> Any:
lines = self.token_file.read_text(encoding="utf-8").splitlines()
try:
lines = self.token_file.read_text(encoding="utf-8").splitlines()
except (OSError, UnicodeError) as exc:
raise GiteaConfigurationError("GITEA_TOKEN_REFERENCE_UNAVAILABLE") from exc
if self.token_line < 1 or self.token_line > len(lines):
raise RuntimeError("Gitea credential reference has no configured token line")
raise GiteaConfigurationError("GITEA_TOKEN_REFERENCE_INVALID")
token = lines[self.token_line - 1].strip()
if not token:
raise RuntimeError("Gitea token file is empty")
raise GiteaConfigurationError("GITEA_TOKEN_REFERENCE_INVALID")
request = urllib.request.Request(
f"{self.api_url}{path}",
headers={
@@ -439,10 +521,12 @@ class GiteaTagClient:
try:
with urllib.request.urlopen(request, timeout=self.timeout) as response:
return json.loads(response.read().decode("utf-8"))
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as exc:
status = getattr(exc, "code", None)
detail = f" HTTP {status}" if status else ""
raise RuntimeError(f"Gitea API unavailable{detail}") from exc
except urllib.error.HTTPError as exc:
if exc.code == 404:
raise GiteaTagNotFound() from exc
raise GiteaUnavailableError() from exc
except (urllib.error.URLError, TimeoutError, OSError) as exc:
raise GiteaUnavailableError() from exc
def repository_identity(self) -> str:
repository = self._get(f"/repos/{self.repository}")
@@ -950,6 +1034,12 @@ class TagPublisher:
listing = state.get("gitea_listing_visible") is True
expected_tag_oid = state.get("tag_oid")
registry_tag_oid = registry.get("tag_oid") or registry.get("tag_object")
target_mismatch = any(
isinstance(value, tuple)
and len(value) > 1
and value[1] not in (None, request.target_oid)
for value in (remote, api)
)
checks = {
"fingerprint": all(
baseline.get(field) == getattr(request, field)
@@ -974,7 +1064,7 @@ class TagPublisher:
and registry.get("enabled") is True,
}
status = "PASS" if all(checks.values()) else "FAIL"
return {
result = {
"version": request.version,
"status": status,
"checks": checks,
@@ -986,8 +1076,19 @@ class TagPublisher:
"gitea_listing_visible": listing,
"repository_identity": repository_identity,
}
if target_mismatch:
result["fail_closed"] = True
result["error"] = TagTargetMismatch().code
return result
except PublicationManualOwnerReviewRequired:
raise
except PublicationError as exc:
return {
"version": request.version,
"status": "UNKNOWN",
"fail_closed": True,
"error": str(getattr(exc, "code", exc)),
}
except Exception as exc:
return {
"version": request.version,
@@ -0,0 +1 @@
"""Permanent AIOS governance source components."""
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
import json
import pathlib
import socket
import time
SOCKET = '/run/aios-governance-executor/executor.sock'
STATE = pathlib.Path('/var/lib/aios-gitops/governance-executor/drift-monitor.json')
APPROVED = pathlib.Path('/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1/protection-drift/semantic-analysis.json')
def call():
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(20)
sock.connect(SOCKET)
request = {'operation': 'GET_BRANCH_PROTECTION', 'payload': {'repository': 'aios/aios', 'branch': 'main'}}
sock.sendall((json.dumps(request, separators=(',', ':')) + '\n').encode())
chunks = []
while True:
part = sock.recv(1024 * 1024)
if not part:
break
chunks.append(part)
if b'\n' in part:
break
sock.close()
return json.loads(b''.join(chunks).splitlines()[0])
def main():
try:
result = call()
if result.get('status') != 'PASS':
raise RuntimeError(str(result.get('code') or 'EXECUTOR_GET_FAILED'))
approved = json.loads(APPROVED.read_text(encoding='utf-8'))
approved_transport = approved['approved_transport_digest_from_evidence']
approved_semantic = approved['approved_semantic_digest']
transport = result['transport_digest']
semantic = result['semantic_digest']
if semantic != approved_semantic:
state = 'GOVERNANCE_POLICY_DRIFT'
elif transport != approved_transport:
state = 'GOVERNANCE_TRANSPORT_METADATA_CHANGED'
else:
state = 'NO_DRIFT'
payload = {
'schema': 'aios.governance-drift-monitor.v1',
'observed_at_unix': int(time.time()),
'status': state,
'release_blocking': False,
'approved_transport_digest': approved_transport,
'observed_transport_digest': transport,
'approved_semantic_digest': approved_semantic,
'observed_semantic_digest': semantic,
'semantic_schema': result.get('semantic_schema'),
'source': 'Dedicated Governance Executor GET_BRANCH_PROTECTION',
}
except Exception as exc:
payload = {
'schema': 'aios.governance-drift-monitor.v1',
'observed_at_unix': int(time.time()),
'status': 'MONITOR_UNAVAILABLE',
'release_blocking': False,
'error_code': str(exc),
}
STATE.parent.mkdir(parents=True, exist_ok=True)
temporary = STATE.with_suffix('.tmp')
temporary.write_text(json.dumps(payload, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n', encoding='utf-8')
temporary.replace(STATE)
STATE.chmod(0o640)
print(json.dumps(payload, sort_keys=True, separators=(',', ':'), ensure_ascii=False))
if __name__ == '__main__':
main()
+417
View File
@@ -0,0 +1,417 @@
#!/usr/bin/env python3
import base64
import copy
import hashlib
import json
import os
import pathlib
import socket
import time
import urllib.error
import urllib.parse
import urllib.request
SOURCE_GOVERNANCE = "/opt/aios/aios-control-plane/current/active-runtime/app/governance"
if SOURCE_GOVERNANCE not in __import__("sys").path:
__import__("sys").path.insert(0, SOURCE_GOVERNANCE)
from stage2_gate_migration import (
CAMPAIGN_GATE,
CAMPAIGN_ID,
OWNER_GATE,
STAGE1_SEMANTIC_SHA,
Stage2GateError,
build_target,
semantic_digest as stage2_semantic_digest,
validate_stage2_facts,
)
API = "http://127.0.0.1:3000"
REPO = "aios/aios"
SOCKET = "/run/aios-governance-executor/executor.sock"
JOURNAL = pathlib.Path("/var/lib/aios-gitops/governance-executor/executor-journal.jsonl")
RECONCILIATION_BINDING_PATH = pathlib.Path("/var/lib/aios-gitops/governance-executor/reconciliation-binding.json")
PR54_HEAD = "015185d7e5191d18c5c1596160807ac228782a0a"
PR54_REVIEW = 104
PR54_MANIFEST_SHA = "1b11ee42d53a9f6c1646e5f5ddd8ce5b7ecf301483b9f3d47a068290d541ba4b"
PR54_PREVIOUS_PROTECTION_SHA = "8b3370add21cd23c2b4f41d8d84d3eaaa09ddf0451549b72c7941bbd92d5d60a"
SEMANTIC_SCHEMA = "aios.semantic-protection.v1"
APPROVED_SEMANTIC_SHA = "ba15d512ed8c1f43e5f72c84125a3a71a7347ef59e18000bbfca24d6e69f1b19"
STAGE1_SEMANTIC_SHA = "d911e856f71de6105b8efa08e13338a448e0b6d37536332decd80c9f409ecf27"
USER_IDS = {"owner": 2, "aios-autonomy-agent": 153}
def canon(value):
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
def sha(value):
return hashlib.sha256(canon(value)).hexdigest()
def token():
value = (pathlib.Path(os.environ["CREDENTIALS_DIRECTORY"]) / "gitea-governance-executor-token").read_text().strip()
if len(value) < 16:
raise RuntimeError("EXECUTOR_CREDENTIAL_INVALID")
return value
def api(method, path, value, body=None, expected=(200,)):
raw = None if body is None else json.dumps(body, separators=(",", ":")).encode()
req = urllib.request.Request(API + path, data=raw, method=method)
req.add_header("Authorization", "token " + value)
req.add_header("Accept", "application/json")
if raw is not None:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=20) as response:
status = response.status
payload = response.read()
except urllib.error.HTTPError as exc:
if exc.code in expected:
payload = exc.read()
return exc.code, json.loads(payload) if payload else None
raise RuntimeError("GITEA_HTTP_" + str(exc.code))
if status not in expected:
raise RuntimeError("GITEA_HTTP_" + str(status))
if not payload or not payload.strip():
raise RuntimeError("GITEA_EMPTY_JSON:" + path)
try:
decoded = json.loads(payload)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RuntimeError("GITEA_INVALID_JSON:" + path) from exc
return status, decoded
def journal(event, **fields):
JOURNAL.parent.mkdir(parents=True, exist_ok=True)
record = {"schema": "aios.governance-executor-journal.v2", "event": event, "unix": int(time.time()), **fields}
with JOURNAL.open("a", encoding="utf-8") as stream:
stream.write(json.dumps(record, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n")
stream.flush()
os.fsync(stream.fileno())
def typed_json(value):
if value in (None, "null"):
return []
if isinstance(value, str):
return json.loads(value)
return value
def user_ids(names):
result = []
for name in names or []:
if name not in USER_IDS:
raise RuntimeError("UNKNOWN_GOVERNANCE_USER:" + str(name))
result.append(USER_IDS[name])
return sorted(result)
def semantic_policy(value):
policy = {
"schema_version": SEMANTIC_SCHEMA,
"repo_id": 1,
"protection_id": 2,
"branch_name": value.get("branch_name"),
"can_push": value.get("enable_push"),
"can_force_push": value.get("enable_force_push"),
"enable_merge_whitelist": value.get("enable_merge_whitelist"),
"merge_whitelist_user_ids": user_ids(value.get("merge_whitelist_usernames")),
"merge_whitelist_team_ids": [],
"required_approvals": value.get("required_approvals"),
"enable_status_check": value.get("enable_status_check"),
"status_check_contexts": sorted(value.get("status_check_contexts") or []),
"block_admin_merge_override": value.get("block_admin_merge_override"),
"block_on_official_review_requests": value.get("block_on_official_review_requests"),
"block_on_outdated_branch": value.get("block_on_outdated_branch"),
"block_on_rejected_reviews": value.get("block_on_rejected_reviews"),
"dismiss_stale_approvals": value.get("dismiss_stale_approvals"),
"ignore_stale_approvals": value.get("ignore_stale_approvals"),
"enable_approvals_whitelist": value.get("enable_approvals_whitelist"),
"approvals_whitelist_user_ids": user_ids(value.get("approvals_whitelist_username")),
"approvals_whitelist_team_ids": [],
"enable_bypass_allowlist": value.get("enable_bypass_allowlist"),
"bypass_allowlist_user_ids": user_ids(value.get("bypass_allowlist_usernames")),
"bypass_allowlist_team_ids": [],
"enable_force_push_allowlist": value.get("enable_force_push_allowlist"),
"force_push_allowlist_user_ids": user_ids(value.get("force_push_allowlist_usernames")),
"force_push_allowlist_team_ids": [],
"enable_whitelist": value.get("enable_push_whitelist"),
"whitelist_user_ids": user_ids(value.get("push_whitelist_usernames")),
"whitelist_team_ids": [],
"force_push_allowlist_deploy_keys": value.get("force_push_allowlist_deploy_keys"),
"whitelist_deploy_keys": value.get("push_whitelist_deploy_keys"),
"require_signed_commits": value.get("require_signed_commits"),
"priority": value.get("priority"),
"protected_file_patterns": value.get("protected_file_patterns"),
"unprotected_file_patterns": value.get("unprotected_file_patterns"),
}
return policy
def semantic_sha(value):
return sha(semantic_policy(value))
def verify_binding(binding, value):
required = {
"governance_request_PR", "exact_request_head", "Owner_review_id", "Owner_review_exact_head",
"manifest_sha256", "expected_previous_snapshot_sha256", "approved_delta",
"semantic_reconciliation_request_id", "semantic_reconciliation_head",
"semantic_reconciliation_review_id", "semantic_reconciliation_review_head",
"approved_semantic_digest",
}
if set(binding) != required:
raise RuntimeError("GOVERNANCE_BINDING_SCHEMA_INVALID")
if binding["governance_request_PR"] != 54 or binding["exact_request_head"] != PR54_HEAD:
raise RuntimeError("GOVERNANCE_BINDING_PR54_MISMATCH")
if binding["Owner_review_id"] != PR54_REVIEW or binding["Owner_review_exact_head"] != PR54_HEAD:
raise RuntimeError("GOVERNANCE_BINDING_REVIEW_MISMATCH")
if binding["manifest_sha256"] != PR54_MANIFEST_SHA:
raise RuntimeError("GOVERNANCE_BINDING_MANIFEST_MISMATCH")
if binding["expected_previous_snapshot_sha256"] != PR54_PREVIOUS_PROTECTION_SHA:
raise RuntimeError("GOVERNANCE_BINDING_SNAPSHOT_MISMATCH")
if binding["approved_delta"] != {"add_merge_whitelist_user_id": 153}:
raise RuntimeError("GOVERNANCE_BINDING_DELTA_DENIED")
if binding["approved_semantic_digest"] != APPROVED_SEMANTIC_SHA:
raise RuntimeError("GOVERNANCE_BINDING_SEMANTIC_DIGEST_MISMATCH")
if not RECONCILIATION_BINDING_PATH.is_file():
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_REQUIRED")
try:
reconciliation = json.loads(RECONCILIATION_BINDING_PATH.read_text(encoding="utf-8"))
except (OSError, ValueError):
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_INVALID") from None
required_reconciliation = {
"schema", "request_id", "pr_number", "request_head", "owner_review_id",
"owner_review_head", "approved_semantic_digest", "approved_delta",
}
if set(reconciliation) != required_reconciliation or reconciliation.get("schema") != "aios.semantic-reconciliation-binding.v1":
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_INVALID")
if reconciliation["approved_semantic_digest"] != APPROVED_SEMANTIC_SHA or reconciliation["approved_delta"] != binding["approved_delta"]:
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_MISMATCH")
if (
reconciliation["request_id"] != binding["semantic_reconciliation_request_id"]
or reconciliation["request_head"] != binding["semantic_reconciliation_head"]
or reconciliation["owner_review_id"] != binding["semantic_reconciliation_review_id"]
or reconciliation["owner_review_head"] != binding["semantic_reconciliation_review_head"]
):
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_MISMATCH")
_, reconciliation_pr = api("GET", f"/api/v1/repos/{REPO}/pulls/{reconciliation['pr_number']}", value)
if (
reconciliation_pr.get("state") != "open"
or reconciliation_pr.get("merged")
or reconciliation_pr.get("head", {}).get("sha") != reconciliation["request_head"]
or reconciliation_pr.get("base", {}).get("ref") != "governance/approvals"
):
raise RuntimeError("SEMANTIC_RECONCILIATION_REQUEST_STALE")
_, reconciliation_reviews = api("GET", f"/api/v1/repos/{REPO}/pulls/{reconciliation['pr_number']}/reviews", value)
reconciliation_review = next((x for x in reconciliation_reviews if x.get("id") == reconciliation["owner_review_id"]), None)
if (
not reconciliation_review
or reconciliation_review.get("user", {}).get("id") != 2
or reconciliation_review.get("state") != "APPROVED"
or reconciliation_review.get("commit_id") != reconciliation["owner_review_head"]
or reconciliation_review.get("dismissed") is not False
):
raise RuntimeError("SEMANTIC_RECONCILIATION_OWNER_REVIEW_STALE")
_, pr = api("GET", f"/api/v1/repos/{REPO}/pulls/54", value)
if pr.get("state") != "open" or pr.get("merged") or pr.get("head", {}).get("sha") != PR54_HEAD:
raise RuntimeError("PR54_APPROVAL_STALE")
_, reviews = api("GET", f"/api/v1/repos/{REPO}/pulls/54/reviews", value)
review = next((x for x in reviews if x.get("id") == PR54_REVIEW), None)
if not review or review.get("user", {}).get("id") != 2 or review.get("state") != "APPROVED" or review.get("commit_id") != PR54_HEAD or review.get("dismissed") is not False:
raise RuntimeError("PR54_APPROVAL_STALE")
manifest_path = "approvals/governance-policy/AIOS-AUTONOMOUS-MERGE-GOVERNANCE-SCOPE-EXTENSION-V1.json"
_, content = api("GET", f"/api/v1/repos/{REPO}/contents/{urllib.parse.quote(manifest_path, safe='/')}?ref={PR54_HEAD}", value)
raw = base64.b64decode("".join(content.get("content", "").split()))
if hashlib.sha256(raw).hexdigest() != PR54_MANIFEST_SHA:
raise RuntimeError("PR54_MANIFEST_STALE")
def get_protection(value, branch):
if branch not in {"main", "governance/approvals"}:
raise RuntimeError("BRANCH_DENIED")
_, protection = api("GET", f"/api/v1/repos/{REPO}/branch_protections/{urllib.parse.quote(branch, safe='')}", value)
return protection
def mutation_receipt(before, after, binding, target_semantic):
return {
"before": {"transport_digest": sha(before), "semantic_digest": semantic_sha(before)},
"approved": {
"mutation_predicate_digest": sha({"operation": "ADD_MERGE_EXECUTOR", "subject_user_id": 153, "preserve_all_unmodified_security_fields": True}),
"Owner_review_id": binding["Owner_review_id"],
"semantic_policy_digest": APPROVED_SEMANTIC_SHA,
},
"after": {"transport_digest": sha(after), "semantic_digest": semantic_sha(after)},
"delta": {"add_merge_whitelist_user_id": 153},
"target_semantic_digest": sha(target_semantic),
"unmodified_security_fields": {"verified": True},
}
def apply_stage1(binding, value):
verify_binding(binding, value)
before = get_protection(value, "main")
before_semantic = semantic_policy(before)
before_semantic_sha = sha(before_semantic)
names = list(before.get("merge_whitelist_usernames") or [])
name_ids = user_ids(names)
if name_ids not in ([2], [2, 153]):
raise RuntimeError("PROTECTION_WHITELIST_PRESTATE_INVALID")
target_names = ["owner", "aios-autonomy-agent"]
target_semantic = copy.deepcopy(before_semantic)
target_semantic["merge_whitelist_user_ids"] = [2, 153]
if name_ids == [2, 153]:
if before_semantic_sha != STAGE1_SEMANTIC_SHA:
journal("APPLY_BLOCKED_STAGE1_IDEMPOTENCY_SEMANTIC_CONFLICT", transport_digest=sha(before), semantic_digest=before_semantic_sha, expected_semantic_digest=STAGE1_SEMANTIC_SHA)
raise RuntimeError("PROTECTION_STAGE1_SEMANTIC_CONFLICT")
receipt = mutation_receipt(before, before, binding, target_semantic)
journal("APPLY_APPROVED_PROTECTION_DELTA_IDEMPOTENT_SUCCESS", receipt=receipt)
return {"status": "IDEMPOTENT_SUCCESS", "protection": before, "transport_digest": sha(before), "semantic_digest": before_semantic_sha, "target_semantic_digest": sha(target_semantic)}
if before_semantic_sha != APPROVED_SEMANTIC_SHA:
journal("APPLY_BLOCKED_SEMANTIC_CAS", transport_digest=sha(before), semantic_digest=before_semantic_sha, expected_semantic_digest=APPROVED_SEMANTIC_SHA)
raise RuntimeError("PROTECTION_SEMANTIC_CAS_CONFLICT")
after_api = api("PATCH", f"/api/v1/repos/{REPO}/branch_protections/main", value, {"merge_whitelist_usernames": target_names})[1]
after = get_protection(value, "main")
for key in before:
if key in {"updated_at", "merge_whitelist_usernames"}:
continue
if after.get(key) != before.get(key):
raise RuntimeError("PROTECTION_UNAPPROVED_FIELD_CHANGED:" + key)
if user_ids(after.get("merge_whitelist_usernames")) != [2, 153]:
raise RuntimeError("PROTECTION_TARGET_READBACK_FAILED")
after_semantic = semantic_policy(after)
if after_semantic != target_semantic:
raise RuntimeError("PROTECTION_SEMANTIC_TARGET_READBACK_FAILED")
receipt = mutation_receipt(before, after, binding, target_semantic)
journal("APPLY_APPROVED_PROTECTION_DELTA_COMPLETED", receipt=receipt)
return {"status": "APPLIED", "protection": after, "previous_transport_digest": sha(before), "previous_semantic_digest": before_semantic_sha, "transport_digest": sha(after), "semantic_digest": sha(after_semantic), "receipt": receipt}
def apply_stage2(binding, facts, value):
verify_binding(binding, value)
validate_stage2_facts(facts)
before = get_protection(value, "main")
if OWNER_GATE in (before.get("status_check_contexts") or []) and stage2_semantic_digest(before) != STAGE1_SEMANTIC_SHA:
raise RuntimeError("STAGE2_SEMANTIC_PRECONDITION_INVALID")
try:
status, target = build_target(before)
except Stage2GateError as exc:
journal("MIGRATE_APPROVED_GATE_BLOCKED", reason=str(exc), semantic_digest=stage2_semantic_digest(before))
raise RuntimeError(str(exc)) from exc
target_digest = stage2_semantic_digest(target)
if status == "IDEMPOTENT_SUCCESS":
journal(
"MIGRATE_APPROVED_GATE_IDEMPOTENT_SUCCESS",
before_semantic_digest=stage2_semantic_digest(before),
target_semantic_digest=target_digest,
campaign_id=CAMPAIGN_ID,
)
return {
"status": status,
"protection": before,
"transport_digest": sha(before),
"semantic_digest": stage2_semantic_digest(before),
"target_semantic_digest": target_digest,
"gate_transition": {"removed": OWNER_GATE, "required": CAMPAIGN_GATE},
}
after_api = api(
"PATCH",
f"/api/v1/repos/{REPO}/branch_protections/main",
value,
{"status_check_contexts": target["status_check_contexts"]},
)[1]
after = get_protection(value, "main")
for key in before:
if key in {"updated_at", "status_check_contexts"}:
continue
if after.get(key) != before.get(key):
raise RuntimeError("PROTECTION_UNAPPROVED_FIELD_CHANGED:" + key)
if after.get("status_check_contexts") != target["status_check_contexts"]:
raise RuntimeError("STAGE2_GATE_TARGET_READBACK_FAILED")
if stage2_semantic_digest(after) != target_digest:
raise RuntimeError("STAGE2_SEMANTIC_TARGET_READBACK_FAILED")
receipt = {
"before_transport_digest": sha(before),
"before_semantic_digest": stage2_semantic_digest(before),
"after_transport_digest": sha(after),
"after_semantic_digest": stage2_semantic_digest(after),
"target_semantic_digest": target_digest,
"campaign_id": CAMPAIGN_ID,
"gate_transition": {"removed": OWNER_GATE, "required": CAMPAIGN_GATE},
"preserved_security_fields": True,
"facts_digest": sha(facts),
}
journal("MIGRATE_APPROVED_GATE_COMPLETED", receipt=receipt)
return {
"status": "APPLIED",
"protection": after,
"previous_transport_digest": sha(before),
"previous_semantic_digest": stage2_semantic_digest(before),
"transport_digest": sha(after),
"semantic_digest": stage2_semantic_digest(after),
"target_semantic_digest": target_digest,
"receipt": receipt,
}
def handle(request, value):
if not isinstance(request, dict) or set(request) != {"operation", "payload"}:
raise RuntimeError("REQUEST_SCHEMA_INVALID")
operation = request["operation"]
payload = request["payload"]
if not isinstance(payload, dict) or payload.get("repository") != REPO:
raise RuntimeError("REPOSITORY_DENIED")
if operation == "GET_BRANCH_PROTECTION":
protection = get_protection(value, payload.get("branch"))
journal("GET_BRANCH_PROTECTION", branch=payload.get("branch"), transport_digest=sha(protection), semantic_digest=semantic_sha(protection))
return {"status": "PASS", "protection": protection, "snapshot_sha256": sha(protection), "transport_digest": sha(protection), "semantic_digest": semantic_sha(protection), "semantic_schema": SEMANTIC_SCHEMA}
if operation == "READBACK_BRANCH_PROTECTION":
protection = get_protection(value, payload.get("branch"))
return {"status": "PASS", "protection": protection, "snapshot_sha256": sha(protection), "transport_digest": sha(protection), "semantic_digest": semantic_sha(protection), "semantic_schema": SEMANTIC_SCHEMA}
if operation == "APPLY_APPROVED_PROTECTION_DELTA":
binding = payload.get("GovernanceExecutionBinding")
if not isinstance(binding, dict):
raise RuntimeError("GOVERNANCE_BINDING_REQUIRED")
return apply_stage1(binding, value)
if operation == "MIGRATE_APPROVED_GATE":
binding = payload.get("GovernanceExecutionBinding")
facts = payload.get("Stage2Facts")
if not isinstance(binding, dict) or not isinstance(facts, dict):
raise RuntimeError("STAGE2_BINDING_OR_FACTS_REQUIRED")
return apply_stage2(binding, facts, value)
raise RuntimeError("OPERATION_DENIED")
def main():
value = token()
sock_path = pathlib.Path(SOCKET)
sock_path.unlink(missing_ok=True)
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(SOCKET)
os.chmod(SOCKET, 0o660)
server.listen(16)
journal("EXECUTOR_STARTED", api_surface=["GET_BRANCH_PROTECTION", "APPLY_APPROVED_PROTECTION_DELTA", "MIGRATE_APPROVED_GATE", "READBACK_BRANCH_PROTECTION"], semantic_schema=SEMANTIC_SCHEMA, approved_semantic_digest=APPROVED_SEMANTIC_SHA, stage1_semantic_digest=STAGE1_SEMANTIC_SHA, campaign_gate=CAMPAIGN_GATE)
try:
while True:
conn, _ = server.accept()
with conn:
data = conn.recv(1024 * 1024 + 1)
if len(data) > 1024 * 1024:
result = {"status": "FAIL", "code": "REQUEST_TOO_LARGE"}
else:
try:
request = json.loads(data.decode())
result = handle(request, value)
except Exception as exc:
result = {"status": "FAIL", "code": str(exc)}
conn.sendall((json.dumps(result, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n").encode())
finally:
server.close()
sock_path.unlink(missing_ok=True)
if __name__ == "__main__":
main()
@@ -0,0 +1,347 @@
#!/usr/bin/env python3
import base64
import hashlib
import json
import os
import pathlib
import shutil
import socket
import subprocess
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
LOCAL_API = 'http://127.0.0.1:3000/api/v1'
NETWORK_API = 'https://g.cnnetmkt.top/git/api/v1'
NETWORK_REMOTE = 'https://g.cnnetmkt.top/git/aios/aios.git'
REPO = 'aios/aios'
BASE_BRANCH = 'governance/approvals'
REQUEST_BRANCH = 'governance/request/protection-semantic-reconciliation-v1-20260808'
SOCKET_PATH = '/run/aios-governance-preapproval-publisher/publisher.sock'
CREDENTIAL_NAME = 'gitea-machine-token'
AUTHORITY_REPO = '/data/aios-gitops/repo.git'
TASK_ROOT = pathlib.Path('/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1')
INPUT_ROOT = TASK_ROOT / 'publication-input'
RECEIPT = TASK_ROOT / 'publication-receipt.json'
MANIFEST_REL = 'approvals/governance-policy/AIOS-PROTECTION-SEMANTIC-SNAPSHOT-RECONCILIATION-V1.json'
EVIDENCE_REL = 'approvals/governance-policy/AIOS-PROTECTION-SEMANTIC-SNAPSHOT-RECONCILIATION-V1.evidence.json'
def digest_bytes(data):
return hashlib.sha256(data).hexdigest()
def digest_json(value):
return digest_bytes(json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode())
def load_token():
path = pathlib.Path(os.environ['CREDENTIALS_DIRECTORY']) / CREDENTIAL_NAME
value = path.read_text(encoding='utf-8').strip()
if len(value) < 16:
raise RuntimeError('MACHINE_CREDENTIAL_INVALID')
return value
def api(base, method, path, token, body=None, expected=(200,)):
raw = None if body is None else json.dumps(body, separators=(',', ':'), ensure_ascii=False).encode()
request = urllib.request.Request(base + path, data=raw, method=method)
request.add_header('Authorization', 'token ' + token)
request.add_header('Accept', 'application/json')
if raw is not None:
request.add_header('Content-Type', 'application/json')
try:
with urllib.request.urlopen(request, timeout=20) as response:
payload = response.read()
if response.status not in expected:
raise RuntimeError('GITEA_HTTP_' + str(response.status))
return response.status, json.loads(payload) if payload else None
except urllib.error.HTTPError as exc:
payload = exc.read()
if exc.code in expected:
return exc.code, json.loads(payload) if payload else None
raise RuntimeError('GITEA_HTTP_' + str(exc.code) + ':' + method + ':' + path) from None
def authority_head():
result = subprocess.run(
['git', '--git-dir=' + AUTHORITY_REPO, 'rev-parse', 'refs/heads/' + BASE_BRANCH],
text=True, capture_output=True, check=False,
)
if result.returncode != 0:
raise RuntimeError('AUTHORITY_BASE_UNAVAILABLE')
return result.stdout.strip()
def branch_api(base, token, branch, expected=(200,)):
return api(base, 'GET', '/repos/' + REPO + '/branches/' + urllib.parse.quote(branch, safe=''), token, expected=expected)
def verify_base(token):
authority = authority_head()
_, local = branch_api(LOCAL_API, token, BASE_BRANCH)
_, network = branch_api(NETWORK_API, token, BASE_BRANCH)
local_oid = local.get('commit', {}).get('id')
network_oid = network.get('commit', {}).get('id')
if not authority or authority != local_oid or authority != network_oid:
raise RuntimeError('RECONCILIATION_REQUIRED')
return authority
def verify_identity(token):
_, user = api(LOCAL_API, 'GET', '/user', token)
if user.get('login') != 'aios-autonomy-agent' or user.get('id') != 153 or user.get('is_admin') is not False:
raise RuntimeError('MACHINE_IDENTITY_REJECTED')
return {'login': user['login'], 'id': user['id'], 'owner': user.get('is_admin')}
def load_inputs():
manifest = json.dumps(json.loads((INPUT_ROOT / 'manifest.json').read_text(encoding='utf-8')), sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode()
evidence = json.dumps(json.loads((INPUT_ROOT / 'evidence.json').read_text(encoding='utf-8')), sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode()
for data in (manifest, evidence):
if any(secret in data.lower() for secret in (b'authorization:', b'ghp_', b'github_pat_', b'secret=', b'token=')):
raise RuntimeError('SECRET_CONTENT_REJECTED')
return manifest, evidence
def publication_key(base_oid, manifest_sha, evidence_sha):
payload = REPO + base_oid + REQUEST_BRANCH + manifest_sha + evidence_sha
return hashlib.sha256(payload.encode()).hexdigest()
def write_receipt(state, **fields):
record = {
'schema': 'aios.governance-preapproval-publication.v1',
'operation': 'SEMANTIC_RECONCILIATION_REQUEST',
'state': state,
'recorded_unix': int(time.time()),
'request_branch': REQUEST_BRANCH,
'manifest_path': MANIFEST_REL,
'evidence_path': EVIDENCE_REL,
'machine_actor': 'aios-autonomy-agent',
'owner': False,
'credential_ref': {'scheme': 'systemd-credential', 'name': CREDENTIAL_NAME},
'governance_capability_executed': False,
'protection_mutation': False,
'force_push': False,
**fields,
}
RECEIPT.parent.mkdir(parents=True, exist_ok=True)
temporary = RECEIPT.with_suffix('.tmp')
temporary.write_text(json.dumps(record, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n', encoding='utf-8')
temporary.replace(RECEIPT)
RECEIPT.chmod(0o640)
return record
def existing_prs(token):
_, prs = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls?state=all&base=' + urllib.parse.quote(BASE_BRANCH, safe='') + '&limit=100', token)
return [pr for pr in prs if pr.get('head', {}).get('ref') == REQUEST_BRANCH and pr.get('base', {}).get('ref') == BASE_BRANCH]
def verify_existing_branch(token, branch_oid, manifest, evidence):
_, commit = api(LOCAL_API, 'GET', '/repos/' + REPO + '/git/commits/' + branch_oid, token)
if commit.get('parents', [{}])[0].get('sha') is None:
raise RuntimeError('REQUEST_BRANCH_COMMIT_INVALID')
for relative, expected in ((MANIFEST_REL, manifest), (EVIDENCE_REL, evidence)):
_, content = api(LOCAL_API, 'GET', '/repos/' + REPO + '/contents/' + urllib.parse.quote(relative, safe='/') + '?ref=' + branch_oid, token)
actual = base64.b64decode(''.join(content.get('content', '').split()))
if actual != expected:
raise RuntimeError('GOVERNANCE_REQUEST_BRANCH_CONFLICT')
def push_one_commit(token, base_oid, manifest, evidence):
work_root = TASK_ROOT / 'publisher-work'
work_root.mkdir(parents=True, exist_ok=True)
temporary = pathlib.Path(tempfile.mkdtemp(prefix='semantic-reconciliation-', dir=work_root))
askpass = temporary / 'askpass.sh'
clone = temporary / 'repo'
credential_path = pathlib.Path(os.environ['CREDENTIALS_DIRECTORY']) / CREDENTIAL_NAME
askpass.write_text(
'#!/bin/sh\ncase "$1" in *Username*) printf "%s\\n" "$GITEA_MACHINE_ACTOR";; *) cat "$CREDENTIAL_FILE";; esac\n',
encoding='utf-8',
)
askpass.chmod(0o700)
env = os.environ.copy()
env.update({
'GIT_ASKPASS': str(askpass),
'GIT_TERMINAL_PROMPT': '0',
'GITEA_MACHINE_ACTOR': 'aios-autonomy-agent',
'CREDENTIAL_FILE': str(credential_path),
'GIT_CONFIG_COUNT': '1',
'GIT_CONFIG_KEY_0': 'safe.directory',
'GIT_CONFIG_VALUE_0': AUTHORITY_REPO,
})
try:
run = subprocess.run(
['git', 'clone', '--branch', BASE_BRANCH, '--single-branch', '--quiet', NETWORK_REMOTE, str(clone)],
text=True, capture_output=True, env=env, check=False,
)
if run.returncode != 0:
detail = ' '.join(run.stderr.strip().split())[:240]
raise RuntimeError('PUBLISHER_CLONE_FAILED:' + detail)
def git(*args, push_env=None):
result = subprocess.run(['git', *args], cwd=clone, text=True, capture_output=True, env=push_env or env, check=False)
if result.returncode != 0:
raise RuntimeError('PUBLISHER_GIT_OPERATION_FAILED')
return result.stdout.strip()
git('checkout', '--detach', base_oid)
git('switch', '--create', REQUEST_BRANCH)
manifest_path = clone / MANIFEST_REL
evidence_path = clone / EVIDENCE_REL
manifest_path.parent.mkdir(parents=True, exist_ok=True)
evidence_path.parent.mkdir(parents=True, exist_ok=True)
manifest_path.write_bytes(manifest)
evidence_path.write_bytes(evidence)
git('add', '--', MANIFEST_REL, EVIDENCE_REL)
staged = git('diff', '--cached', '--name-only').splitlines()
if staged != sorted([MANIFEST_REL, EVIDENCE_REL]):
raise RuntimeError('PUBLISHER_PATH_ALLOWLIST_FAILED')
git('-c', 'user.name=aios-autonomy-agent', '-c', 'user.email=aios-autonomy-agent@aios.invalid', 'commit', '--quiet', '-m', '[governance] publish semantic reconciliation evidence')
if git('rev-list', '--count', base_oid + '..HEAD') != '1':
raise RuntimeError('PUBLISHER_COMMIT_COUNT_FAILED')
git('push', '--porcelain', NETWORK_REMOTE, 'HEAD:refs/heads/' + REQUEST_BRANCH, push_env=env)
head = git('rev-parse', 'HEAD')
return head
finally:
shutil.rmtree(temporary, ignore_errors=True)
def publish():
token = load_token()
identity = verify_identity(token)
base_oid = verify_base(token)
manifest, evidence = load_inputs()
manifest_sha = digest_bytes(manifest)
evidence_sha = digest_bytes(evidence)
key = publication_key(base_oid, manifest_sha, evidence_sha)
write_receipt('REQUESTED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, machine_identity=identity)
write_receipt('LOCAL_CANDIDATE_VERIFIED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, machine_identity=identity)
branch_status, branch = branch_api(LOCAL_API, token, REQUEST_BRANCH, expected=(200, 404))
if branch_status == 200:
branch_oid = branch.get('commit', {}).get('id')
verify_existing_branch(token, branch_oid, manifest, evidence)
else:
branch_oid = push_one_commit(token, base_oid, manifest, evidence)
verify_existing_branch(token, branch_oid, manifest, evidence)
write_receipt('BRANCH_PUSHED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, machine_identity=identity)
prs = existing_prs(token)
if len(prs) > 1:
raise RuntimeError('MULTIPLE_MATCHING_APPROVAL_PRS')
if prs:
pr = prs[0]
if pr.get('head', {}).get('sha') != branch_oid:
raise RuntimeError('APPROVAL_PR_HEAD_MISMATCH')
pr_number = pr.get('number')
else:
_, pr = api(LOCAL_API, 'POST', '/repos/' + REPO + '/pulls', token, {
'title': '[GOVERNANCE RECONCILIATION] Semantic protection snapshot schema migration',
'head': REQUEST_BRANCH,
'base': BASE_BRANCH,
'body': 'Schema-only reconciliation for the existing PR54 Stage-1 and Stage-2 predicates. No new governance capability; Owner exact-head re-attestation required.',
}, expected=(201,))
pr_number = pr.get('number')
write_receipt('PR_CREATED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, pr_number=pr_number, machine_identity=identity)
_, remote_branch = branch_api(NETWORK_API, token, REQUEST_BRANCH)
if remote_branch.get('commit', {}).get('id') != branch_oid:
raise RuntimeError('NETWORK_BRANCH_READBACK_MISMATCH')
_, read_pr = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number), token)
if read_pr.get('head', {}).get('sha') != branch_oid or read_pr.get('base', {}).get('ref') != BASE_BRANCH:
raise RuntimeError('PR_READBACK_MISMATCH')
result = write_receipt('COMPLETED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, pr_number=pr_number, pr_head_oid=read_pr.get('head', {}).get('sha'), machine_identity=identity)
return result
def self_test():
token = load_token()
identity = verify_identity(token)
base_oid = verify_base(token)
branch_status, _ = branch_api(LOCAL_API, token, REQUEST_BRANCH, expected=(200, 404))
return {
'status': 'PASS',
'governance_base_oid': base_oid,
'machine_identity': identity,
'credential_ref': {'scheme': 'systemd-credential', 'name': CREDENTIAL_NAME},
'branch_namespace': 'refs/heads/governance/request/*',
'target_base': BASE_BRANCH,
'request_branch_status': 'EXISTS' if branch_status == 200 else 'ABSENT',
'operation_allowlist': ['READ_BASE', 'CREATE_ONE_REQUEST_BRANCH', 'WRITE_TWO_EVIDENCE_PATHS_ONE_COMMIT', 'CREATE_OR_READ_ONE_APPROVAL_PR', 'READBACK'],
'denied': ['MAIN_WRITE', 'DIRECT_APPROVALS_WRITE', 'BRANCH_PROTECTION', 'MERGE', 'FORCE_PUSH', 'TAG', 'PRODUCTION', 'CREDENTIAL_MUTATION'],
'governance_capability_executed': False,
}
def read_publication_status():
token = load_token()
receipt = json.loads(RECEIPT.read_text(encoding='utf-8'))
pr_number = receipt.get('pr_number')
if not pr_number:
raise RuntimeError('PUBLICATION_RECEIPT_PR_MISSING')
_, pr = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number), token)
_, reviews = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number) + '/reviews', token)
_, branch = branch_api(LOCAL_API, token, REQUEST_BRANCH)
_, network_branch = branch_api(NETWORK_API, token, REQUEST_BRANCH)
return {
'status': 'PASS',
'request_branch': REQUEST_BRANCH,
'branch_head': branch.get('commit', {}).get('id'),
'network_branch_head': network_branch.get('commit', {}).get('id'),
'pr_number': pr.get('number'),
'pr_state': pr.get('state'),
'pr_merged': pr.get('merged'),
'pr_head': pr.get('head', {}).get('sha'),
'pr_base': pr.get('base', {}).get('ref'),
'reviews': [
{
'id': item.get('id'),
'reviewer_id': item.get('user', {}).get('id'),
'state': item.get('state'),
'commit_id': item.get('commit_id'),
'dismissed': item.get('dismissed'),
}
for item in reviews
],
'governance_capability_executed': False,
'protection_mutation': False,
}
def handle(request):
if not isinstance(request, dict) or set(request) != {'operation'}:
raise RuntimeError('REQUEST_SCHEMA_INVALID')
if request['operation'] == 'DRY_RUN_SELF_TEST':
return self_test()
if request['operation'] == 'PUBLISH_SEMANTIC_RECONCILIATION':
return publish()
if request['operation'] == 'READ_SEMANTIC_RECONCILIATION_STATUS':
return read_publication_status()
raise RuntimeError('OPERATION_DENIED')
def serve(listener):
conn, _ = listener.accept()
with conn:
try:
data = conn.recv(1024 * 1024 + 1)
if len(data) > 1024 * 1024:
raise RuntimeError('REQUEST_TOO_LARGE')
result = handle(json.loads(data.decode()))
except Exception as exc:
result = {'status': 'FAIL', 'code': str(exc)}
conn.sendall((json.dumps(result, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n').encode())
def main():
if os.environ.get('LISTEN_FDS') != '1':
raise SystemExit('SOCKET_ACTIVATION_REQUIRED')
listener = socket.fromfd(3, socket.AF_UNIX, socket.SOCK_STREAM)
serve(listener)
if __name__ == '__main__':
main()
@@ -0,0 +1,73 @@
"""Fail-closed Authority/Gitea ref reconciliation after protected merge."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import subprocess
class RefReconciliationError(RuntimeError):
pass
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or len(value) != 40 or any(char not in "0123456789abcdef" for char in value):
raise RefReconciliationError(field + "_INVALID")
return value
@dataclass(frozen=True, slots=True)
class RefReconciler:
git_dir: Path
def read(self, ref: str) -> str:
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "rev-parse", "--verify", ref],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise RefReconciliationError("AUTHORITY_REF_UNAVAILABLE")
return result.stdout.strip()
def is_ancestor(self, ancestor: str, descendant: str) -> bool:
_oid(ancestor, "ancestor")
_oid(descendant, "descendant")
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "merge-base", "--is-ancestor", ancestor, descendant],
text=True,
capture_output=True,
check=False,
)
if result.returncode == 0:
return True
if result.returncode == 1:
return False
raise RefReconciliationError("AUTHORITY_ANCESTRY_CHECK_FAILED")
def reconcile_main(self, *, expected_oid: str, merged_oid: str, gitea_main_oid: str) -> str:
_oid(expected_oid, "expected_oid")
_oid(merged_oid, "merged_oid")
_oid(gitea_main_oid, "gitea_main_oid")
if gitea_main_oid != merged_oid:
raise RefReconciliationError("GITEA_MAIN_EXPECTATION_MISMATCH")
if not self.is_ancestor(expected_oid, merged_oid):
raise RefReconciliationError("MAIN_FF_REQUIRED")
current = self.read("refs/heads/main")
if current == merged_oid:
return merged_oid
if current != expected_oid:
raise RefReconciliationError("AUTHORITY_MAIN_EXPECTATION_MISMATCH")
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "update-ref", "refs/heads/main", merged_oid, expected_oid],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise RefReconciliationError("AUTHORITY_MAIN_CAS_FAILED")
if self.read("refs/heads/main") != merged_oid:
raise RefReconciliationError("AUTHORITY_MAIN_CAS_READBACK_MISMATCH")
return merged_oid
@@ -0,0 +1,81 @@
"""Canonical semantic branch-protection model used by AIOS governance gates."""
from __future__ import annotations
import hashlib
import json
from typing import Any, Mapping
SCHEMA = "aios.semantic-protection.v1"
USER_IDS = {"owner": 2, "aios-autonomy-agent": 153}
def canonical_json(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def _typed_json(value: Any) -> list[Any]:
if value in (None, "null"):
return []
if isinstance(value, str):
return json.loads(value)
return list(value)
def _user_ids(names: Any) -> list[int]:
result = []
for name in names or []:
if name not in USER_IDS:
raise ValueError("UNKNOWN_GOVERNANCE_USER:" + str(name))
result.append(USER_IDS[name])
return sorted(result)
def semantic_policy(protection: Mapping[str, Any]) -> dict[str, Any]:
"""Drop transport metadata and canonicalize semantic security fields."""
return {
"schema_version": SCHEMA,
"repo_id": 1,
"protection_id": 2,
"branch_name": protection.get("branch_name"),
"can_push": protection.get("enable_push"),
"can_force_push": protection.get("enable_force_push"),
"enable_merge_whitelist": protection.get("enable_merge_whitelist"),
"merge_whitelist_user_ids": _user_ids(protection.get("merge_whitelist_usernames")),
"merge_whitelist_team_ids": [],
"required_approvals": protection.get("required_approvals"),
"enable_status_check": protection.get("enable_status_check"),
"status_check_contexts": sorted(protection.get("status_check_contexts") or []),
"block_admin_merge_override": protection.get("block_admin_merge_override"),
"block_on_official_review_requests": protection.get("block_on_official_review_requests"),
"block_on_outdated_branch": protection.get("block_on_outdated_branch"),
"block_on_rejected_reviews": protection.get("block_on_rejected_reviews"),
"dismiss_stale_approvals": protection.get("dismiss_stale_approvals"),
"ignore_stale_approvals": protection.get("ignore_stale_approvals"),
"enable_approvals_whitelist": protection.get("enable_approvals_whitelist"),
"approvals_whitelist_user_ids": _user_ids(protection.get("approvals_whitelist_username")),
"approvals_whitelist_team_ids": [],
"enable_bypass_allowlist": protection.get("enable_bypass_allowlist"),
"bypass_allowlist_user_ids": _user_ids(protection.get("bypass_allowlist_usernames")),
"bypass_allowlist_team_ids": [],
"enable_force_push_allowlist": protection.get("enable_force_push_allowlist"),
"force_push_allowlist_user_ids": _user_ids(protection.get("force_push_allowlist_usernames")),
"enable_whitelist": protection.get("enable_push_whitelist"),
"whitelist_user_ids": _user_ids(protection.get("push_whitelist_usernames")),
"whitelist_team_ids": [],
"force_push_allowlist_deploy_keys": protection.get("force_push_allowlist_deploy_keys"),
"whitelist_deploy_keys": protection.get("push_whitelist_deploy_keys"),
"require_signed_commits": protection.get("require_signed_commits"),
"priority": protection.get("priority"),
"protected_file_patterns": _typed_json(protection.get("protected_file_patterns")),
"unprotected_file_patterns": _typed_json(protection.get("unprotected_file_patterns")),
}
def semantic_digest(protection: Mapping[str, Any]) -> str:
return hashlib.sha256(canonical_json(semantic_policy(protection))).hexdigest()
def transport_digest(protection: Mapping[str, Any]) -> str:
return hashlib.sha256(canonical_json(protection)).hexdigest()
@@ -0,0 +1,141 @@
"""Fail-closed Stage-2 campaign gate migration predicates."""
from __future__ import annotations
import copy
import hashlib
import json
import re
from typing import Any, Mapping
SEMANTIC_SCHEMA = "aios.semantic-protection.v1"
STAGE1_SEMANTIC_SHA = "d911e856f71de6105b8efa08e13338a448e0b6d37536332decd80c9f409ecf27"
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
OWNER_GATE = "owner-approval-gate"
CAMPAIGN_GATE = "campaign-approval-gate"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
class Stage2GateError(RuntimeError):
pass
def canonical_json(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def _typed_json(value: Any) -> list[Any]:
if value in (None, "", "null"):
return []
if isinstance(value, str):
return json.loads(value)
return list(value)
def _user_ids(names: Any) -> list[int]:
mapping = {"owner": 2, "aios-autonomy-agent": 153}
result = []
for name in names or []:
if name not in mapping:
raise Stage2GateError("UNKNOWN_GOVERNANCE_USER")
result.append(mapping[name])
return sorted(result)
def semantic_policy(protection: Mapping[str, Any]) -> dict[str, Any]:
return {
"schema_version": SEMANTIC_SCHEMA,
"repo_id": 1,
"protection_id": 2,
"branch_name": protection.get("branch_name"),
"can_push": protection.get("enable_push"),
"can_force_push": protection.get("enable_force_push"),
"enable_merge_whitelist": protection.get("enable_merge_whitelist"),
"merge_whitelist_user_ids": _user_ids(protection.get("merge_whitelist_usernames")),
"merge_whitelist_team_ids": [],
"required_approvals": protection.get("required_approvals"),
"enable_status_check": protection.get("enable_status_check"),
"status_check_contexts": sorted(protection.get("status_check_contexts") or []),
"block_admin_merge_override": protection.get("block_admin_merge_override"),
"block_on_official_review_requests": protection.get("block_on_official_review_requests"),
"block_on_outdated_branch": protection.get("block_on_outdated_branch"),
"block_on_rejected_reviews": protection.get("block_on_rejected_reviews"),
"dismiss_stale_approvals": protection.get("dismiss_stale_approvals"),
"ignore_stale_approvals": protection.get("ignore_stale_approvals"),
"enable_approvals_whitelist": protection.get("enable_approvals_whitelist"),
"approvals_whitelist_user_ids": _user_ids(protection.get("approvals_whitelist_username")),
"approvals_whitelist_team_ids": [],
"enable_bypass_allowlist": protection.get("enable_bypass_allowlist"),
"bypass_allowlist_user_ids": _user_ids(protection.get("bypass_allowlist_usernames")),
"bypass_allowlist_team_ids": [],
"enable_force_push_allowlist": protection.get("enable_force_push_allowlist"),
"force_push_allowlist_user_ids": _user_ids(protection.get("force_push_allowlist_usernames")),
"force_push_allowlist_team_ids": [],
"enable_whitelist": protection.get("enable_push_whitelist"),
"whitelist_user_ids": _user_ids(protection.get("push_whitelist_usernames")),
"whitelist_team_ids": [],
"force_push_allowlist_deploy_keys": protection.get("force_push_allowlist_deploy_keys"),
"whitelist_deploy_keys": protection.get("push_whitelist_deploy_keys"),
"require_signed_commits": protection.get("require_signed_commits"),
"priority": protection.get("priority"),
"protected_file_patterns": protection.get("protected_file_patterns"),
"unprotected_file_patterns": protection.get("unprotected_file_patterns"),
}
def semantic_digest(protection: Mapping[str, Any]) -> str:
return hashlib.sha256(canonical_json(semantic_policy(protection))).hexdigest()
def build_target(protection: Mapping[str, Any]) -> tuple[str, dict[str, Any]]:
current = list(protection.get("status_check_contexts") or [])
has_owner = OWNER_GATE in current
has_campaign = CAMPAIGN_GATE in current
if has_owner and has_campaign:
raise Stage2GateError("GATE_PRESTATE_CONFLICT")
if not has_owner and not has_campaign:
raise Stage2GateError("OWNER_GATE_MISSING")
if has_campaign:
return "IDEMPOTENT_SUCCESS", copy.deepcopy(dict(protection))
if semantic_digest(protection) != STAGE1_SEMANTIC_SHA:
raise Stage2GateError("SEMANTIC_PRECONDITION_INVALID")
target = copy.deepcopy(dict(protection))
target["status_check_contexts"] = sorted(
[gate for gate in current if gate != OWNER_GATE] + [CAMPAIGN_GATE]
)
return "READY", target
def validate_stage2_facts(facts: Mapping[str, Any]) -> None:
required = {
"campaign_id",
"campaign_active",
"campaign_revoked",
"machine_actor",
"required_checks_all_success",
"required_checks_same_head",
"unresolved_conversations",
"mergeable",
"protected_target",
"current_main_oid",
"gitea_main_oid",
"current_main_lineage_verified",
}
if set(facts) != required:
raise Stage2GateError("STAGE2_FACTS_SCHEMA_INVALID")
if facts["campaign_id"] != CAMPAIGN_ID or facts["campaign_active"] is not True or facts["campaign_revoked"] is not False:
raise Stage2GateError("CAMPAIGN_NOT_ACTIVE")
if facts["machine_actor"] != "aios-autonomy-agent":
raise Stage2GateError("MACHINE_ACTOR_MISMATCH")
if facts["required_checks_all_success"] is not True or facts["required_checks_same_head"] is not True:
raise Stage2GateError("REQUIRED_CHECKS_NOT_CURRENT")
if facts["unresolved_conversations"] != 0 or facts["mergeable"] is not True:
raise Stage2GateError("CAMPAIGN_GATE_FACTS_INVALID")
if facts["protected_target"] != "main" or facts["current_main_lineage_verified"] is not True:
raise Stage2GateError("PROTECTED_TARGET_OR_LINEAGE_INVALID")
if (
not isinstance(facts["current_main_oid"], str)
or not OID_RE.fullmatch(facts["current_main_oid"])
or facts["gitea_main_oid"] != facts["current_main_oid"]
):
raise Stage2GateError("MAIN_READBACK_MISMATCH")
@@ -0,0 +1,710 @@
"""Governed V2R2C1 production release orchestration.
This module is deliberately scoped to the Owner-approved V2R2C1 binding. It
uses the existing TagPublisher and metadata adapters; it does not introduce a
second Git writer, a second durable job store, or an alternate baseline.
"""
from __future__ import annotations
import argparse
import fcntl
import hashlib
import json
import os
import re
import subprocess
import tarfile
import tempfile
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
from gitea_tag_publication import (
GiteaTagClient,
GitTagBackend,
PublicationManualOwnerReviewRequired,
PublicationPending,
PublicationReconciliationRequired,
PublicationRequest,
TagPublisher,
)
from version_registry import (
RegistryConvergenceError,
assert_source_registry_convergence,
resolve_source_registry_main_oid,
source_registry_snapshot,
verify_authoritative_repository,
verify_source_registry_git_identity,
)
VERSION = "V2R2C1"
TAG_OID = "07fc2746b688f5f243f29b39de39f32edc3dd9d2"
TARGET_OID = "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
EVIDENCE_CLOSURE_OID = "a8b906d1bc24088c19647e98de6a679268f6d0a2"
APPROVAL_PLAN_ID = "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e"
REPOSITORY = "aios/aios"
SERVICE = "aios-v2r1c7-production.service"
PORT = 18927
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
class ReleaseBlocked(RuntimeError):
"""A release gate failed and no unsafe recovery is permitted."""
class ManualOwnerReviewRequired(ReleaseBlocked):
pass
class ReconciliationRequired(ReleaseBlocked):
pass
def canonical(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
def sha256_file(path: Path) -> str:
if path.is_symlink() or not path.is_file():
raise ReleaseBlocked("artifact must be a regular file")
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def atomic_json(path: Path, value: Mapping[str, Any], mode: int = 0o600) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_name(path.name + ".tmp")
temporary.write_bytes(canonical(dict(value)))
os.chmod(temporary, mode)
os.replace(temporary, path)
@dataclass(frozen=True)
class ReleaseBinding:
version: str = VERSION
tag_oid: str = TAG_OID
target_oid: str = TARGET_OID
evidence_closure_oid: str = EVIDENCE_CLOSURE_OID
approval_plan_id: str = APPROVAL_PLAN_ID
repository: str = REPOSITORY
production_service: str = SERVICE
production_port: int = PORT
def validate(self) -> None:
if self.version != VERSION or self.repository != REPOSITORY:
raise ManualOwnerReviewRequired("release binding is outside V2R2C1 scope")
if not OID_RE.fullmatch(self.tag_oid) or self.tag_oid != TAG_OID:
raise ManualOwnerReviewRequired("V2R2C1 tag_oid mismatch")
if not OID_RE.fullmatch(self.target_oid) or self.target_oid != TARGET_OID:
raise ManualOwnerReviewRequired("V2R2C1 target_oid mismatch")
if not OID_RE.fullmatch(self.evidence_closure_oid) or self.evidence_closure_oid != EVIDENCE_CLOSURE_OID:
raise ManualOwnerReviewRequired("V2R2C1 evidence closure mismatch")
if self.approval_plan_id != APPROVAL_PLAN_ID:
raise ManualOwnerReviewRequired("V2R2C1 approval plan mismatch")
if self.production_service != SERVICE or int(self.production_port) != PORT:
raise ManualOwnerReviewRequired("production service binding mismatch")
def as_dict(self) -> dict[str, Any]:
return {
"version": self.version,
"target_oid": self.target_oid,
"evidence_closure_oid": self.evidence_closure_oid,
"approval_plan_id": self.approval_plan_id,
"repository": self.repository,
"production_service": self.production_service,
"production_port": self.production_port,
}
class StateLock:
def __init__(self, path: Path):
self.path = path
self.handle = None
def __enter__(self) -> "StateLock":
self.path.parent.mkdir(parents=True, exist_ok=True)
self.handle = self.path.open("a+", encoding="utf-8")
fcntl.flock(self.handle.fileno(), fcntl.LOCK_EX)
return self
def __exit__(self, exc_type, exc, tb) -> None:
if self.handle is not None:
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
self.handle.close()
class ReleaseState:
def __init__(self, root: Path, binding: ReleaseBinding):
self.root = root
self.path = root / "release-state.json"
self.binding = binding
root.mkdir(parents=True, exist_ok=True)
if self.path.exists():
value = json.loads(self.path.read_text(encoding="utf-8"))
if value.get("binding") != binding.as_dict():
raise ManualOwnerReviewRequired("persisted release binding conflict")
self.value = value
else:
self.value = {
"schema": "aios.v2r2c1.governed-production-release-state.v1",
"binding": binding.as_dict(),
"state": "INITIAL",
"completed": {},
"history": [],
}
self.save()
def save(self) -> None:
atomic_json(self.path, self.value)
def record(self, state: str, step: str, result: Mapping[str, Any]) -> None:
safe = json.loads(json.dumps(dict(result)))
self.value["state"] = state
self.value.setdefault("completed", {})[step] = safe
self.value.setdefault("history", []).append(
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "step": step, "result": safe}
)
self.save()
def fail(self, state: str, reason: str) -> None:
self.value["state"] = state
self.value.setdefault("history", []).append(
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "reason": str(reason)[:500]}
)
self.save()
def _git(git_dir: Path, *args: str, check: bool = True) -> str:
completed = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
check=False,
capture_output=True,
text=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if check and completed.returncode != 0:
raise ReconciliationRequired(completed.stderr.strip() or "authoritative git query failed")
return completed.stdout.strip()
def _ancestor(git_dir: Path, ancestor: str, descendant: str) -> bool:
return subprocess.run(
["git", f"--git-dir={git_dir}", "merge-base", "--is-ancestor", ancestor, descendant],
check=False,
capture_output=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
).returncode == 0
def verify_lineage(
git_dir: Path,
binding: ReleaseBinding,
*,
authority_main_oid: str | None = None,
) -> dict[str, Any]:
binding.validate()
main_oid = authority_main_oid or _git(git_dir, "rev-parse", "refs/heads/main")
if not OID_RE.fullmatch(main_oid) or _git(git_dir, "cat-file", "-t", main_oid) != "commit":
raise ReleaseBlocked("authoritative main OID is not a commit")
if _git(git_dir, "cat-file", "-t", binding.target_oid) != "commit":
raise ReleaseBlocked("approved target_oid is not a commit")
if _git(git_dir, "cat-file", "-t", binding.evidence_closure_oid) != "commit":
raise ReleaseBlocked("evidence closure is not a commit")
if not _ancestor(git_dir, binding.target_oid, binding.evidence_closure_oid):
raise ManualOwnerReviewRequired("approved target is not an ancestor of evidence closure")
if not _ancestor(git_dir, binding.target_oid, main_oid):
raise ManualOwnerReviewRequired("approved target is not an ancestor of source registry main")
return {
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"target_is_ancestor_of_evidence": True,
"main_oid": main_oid,
"target_is_ancestor_of_main": True,
}
def verify_local_annotated_tag(git_dir: Path, binding: ReleaseBinding) -> dict[str, Any]:
"""Verify the local authoritative tag object without creating or rewriting it."""
tag_oid = _git(git_dir, "rev-parse", "--verify", f"refs/tags/{binding.version}")
if tag_oid != binding.tag_oid:
raise ManualOwnerReviewRequired("local annotated tag object mismatch")
if _git(git_dir, "cat-file", "-t", tag_oid) != "tag":
raise ReleaseBlocked("release tag is not annotated")
peeled = _git(git_dir, "rev-parse", "--verify", f"{tag_oid}^{{commit}}")
if peeled != binding.target_oid:
raise ManualOwnerReviewRequired("local annotated tag target mismatch")
return {
"tag_oid": tag_oid,
"tag_object_type": "tag",
"peeled_target_oid": peeled,
"target_oid": binding.target_oid,
}
def verify_owner_approval(value: Mapping[str, Any], binding: ReleaseBinding) -> dict[str, Any]:
approval = value.get("owner_approval")
if not isinstance(approval, Mapping):
raise ReleaseBlocked("OWNER_APPROVAL_RECEIPT_REQUIRED")
if approval.get("approved") is not True or approval.get("status") != "APPROVED":
raise ReleaseBlocked("OWNER_APPROVAL_NOT_ACCEPTED")
if approval.get("approved_by") != "aiman":
raise ManualOwnerReviewRequired("OWNER_APPROVAL_ACTOR_MISMATCH")
if approval.get("approval_source") != "explicit_owner_approval_in_chat":
raise ManualOwnerReviewRequired("OWNER_APPROVAL_SOURCE_MISMATCH")
for key, expected in binding.as_dict().items():
if key in {"repository", "production_service", "production_port"}:
continue
if approval.get(key) != expected:
raise ManualOwnerReviewRequired("OWNER_APPROVAL_BINDING_MISMATCH:" + key)
return dict(approval)
def _payload_file(git_dir: Path, oid: str, path: str) -> bytes:
completed = subprocess.run(
["git", f"--git-dir={git_dir}", "show", f"{oid}:{path}"],
check=False,
capture_output=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise ReleaseBlocked("required release source missing:" + path)
return completed.stdout
def _commit_epoch(git_dir: Path, oid: str) -> int:
return int(_git(git_dir, "show", "-s", "--format=%ct", oid))
def build_production_artifact(
git_dir: Path,
binding: ReleaseBinding,
output_root: Path,
) -> dict[str, Any]:
"""Build a deterministic artifact from composite immutable provenance.
Release payload files are read from target ``T``. The source registry is
read once from the pinned main/convergence commit ``M`` and its exact blob
bytes are reused for packaging and all recorded digests.
"""
binding.validate()
verify_authoritative_repository(git_dir)
source_registry_main_oid = resolve_source_registry_main_oid(
git_dir,
main_ref="refs/heads/main",
)
verify_lineage(git_dir, binding, authority_main_oid=source_registry_main_oid)
tag_lineage = verify_local_annotated_tag(git_dir, binding)
try:
source_snapshot = source_registry_snapshot(
git_dir,
VERSION,
source_registry_main_oid=source_registry_main_oid,
expected_tag_oid=binding.tag_oid,
expected_target_oid=binding.target_oid,
expected_evidence_closure_oid=binding.evidence_closure_oid,
expected_approval_plan_id=binding.approval_plan_id,
)
source_registry = source_snapshot["record"]
source_registry_bytes = source_snapshot["bytes"]
verify_source_registry_git_identity(git_dir, source_registry)
except RegistryConvergenceError as exc:
raise ReleaseBlocked(f"SOURCE_REGISTRY_NOT_CONVERGED:{exc}") from exc
if source_snapshot["source_registry_main_oid"] != source_registry_main_oid:
raise ReconciliationRequired("source registry main OID changed during build")
if not _ancestor(git_dir, binding.target_oid, source_registry_main_oid):
raise ManualOwnerReviewRequired("release target is not an ancestor of source registry main")
source_registry_path = source_snapshot["source_registry_path"]
source_registry_sha256 = source_snapshot["source_registry_sha256"]
epoch = _commit_epoch(git_dir, binding.target_oid)
output_root.mkdir(parents=True, exist_ok=True)
stem = f"aios-{VERSION}-{binding.target_oid[:12]}"
output = output_root / (stem + ".tar.zst")
with tempfile.TemporaryDirectory(prefix="v2r2c1-artifact-") as temp:
root = Path(temp) / "payload"
root.mkdir(parents=True)
source_paths = {
"active-runtime/app/deployment_state.py": "active-runtime/app/deployment_state.py",
"active-runtime/app/v2r1c6_privacy_tools.py": "active-runtime/app/v2r1c6_privacy_tools.py",
"versions/V2R2C1.json": "versions/V2R2C1.json",
}
for destination, source in source_paths.items():
target = root / destination
target.parent.mkdir(parents=True, exist_ok=True)
if destination == source_registry_path:
target.write_bytes(source_registry_bytes)
else:
target.write_bytes(_payload_file(git_dir, binding.target_oid, source))
generated: dict[str, str] = {
"VERSION": VERSION + "\n",
"active-runtime/app/v2r2c1_health_service.py": (
"from v2r1c6_privacy_tools import main\n\n"
"if __name__ == '__main__':\n raise SystemExit(main())\n"
),
"deploy/aios-v2/systemd/aios-v2r1c7-production.service": (
"[Unit]\nDescription=AIOS V2R2C1 governed health contract service\n"
"After=network.target\n\n[Service]\nType=simple\n"
"User=aios-v2r1c7\nGroup=aios-v2r1c7\n"
"WorkingDirectory=/opt/aios/v2r2c1\n"
"EnvironmentFile=/etc/aios/aios-v2r2c1.env\n"
"ExecStart=/usr/bin/python3 /opt/aios/v2r2c1/active-runtime/app/v2r2c1_health_service.py --serve --host 127.0.0.1 --port ${AIOS_V2R1C7_PORT}\n"
"Restart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nPrivateTmp=true\n"
"ProtectHome=true\nProtectSystem=strict\nProtectKernelLogs=true\n"
"ProtectKernelModules=true\nProtectKernelTunables=true\nProtectControlGroups=true\n"
"RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\nIPAddressDeny=any\n"
"IPAddressAllow=localhost\nUMask=0077\n\n[Install]\nWantedBy=multi-user.target\n"
),
"deploy/aios-v2/v2r2c1-runtime-config.json": json.dumps({
"schema": "aios.v2r2c1.production-runtime.v1",
"version": VERSION,
"target_oid": binding.target_oid,
"service": SERVICE,
"port": PORT,
"health_path": "/health",
"deployment_state_source": "environment_and_state_file",
}, sort_keys=True, separators=(",", ":")) + "\n",
"deploy/aios-v2/v2r2c1-rollback.json": json.dumps({
"schema": "aios.v2r2c1.rollback-metadata.v1",
"rollback_version": "V2R1C7",
"rollback_service": SERVICE,
"rollback_target_is_previous_production": True,
"automatic_rollback": False,
}, sort_keys=True, separators=(",", ":")) + "\n",
"scripts/probe_v2r2c1_health.py": (
"import json,sys,urllib.request\n"
"url=sys.argv[1] if len(sys.argv)>1 else 'http://127.0.0.1:18927/health'\n"
"with urllib.request.urlopen(url, timeout=5) as r: print(json.dumps(json.load(r), sort_keys=True))\n"
),
"scripts/rollback_v2r2c1_release.sh": "#!/bin/sh\nset -eu\nprintf '%s\\n' 'rollback requires governed owner-approved executor'\nexit 2\n",
}
for rel, text in generated.items():
target = root / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(text, encoding="utf-8")
if rel.startswith("scripts/"):
target.chmod(0o755)
metadata = {
"schema": "aios.release-artifact-build.v3",
"version": VERSION,
"repository": REPOSITORY,
"release_target_oid": binding.target_oid,
"source_registry_main_oid": source_registry_main_oid,
"source_registry_path": source_registry_path,
"source_registry_sha256": source_registry_sha256,
"source_head_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"source_date_epoch": epoch,
"clean_source": True,
"executable_payload": True,
"production_deployed": False,
"tag_created": False,
"baseline_changed": False,
"secrets_included": False,
"source_registry": {
"path": source_registry_path,
"version": source_registry["version"],
"tag_oid": source_registry["tag_oid"],
"target_oid": source_registry["target_oid"],
"peeled_target_oid": source_registry["peeled_target_oid"],
"evidence_closure_oid": source_registry["evidence_closure_oid"],
"approval_plan_id": source_registry["approval_plan_id"],
"source_registry_main_oid": source_registry_main_oid,
"source_registry_sha256": source_registry_sha256,
},
"provenance": {
"release_payload_source": {
"commit_oid": binding.target_oid,
"role": "release_target",
},
"source_registry_source": {
"commit_oid": source_registry_main_oid,
"path": source_registry_path,
"sha256": source_registry_sha256,
"role": "source_registry_convergence",
},
"release_target_is_ancestor_of_source_registry_main": True,
"local_annotated_tag": tag_lineage,
},
}
(root / "BUILD-METADATA.json").write_bytes(canonical(metadata))
(root / "ROLLBACK-METADATA.json").write_bytes(canonical(json.loads((root / "deploy/aios-v2/v2r2c1-rollback.json").read_text(encoding="utf-8"))))
names = sorted("payload/" + str(path.relative_to(root)).replace(os.sep, "/") for path in root.rglob("*") if path.is_file())
(root / "ARTIFACT-MANIFEST.json").write_bytes(canonical({
"schema": "aios.release-artifact-manifest.v3",
"version": VERSION,
"release_target_oid": binding.target_oid,
"source_registry_main_oid": source_registry_main_oid,
"source_registry_path": source_registry_path,
"source_registry_sha256": source_registry_sha256,
"source_head_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"repository": REPOSITORY,
"files": names + ["payload/ARTIFACT-MANIFEST.json"],
"deployable": True,
"executable_payload": True,
"production_deployed": False,
"tag_created": False,
"baseline_changed": False,
}))
tar_path = Path(temp) / "release.tar"
with tarfile.open(tar_path, "w") as archive:
for path in sorted(root.rglob("*")):
relative = str(path.relative_to(root)).replace(os.sep, "/")
info = archive.gettarinfo(str(path), arcname="payload/" + relative)
info.uid = info.gid = 0
info.uname = "root"
info.gname = "root"
info.mtime = epoch
info.mode = 0o755 if path.is_dir() or path.suffix == ".sh" else 0o644
if path.is_file():
with path.open("rb") as stream:
archive.addfile(info, stream)
else:
archive.addfile(info)
subprocess.run(["zstd", "-T1", "-19", "--no-progress", "-q", "-f", str(tar_path), "-o", str(output)], check=True, timeout=120)
digest = sha256_file(output)
receipt = {
"schema": "aios.release-artifact-receipt.v3",
"version": VERSION,
"repository": REPOSITORY,
"release_target_oid": binding.target_oid,
"source_registry_main_oid": source_registry_main_oid,
"source_registry_path": source_registry_path,
"source_registry_sha256": source_registry_sha256,
"source_head_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact": str(output),
"artifact_sha256": digest,
"artifact_size": output.stat().st_size,
"deployable": True,
"executable_payload": True,
"production_deployed": False,
"tag_created": False,
"baseline_changed": False,
"source_registry_path": source_registry_path,
"source_registry_identity": source_registry,
"provenance": {
"release_payload_source": binding.target_oid,
"source_registry_source": source_registry_main_oid,
"release_target_is_ancestor_of_source_registry_main": True,
"local_annotated_tag": tag_lineage,
},
}
atomic_json(output.with_suffix(output.suffix + ".receipt.json"), receipt, mode=0o640)
return receipt
class GovernedProductionReleaseExecutor:
def __init__(self, config: Any):
self.config = config
self.binding = ReleaseBinding()
self.binding.validate()
self.root = Path(config.state_root) / "v2r2c1-governed-production-release"
self.state = ReleaseState(self.root, self.binding)
workspace = self.root / "publication-workspace"
workspace.mkdir(parents=True, exist_ok=True)
self.publisher = TagPublisher(
workspace,
GitTagBackend(workspace, authoritative_repo=Path(config.authoritative_git_dir)),
GiteaTagClient(config.api_base, REPOSITORY, Path(config.token_path)),
config.remote_name,
state_root=self.root / "tag-publication",
)
def _request(self, request: Mapping[str, Any]) -> PublicationRequest:
binding = self.binding
for key, expected in binding.as_dict().items():
if key in request and request[key] != expected:
raise ManualOwnerReviewRequired("release request binding mismatch:" + key)
return PublicationRequest(binding.version, binding.target_oid, binding.evidence_closure_oid, binding.approval_plan_id)
def approval_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
if request.get("execute") is not False:
raise ReleaseBlocked("TAG_APPROVAL_REQUEST_MUST_BE_DRY_RUN")
owner = verify_owner_approval(request, self.binding)
with StateLock(self.root / "release.lock"):
current = self.state.value.get("approval")
approval_digest = hashlib.sha256(canonical(owner)).hexdigest()
record = {
"schema": "aios.v2r2c1.tag-approval-request.v1",
"binding": self.binding.as_dict(),
"owner_approval": owner,
"owner_approval_digest_sha256": approval_digest,
"execute": False,
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
if current and current != record:
raise ManualOwnerReviewRequired("persisted Owner approval differs")
self.state.value["approval"] = record
self.state.record("APPROVAL_REQUEST_RECORDED", "tag_approval_request", record)
return {"status": "RECORDED", "execute": False, "binding": self.binding.as_dict(), "owner_approval_digest_sha256": approval_digest}
def _require_approval(self) -> None:
approval = self.state.value.get("approval")
if not isinstance(approval, Mapping) or approval.get("binding") != self.binding.as_dict():
raise ReleaseBlocked("OWNER_APPROVAL_REQUEST_NOT_RECORDED")
def publish_tag(self, request: Mapping[str, Any]) -> dict[str, Any]:
if request.get("execute") is not True:
raise ReleaseBlocked("TAG_PUBLICATION_REQUIRES_EXECUTE_TRUE")
self._require_approval()
publication = self._request(request)
with StateLock(self.root / "release.lock"):
try:
verified = self.publisher._verify_publication(publication, allow_push=True, previously_deployed=False)
except PublicationManualOwnerReviewRequired as exc:
self.state.fail("MANUAL_OWNER_REVIEW_REQUIRED", str(exc))
raise ManualOwnerReviewRequired(str(exc)) from exc
except PublicationPending as exc:
self.state.fail("REMOTE_PUSH_PENDING", str(exc))
raise ReconciliationRequired(str(exc)) from exc
except PublicationReconciliationRequired as exc:
self.state.fail("RECONCILIATION_REQUIRED", str(exc))
raise ReconciliationRequired(str(exc)) from exc
result = dict(verified)
if result.get("tag_oid") != self.binding.tag_oid:
self.state.fail(
"MANUAL_OWNER_REVIEW_REQUIRED",
"same-name tag object differs from the approved V2R2C1 tag",
)
raise ManualOwnerReviewRequired("approved tag object mismatch")
self.state.record("TAG_VERIFIED", "tag", result)
return {"status": "REMOTE_VERIFIED", **result}
def tag_status(self, request: Mapping[str, Any]) -> dict[str, Any]:
self._request(request)
return self.publisher.get_tag_publication_status(VERSION)
def build_artifact(self, request: Mapping[str, Any]) -> dict[str, Any]:
self._require_approval()
self._request(request)
receipt = build_production_artifact(Path(self.config.authoritative_git_dir), self.binding, self.root / "artifacts")
self.state.record("ARTIFACT_VERIFIED", "production_artifact", receipt)
return {"status": "VERIFIED", **receipt}
def execute_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
self._require_approval()
publication = self._request(request)
tag_state = self.state.value.get("completed", {}).get("tag")
if not isinstance(tag_state, Mapping) or tag_state.get("state") not in {"GITEA_API_CONFIRMED", "REMOTE_VERIFIED"}:
raise ReleaseBlocked("REMOTE_TAG_PROOF_REQUIRED")
if tag_state.get("tag_oid") != self.binding.tag_oid:
raise ManualOwnerReviewRequired("approved tag object mismatch")
artifact = self.state.value.get("completed", {}).get("production_artifact")
if not isinstance(artifact, Mapping):
artifact = self.build_artifact(request)
value = {
**self.binding.as_dict(),
"tag_oid": tag_state.get("tag_oid") or tag_state.get("gitea_tag_oid"),
"artifact_path": artifact["artifact"],
"artifact_size": int(artifact["artifact_size"]),
"artifact_sha256": artifact["artifact_sha256"],
}
for name in (
"source_registry_main_oid",
"source_registry_path",
"source_registry_sha256",
):
if name in artifact:
value[name] = artifact[name]
# The production adapter is privileged and is run through the root
# approved-release service boundary by the caller. This handler is
# still useful for a root-owned staging invocation and remains
# fail-closed when the adapter is not available.
import v2r2c1_production_adapter
runtime = v2r2c1_production_adapter.execute_request({"schema": "aios.production-executor-request.v1", "operation": "apply", **value})
if runtime.get("status") != "ACTIVE_VERIFIED":
self.state.fail("RECONCILIATION_REQUIRED", "production health did not verify")
raise ReconciliationRequired("production health did not verify")
self.state.record("PRODUCTION_VERIFIED", "production", runtime)
return {"status": "PRODUCTION_VERIFIED", "runtime": runtime, "artifact": artifact, "tag_oid": value["tag_oid"]}
def consistency(self, request: Mapping[str, Any]) -> dict[str, Any]:
publication = self._request(request)
result = self.publisher.get_release_consistency(publication)
result["release_binding"] = self.binding.as_dict()
result["production_health"] = _health(self.config.health_url, self.binding, self.state.value.get("completed", {}).get("production_artifact", {}))
result["registry"] = _read_json(Path(self.config.registry_root) / f"{VERSION}.json")
result["baseline"] = _read_json(Path(self.config.baseline_path))
try:
assert_source = Path(self.config.registry_root) / f"{VERSION}.json"
if not assert_source.is_file():
raise RegistryConvergenceError("runtime_registry_entry_missing")
assert_source_registry_convergence(
assert_source,
repo_git_dir=self.config.authoritative_git_dir,
)
source_check = {"status": "PASS", "path": str(assert_source)}
except RegistryConvergenceError as exc:
source_check = {"status": "FAIL", "error": str(exc)}
result["source_registry"] = source_check
result.setdefault("checks", {})["source_registry"] = source_check["status"] == "PASS"
if result.get("status") == "PASS" and source_check["status"] != "PASS":
result["status"] = "FAIL"
return result
def acceptance(self, request: Mapping[str, Any]) -> dict[str, Any]:
result = self.consistency(request)
result["accepted"] = result.get("status") == "PASS" and result.get("production_health", {}).get("status") == "ACTIVE_VERIFIED"
self.state.record("ACCEPTED" if result["accepted"] else "RECONCILIATION_REQUIRED", "acceptance", result)
return result
def _read_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
return {"status": "MISSING", "path": str(path)}
if not isinstance(value, dict):
return {"status": "INVALID", "path": str(path)}
return {"status": "PRESENT", "path": str(path), **value}
def _health(url: str, binding: ReleaseBinding, artifact: Mapping[str, Any]) -> dict[str, Any]:
import urllib.request
try:
with urllib.request.urlopen(url, timeout=5) as response:
body = json.loads(response.read().decode("utf-8"))
status = response.status
except Exception as exc:
return {"status": "UNAVAILABLE", "error": type(exc).__name__}
expected_artifact = artifact.get("artifact_sha256")
exact = (
status == 200
and body.get("version") == binding.version
and body.get("commit_oid") == binding.target_oid
and (not expected_artifact or body.get("artifact_sha256") == expected_artifact)
and body.get("deployment_state") == "ACTIVE"
and body.get("production_deployed") is True
)
return {"status": "ACTIVE_VERIFIED" if exact else "CONFLICT", "http_status": status, **body}
def build_executor_config(config: Any) -> GovernedProductionReleaseExecutor:
return GovernedProductionReleaseExecutor(config)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("operation", choices=("build-artifact", "status"))
parser.add_argument("--git-dir", type=Path, default=Path("/data/aios-gitops/repo.git"))
parser.add_argument("--output-root", type=Path, default=Path("/var/lib/aios-gitops/approved-artifacts/V2R2C1"))
args = parser.parse_args()
binding = ReleaseBinding()
if args.operation == "build-artifact":
print(json.dumps(build_production_artifact(args.git_dir, binding, args.output_root), sort_keys=True))
else:
print(json.dumps({"binding": binding.as_dict(), "status": "READY"}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+225
View File
@@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""AIOS durable autonomous release-campaign authorization.
The campaign receipt represents one explicit Owner policy delegation. It is not
a reusable Owner credential and it is not an unrestricted Git/production token.
Normal release operations are allowed only when all machine gates in the
campaign policy succeed. Formal tag conflicts, non-fast-forward main divergence,
history rewrite, private-signing-material exposure, repository identity conflict
or campaign revocation remain fail-closed/manual-review conditions.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import re
import stat
from typing import Any, Mapping
OID_RE = re.compile(r"^[0-9a-f]{40}$")
CAMPAIGN_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{3,127}$")
SCHEMA = "aios.release-campaign-authorization.v1"
ALLOWED_ENGINEERING = frozenset({
"create_task_branch",
"create_worktree",
"append_commit",
"push_task_branch",
"repair_task_branch",
"create_pr",
"update_pr",
"run_required_checks",
"publish_truthful_status",
"protected_merge",
})
ALLOWED_RECONCILIATION = frozenset({
"fetch_gitea_main",
"verify_ancestor",
"authority_main_ff_cas",
"refresh_runtime_context",
})
ALLOWED_RELEASE = frozenset({
"deterministic_build",
"retain_artifact",
"materialize_approved_artifact",
"staging_install",
"health_check",
"restart_test",
"rollback_readiness_test",
"create_evidence_commit",
"generate_exact_binding",
"derive_campaign_acceptance",
})
ALLOWED_PRODUCTION = frozenset({
"create_annotated_tag",
"publish_tag",
"verify_tag",
"deploy_exact_artifact",
"verify_production_health",
"update_runtime_registry",
"update_production_baseline",
"update_baseline_sidecar",
})
DENIED = frozenset({
"force_push",
"non_fast_forward_main_write",
"direct_main_push",
"published_history_rewrite",
"formal_tag_rewrite",
"formal_tag_delete",
"overwrite_conflicting_same_name_tag",
"expose_private_signing_material",
"commit_plaintext_secret",
"repository_identity_change",
"weaken_branch_protection",
})
class CampaignError(RuntimeError):
pass
def canonical_json(value: Any) -> bytes:
return (
json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
+ "\n"
).encode("utf-8")
def sha256_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise CampaignError(f"invalid_file:{path}")
st = path.lstat()
if st.st_uid != 0:
raise CampaignError(f"root_owner_required:{path}")
if stat.S_IMODE(st.st_mode) & 0o022:
raise CampaignError(f"group_world_writable_rejected:{path}")
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise CampaignError(f"invalid_json:{path}") from exc
if not isinstance(value, dict):
raise CampaignError(f"json_object_required:{path}")
return value
def _exact_set(value: Any, expected: frozenset[str], name: str) -> None:
if not isinstance(value, list) or frozenset(value) != expected:
raise CampaignError(f"{name}_mismatch")
def validate_policy(value: Mapping[str, Any]) -> dict[str, Any]:
required = {
"schema",
"campaign_id",
"generation",
"owner_identity",
"owner_role",
"repository",
"lineage_anchor_oid",
"control_plane_branch",
"activation_state",
"active",
"revoked",
"valid_until_revoked",
"allowed_engineering",
"allowed_reconciliation",
"allowed_release",
"allowed_production",
"denied",
"normal_owner_interaction_required",
}
if set(value) != required:
raise CampaignError("campaign_fields_invalid")
if value["schema"] != SCHEMA:
raise CampaignError("campaign_schema_invalid")
if not isinstance(value["campaign_id"], str) or not CAMPAIGN_RE.fullmatch(value["campaign_id"]):
raise CampaignError("campaign_id_invalid")
if value["generation"] != 1:
raise CampaignError("campaign_generation_invalid")
if value["owner_identity"] != "aiman" or value["owner_role"] != "owner":
raise CampaignError("campaign_owner_invalid")
if value["repository"] != "aios/aios":
raise CampaignError("campaign_repository_invalid")
if not isinstance(value["lineage_anchor_oid"], str) or not OID_RE.fullmatch(value["lineage_anchor_oid"]):
raise CampaignError("campaign_anchor_invalid")
branch = value["control_plane_branch"]
if branch != "agent/autonomous-release-campaign-v1-20260807":
raise CampaignError("campaign_branch_invalid")
if value["activation_state"] not in {"PENDING_CONTROL_PLANE_MERGE", "ACTIVE"}:
raise CampaignError("campaign_activation_state_invalid")
if value["revoked"] is not False or value["valid_until_revoked"] is not True:
raise CampaignError("campaign_lifecycle_invalid")
if value["normal_owner_interaction_required"] is not False:
raise CampaignError("campaign_owner_interaction_policy_invalid")
if value["activation_state"] == "ACTIVE" and value["active"] is not True:
raise CampaignError("campaign_active_state_invalid")
if value["activation_state"] == "PENDING_CONTROL_PLANE_MERGE" and value["active"] is not False:
raise CampaignError("campaign_pending_state_invalid")
_exact_set(value["allowed_engineering"], ALLOWED_ENGINEERING, "allowed_engineering")
_exact_set(value["allowed_reconciliation"], ALLOWED_RECONCILIATION, "allowed_reconciliation")
_exact_set(value["allowed_release"], ALLOWED_RELEASE, "allowed_release")
_exact_set(value["allowed_production"], ALLOWED_PRODUCTION, "allowed_production")
_exact_set(value["denied"], DENIED, "denied")
return dict(value)
@dataclass(frozen=True)
class CampaignAuthorization:
path: Path
data: dict[str, Any]
digest: str
@classmethod
def load(
cls,
path: Path,
*,
sidecar: Path | None = None,
revocation: Path | None = None,
require_active: bool = True,
) -> "CampaignAuthorization":
data = validate_policy(secure_json(path))
raw = path.read_bytes()
digest = sha256_bytes(raw)
sidecar = sidecar or path.with_suffix(path.suffix + ".sha256")
if not sidecar.is_file() or sidecar.is_symlink():
raise CampaignError("campaign_digest_sidecar_missing")
token = sidecar.read_text(encoding="ascii").strip().split()[0]
if token != digest:
raise CampaignError("campaign_digest_sidecar_mismatch")
if revocation and revocation.exists():
revoked = secure_json(revocation)
if revoked.get("campaign_id") == data["campaign_id"] and revoked.get("revoked") is True:
raise CampaignError("campaign_revoked")
if require_active and (data["activation_state"] != "ACTIVE" or data["active"] is not True):
raise CampaignError("campaign_not_active")
return cls(path=path, data=data, digest=digest)
@property
def campaign_id(self) -> str:
return str(self.data["campaign_id"])
@property
def anchor_oid(self) -> str:
return str(self.data["lineage_anchor_oid"])
def assert_operation(self, operation: str) -> None:
if operation in DENIED:
raise CampaignError(f"campaign_operation_denied:{operation}")
universe = (
ALLOWED_ENGINEERING
| ALLOWED_RECONCILIATION
| ALLOWED_RELEASE
| ALLOWED_PRODUCTION
)
if operation not in universe:
raise CampaignError(f"campaign_operation_out_of_scope:{operation}")
+427
View File
@@ -0,0 +1,427 @@
#!/usr/bin/env python3
"""One-interaction bootstrap for the AIOS autonomous release campaign.
A single real local `aiman` login confirmation does two things atomically at the
policy layer:
1. Writes a root-owned, digest-sidecar protected campaign authorization receipt
in PENDING_CONTROL_PLANE_MERGE state. It grants no immediate production side
effect; the campaign becomes ACTIVE only after the campaign control-plane PR
is merged and machine activation verifies lineage and implementation.
2. Invokes the existing OwnerAuthorizationBroker to issue only the narrow
engineering writer needed to implement that control-plane PR.
No reusable Owner token is minted.
"""
from __future__ import annotations
import argparse
import fcntl
import hashlib
import json
import os
from pathlib import Path
import secrets
import socket
import struct
import tempfile
import time
from typing import Any, Mapping
from owner_local_auth_ingress import (
BootstrapError,
canonical_json,
read_frame,
verify_peer_session,
write_frame,
)
from release_campaign_authorization import (
ALLOWED_ENGINEERING,
ALLOWED_PRODUCTION,
ALLOWED_RECONCILIATION,
ALLOWED_RELEASE,
DENIED,
SCHEMA,
validate_policy,
)
BEGIN = "aios.release-campaign-bootstrap.begin.v1"
CHALLENGE = "aios.release-campaign-bootstrap.challenge.v1"
CONFIRM = "aios.release-campaign-bootstrap.confirm.v1"
RESULT = "aios.release-campaign-bootstrap.result.v1"
TASK_ID = "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1"
TARGET_BRANCH = "agent/autonomous-release-campaign-v1-20260807"
REPOSITORY = "aios/aios"
class CampaignBootstrapError(RuntimeError):
pass
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
h.update(block)
return h.hexdigest()
def atomic_json_new(path: Path, value: Mapping[str, Any]) -> None:
if path.exists() or path.is_symlink():
raise CampaignBootstrapError(f"target_exists:{path}")
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
if os.geteuid() == 0:
os.fchown(fd, 0, 0)
with os.fdopen(fd, "wb", closefd=True) as stream:
stream.write(canonical_json(dict(value)))
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
os.chmod(path, 0o600)
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(dfd)
finally:
os.close(dfd)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
def atomic_sidecar(path: Path, digest: str, target_name: str) -> None:
if path.exists() or path.is_symlink():
raise CampaignBootstrapError(f"sidecar_exists:{path}")
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
if os.geteuid() == 0:
os.fchown(fd, 0, 0)
with os.fdopen(fd, "w", encoding="ascii", closefd=True) as stream:
stream.write(f"{digest} {target_name}\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
os.chmod(path, 0o600)
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(dfd)
finally:
os.close(dfd)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
def writer_request(base_oid: str) -> dict[str, Any]:
return {
"action": "AUTHORIZE_TASK_WRITER_PROVISIONING",
"task_id": TASK_ID,
"repository": REPOSITORY,
"base_ref": "refs/heads/main",
"base_oid": base_oid,
"target_branch": TARGET_BRANCH,
"one_time_use": True,
"expires_in_seconds": 300,
"production_change_authorized": False,
"tag_change_authorized": False,
"baseline_change_authorized": False,
"main_write_authorized": False,
"allowed_operations": [
"create_worktree",
"create_target_branch_if_absent",
"commit_to_target_branch",
"push_target_branch",
],
"denied_operations": [
"direct_main_push",
"force_push",
"tag_create",
"tag_update",
"tag_delete",
"branch_delete",
"repository_admin",
"baseline_write",
"production_deploy",
"production_marker_write",
"history_rewrite",
],
}
def campaign_policy(campaign_id: str, base_oid: str) -> dict[str, Any]:
value = {
"schema": SCHEMA,
"campaign_id": campaign_id,
"generation": 1,
"owner_identity": "aiman",
"owner_role": "owner",
"repository": REPOSITORY,
"lineage_anchor_oid": base_oid,
"control_plane_branch": TARGET_BRANCH,
"activation_state": "PENDING_CONTROL_PLANE_MERGE",
"active": False,
"revoked": False,
"valid_until_revoked": True,
"allowed_engineering": sorted(ALLOWED_ENGINEERING),
"allowed_reconciliation": sorted(ALLOWED_RECONCILIATION),
"allowed_release": sorted(ALLOWED_RELEASE),
"allowed_production": sorted(ALLOWED_PRODUCTION),
"denied": sorted(DENIED),
"normal_owner_interaction_required": False,
}
return validate_policy(value)
def invoke_writer_broker(base_oid: str) -> dict[str, Any]:
from owner_authorization_broker import (
OwnerAuthorizationBroker,
OwnerAuthorizationBrokerConfig,
OwnerPrincipal,
)
config = OwnerAuthorizationBrokerConfig.from_environment()
if "aiman" not in config.allowed_owner_identities:
raise CampaignBootstrapError("aiman_not_allowed_by_live_broker")
if "commit:create" not in config.allowed_intake_scopes:
raise CampaignBootstrapError("commit_create_not_allowed_by_live_broker")
principal = OwnerPrincipal(
identity="aiman",
role="owner",
authenticated_scope="commit:create",
token_scopes=frozenset({"commit:create"}),
credential_id="",
)
return OwnerAuthorizationBroker(config=config).authorize_and_provision(
writer_request(base_oid),
principal,
)
def safe_broker_result(value: Mapping[str, Any]) -> dict[str, Any]:
allowed = {
"status",
"approval_plan_id",
"grant_id",
"grant_digest_sha256",
"branch_oid",
"branch_ref_created",
"grant_consumed",
"owner_authorization_event_id",
"owner_authorization_reference",
"owner_authorization_digest_sha256",
"owner_authorization_consumed",
"owner_authorization_idempotent",
}
lowered = " ".join(str(k).lower() for k in value)
for secret_word in ("token", "password", "private_key", "secret_value", "bearer"):
if secret_word in lowered:
raise CampaignBootstrapError("broker_response_sensitive_field_rejected")
return {k: value[k] for k in sorted(allowed) if k in value}
def serve(
*,
socket_path: Path,
state_root: Path,
base_oid: str,
campaign_id: str,
timeout: int,
) -> int:
if os.geteuid() != 0:
raise CampaignBootstrapError("server_root_required")
if len(base_oid) != 40 or any(c not in "0123456789abcdef" for c in base_oid):
raise CampaignBootstrapError("base_oid_invalid")
policy = campaign_policy(campaign_id, base_oid)
campaign_dir = state_root / campaign_id
campaign_path = campaign_dir / "authorization.json"
sidecar_path = campaign_dir / "authorization.json.sha256"
socket_path.parent.mkdir(parents=True, exist_ok=True)
if socket_path.exists() or socket_path.is_symlink():
raise CampaignBootstrapError("socket_already_exists")
import pwd
owner = pwd.getpwnam("aiman")
os.chown(socket_path.parent, 0, owner.pw_gid)
os.chmod(socket_path.parent, 0o750)
listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
listener.bind(str(socket_path))
os.chown(socket_path, 0, owner.pw_gid)
os.chmod(socket_path, 0o660)
listener.listen(1)
listener.settimeout(timeout)
conn, _ = listener.accept()
with conn:
raw = conn.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i"))
pid, uid, gid = struct.unpack("3i", raw)
peer = verify_peer_session(pid, uid, gid, "aiman")
if read_frame(conn) != {"schema": BEGIN, "action": "BEGIN"}:
raise CampaignBootstrapError("client_begin_invalid")
challenge = secrets.token_hex(12)
issued = time.monotonic()
summary = {
"campaign_id": campaign_id,
"repository": REPOSITORY,
"lineage_anchor_oid": base_oid,
"control_plane_branch": TARGET_BRANCH,
"normal_owner_interaction_after_activation": False,
"allowed_engineering": sorted(ALLOWED_ENGINEERING),
"allowed_reconciliation": sorted(ALLOWED_RECONCILIATION),
"allowed_release": sorted(ALLOWED_RELEASE),
"allowed_production": sorted(ALLOWED_PRODUCTION),
"denied": sorted(DENIED),
"writer_request": writer_request(base_oid),
}
write_frame(
conn,
{
"schema": CHALLENGE,
"challenge": challenge,
"expires_in_seconds": 120,
"summary": summary,
},
)
confirm = read_frame(conn)
phrase = f"AUTHORIZE CAMPAIGN {campaign_id} {challenge}"
if (
confirm.get("schema") != CONFIRM
or confirm.get("challenge") != challenge
or confirm.get("confirmation") != phrase
):
raise CampaignBootstrapError("campaign_confirmation_invalid")
if time.monotonic() - issued > 120:
raise CampaignBootstrapError("campaign_confirmation_expired")
# First issue the narrow engineering writer. If this fails, do not
# persist a campaign authorization that cannot be implemented.
broker = safe_broker_result(invoke_writer_broker(base_oid))
if broker.get("status") != "READY_FOR_CODE_AUTHORING":
raise CampaignBootstrapError("writer_not_ready")
atomic_json_new(campaign_path, policy)
digest = sha256_file(campaign_path)
atomic_sidecar(sidecar_path, digest, campaign_path.name)
receipt = {
"schema": "aios.release-campaign-bootstrap-receipt.v1",
"status": "CAMPAIGN_PENDING_CONTROL_PLANE_MERGE",
"campaign_id": campaign_id,
"campaign_authorization_path": str(campaign_path),
"campaign_authorization_sha256": digest,
"owner_identity": "aiman",
"owner_role": "owner",
"authentication": {
"method": "local_so_peercred_loginuid_tty",
"uid": peer["uid"],
"loginuid": peer["loginuid"],
"tty": peer["tty"],
},
"writer": broker,
"reusable_owner_token_created": False,
"production_side_effect_executed": False,
"completed_at_unix": int(time.time()),
}
receipt_path = campaign_dir / "bootstrap-receipt.json"
atomic_json_new(receipt_path, receipt)
write_frame(
conn,
{
"schema": RESULT,
"ok": True,
"campaign_id": campaign_id,
"campaign_authorization_path": str(campaign_path),
"campaign_authorization_sha256": digest,
"writer": broker,
},
)
return 0
finally:
listener.close()
try:
socket_path.unlink()
except FileNotFoundError:
pass
def client(socket_path: Path) -> int:
import sys
if not sys.stdin.isatty() or not sys.stdout.isatty():
raise CampaignBootstrapError("interactive_tty_required")
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
conn.connect(str(socket_path))
with conn:
write_frame(conn, {"schema": BEGIN, "action": "BEGIN"})
challenge = read_frame(conn)
if challenge.get("schema") != CHALLENGE:
raise CampaignBootstrapError("challenge_invalid")
print("\nAIOS Autonomous Release Campaign authorization")
print(json.dumps(challenge["summary"], indent=2, sort_keys=True))
phrase = (
f"AUTHORIZE CAMPAIGN "
f"{challenge['summary']['campaign_id']} "
f"{challenge['challenge']}"
)
typed = input(f"\nType exactly:\n{phrase}\n> ").strip()
if typed != phrase:
raise CampaignBootstrapError("confirmation_mismatch")
write_frame(
conn,
{
"schema": CONFIRM,
"challenge": challenge["challenge"],
"confirmation": typed,
},
)
result = read_frame(conn)
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result.get("ok") is True else 3
def main() -> int:
p = argparse.ArgumentParser()
mode = p.add_mutually_exclusive_group(required=True)
mode.add_argument("--serve", action="store_true")
mode.add_argument("--client", action="store_true")
p.add_argument("--socket", type=Path, default=Path("/run/aios-release-campaign/owner.sock"))
p.add_argument(
"--state-root",
type=Path,
default=Path("/var/lib/aios-gitops/release-campaigns"),
)
p.add_argument("--base-oid")
p.add_argument("--campaign-id")
p.add_argument("--timeout", type=int, default=300)
a = p.parse_args()
if a.serve:
if not a.base_oid or not a.campaign_id:
raise CampaignBootstrapError("server_binding_required")
return serve(
socket_path=a.socket,
state_root=a.state_root,
base_oid=a.base_oid,
campaign_id=a.campaign_id,
timeout=a.timeout,
)
return client(a.socket)
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignBootstrapError, BootstrapError) as exc:
print(json.dumps({"status": "BLOCKED", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Durable state model for the AIOS autonomous release campaign."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import tempfile
from typing import Any
STATES = (
"DEVELOPMENT",
"VALIDATING",
"PR_READY",
"MERGING",
"MAIN_RECONCILING",
"POSTMERGE_BUILDING",
"STAGING",
"EVIDENCE_CLOSING",
"BINDING_ACCEPTING",
"TAGGING",
"DEPLOYING",
"VERIFYING",
"REGISTRY_FINALIZING",
"BASELINE_FINALIZING",
"PRODUCTION_DEPLOYED",
)
RECOVERABLE = "RECOVERABLE_FAILURE"
RECONCILIATION = "RECONCILIATION_REQUIRED"
MANUAL = "MANUAL_OWNER_REVIEW_REQUIRED"
NEXT = {STATES[i]: STATES[i + 1] for i in range(len(STATES) - 1)}
MANUAL_REASONS = frozenset({
"formal_same_name_tag_conflict",
"force_push_required",
"published_history_rewrite_required",
"non_fast_forward_main_divergence",
"private_signing_material_exposure_required",
"repository_identity_conflict",
"campaign_revoked",
"operation_outside_campaign_scope",
})
class OrchestratorError(RuntimeError):
pass
@dataclass
class ReleaseState:
version: str
state: str = "DEVELOPMENT"
last_stable_state: str = "DEVELOPMENT"
recoverable_reason: str | None = None
def advance(self) -> None:
if self.state not in NEXT:
raise OrchestratorError(f"cannot_advance:{self.state}")
self.last_stable_state = self.state
self.state = NEXT[self.state]
self.recoverable_reason = None
def recoverable_failure(self, reason: str) -> None:
self.last_stable_state = self.state
self.state = RECOVERABLE
self.recoverable_reason = reason
def reconciliation_required(self, reason: str) -> None:
self.last_stable_state = self.state
self.state = RECONCILIATION
self.recoverable_reason = reason
def manual_review(self, reason: str) -> None:
if reason not in MANUAL_REASONS:
raise OrchestratorError("reason_not_manual_red_line")
self.last_stable_state = self.state
self.state = MANUAL
self.recoverable_reason = reason
def resume(self) -> None:
if self.state not in {RECOVERABLE, RECONCILIATION}:
raise OrchestratorError("state_not_resumable")
self.state = self.last_stable_state
def as_dict(self) -> dict[str, Any]:
return {
"schema": "aios.autonomous-release-state.v1",
"version": self.version,
"state": self.state,
"last_stable_state": self.last_stable_state,
"recoverable_reason": self.recoverable_reason,
"owner_interaction_required": self.state == MANUAL,
}
def atomic_state(path: Path, state: ReleaseState) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "w", encoding="utf-8", closefd=True) as stream:
json.dump(state.as_dict(), stream, sort_keys=True, indent=2)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
+73 -5
View File
@@ -7,7 +7,7 @@ Registry and authoritative runtime-anchor Baseline only.
Registry is finalized before Baseline. Baseline apply independently verifies:
- exact Authority annotated tag and peeled target
- exact ACTIVE production health
- exact V2R1C7 Registry record
- exact runtime Registry record and source-registry convergence
- exact previous Baseline file SHA-256 (CAS)
"""
from __future__ import annotations
@@ -25,6 +25,7 @@ import time
import urllib.error
import urllib.request
from pathlib import Path
from version_registry import assert_source_registry_convergence
from typing import Any, Mapping
OID_RE = re.compile(r"^[0-9a-f]{40}$")
@@ -97,11 +98,40 @@ def load_request() -> dict[str, Any]:
raise ValueError("approval_plan_id_invalid")
if "tag_oid" in value and not OID_RE.fullmatch(str(value["tag_oid"])):
raise ValueError("tag_oid_invalid")
if "source_registry_main_oid" in value and not OID_RE.fullmatch(
str(value["source_registry_main_oid"])
):
raise ValueError("source_registry_main_oid_invalid")
if "source_registry_sha256" in value and not SHA_RE.fullmatch(
str(value["source_registry_sha256"])
):
raise ValueError("source_registry_sha256_invalid")
if "source_registry_path" in value:
source_path = str(value["source_registry_path"])
if (
not source_path.startswith("versions/")
or ".." in Path(source_path).parts
or "\\" in source_path
or not source_path.endswith(".json")
):
raise ValueError("source_registry_path_invalid")
provenance = tuple(
value.get(name)
for name in (
"source_registry_main_oid",
"source_registry_path",
"source_registry_sha256",
)
)
if any(item is not None for item in provenance) and not all(
item is not None for item in provenance
):
raise ValueError("source_registry_provenance_incomplete")
return value
def exact_response(value: Mapping[str, Any], status: str, **extra: Any) -> dict[str, Any]:
return {
result = {
"status": status,
"version": value["version"],
"target_oid": value["target_oid"],
@@ -110,6 +140,14 @@ def exact_response(value: Mapping[str, Any], status: str, **extra: Any) -> dict[
"approval_plan_id": value["approval_plan_id"],
**extra,
}
for name in (
"source_registry_main_oid",
"source_registry_path",
"source_registry_sha256",
):
if name in value:
result[name] = value[name]
return result
def git(repository: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
@@ -154,7 +192,7 @@ def health(url: str, value: Mapping[str, Any]) -> dict[str, Any]:
def registry_record(value: Mapping[str, Any], tag_oid: str, production_health: Mapping[str, Any]) -> dict[str, Any]:
return {
result = {
"schema": "aios.production-registry.v2",
"version": value["version"],
"repository": "aios/aios",
@@ -175,10 +213,18 @@ def registry_record(value: Mapping[str, Any], tag_oid: str, production_health: M
"deployment_state": production_health["deployment_state"],
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
for name in (
"source_registry_main_oid",
"source_registry_path",
"source_registry_sha256",
):
if name in value:
result[name] = value[name]
return result
def registry_matches(record: Mapping[str, Any], value: Mapping[str, Any], tag_oid: str) -> bool:
return all(
matches = all(
record.get(key) == expected
for key, expected in {
"version": value["version"],
@@ -192,11 +238,16 @@ def registry_matches(record: Mapping[str, Any], value: Mapping[str, Any], tag_oi
"production_deployed": True,
}.items()
) and record.get("registered") is True and record.get("enabled") is True
return matches and all(
record.get(name) == value.get(name)
for name in ("source_registry_main_oid", "source_registry_path", "source_registry_sha256")
if name in value
)
def baseline_matches(record: Mapping[str, Any], value: Mapping[str, Any], tag_oid: str) -> bool:
current = record.get("current_baseline") or record.get("version")
return current == value["version"] and all(
matches = current == value["version"] and all(
record.get(key) == expected
for key, expected in {
"target_oid": value["target_oid"],
@@ -209,6 +260,11 @@ def baseline_matches(record: Mapping[str, Any], value: Mapping[str, Any], tag_oi
"status": "PRODUCTION_DEPLOYED",
}.items()
)
return matches and all(
record.get(name) == value.get(name)
for name in ("source_registry_main_oid", "source_registry_path", "source_registry_sha256")
if name in value
)
def registry_probe(args: argparse.Namespace, value: Mapping[str, Any]) -> dict[str, Any]:
@@ -305,6 +361,11 @@ def baseline_apply(args: argparse.Namespace, value: Mapping[str, Any]) -> dict[s
int(previous.get("generation", 0) or 0),
int(previous.get("authority_generation", 0) or 0),
) + 1
assert_source_registry_convergence(
registry_path,
repo_git_dir=args.repository,
source_registry_main_oid=value.get("source_registry_main_oid"),
)
record = {
"schema": "aios.production-baseline.v2",
"source_identity": "aios-gitops-runtime-anchor-baseline-authority-v1",
@@ -336,6 +397,13 @@ def baseline_apply(args: argparse.Namespace, value: Mapping[str, Any]) -> dict[s
"promoted_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"promoted_by": "aios-approved-production-release-executor",
}
for name in (
"source_registry_main_oid",
"source_registry_path",
"source_registry_sha256",
):
if name in value:
record[name] = value[name]
atomic_json(args.baseline_path, record)
return baseline_probe(args, value)
@@ -15,6 +15,8 @@ import subprocess
from pathlib import Path
from typing import Any, Mapping, Sequence
from aios_signer_broker import SignerBroker, SignerBrokerError, UnixSignerBroker
_SIGNER_FLAGS = (
"--namespace",
@@ -155,6 +157,36 @@ def _sign(
raise SignerContractError("signer_output_empty")
def run_signer_self_test(broker: SignerBroker | None = None) -> dict[str, str]:
active = broker or UnixSignerBroker()
try:
active.self_test()
except SignerBrokerError as exc:
raise SignerContractError(exc.code) from exc
return {"status": "PASS", "contract": "aios.signer-broker.v1"}
def _sign_with_broker(
broker: SignerBroker,
payload_kind: str,
input_path: Path,
signature_path: Path,
key_id_path: Path,
) -> None:
if signature_path.exists() or key_id_path.exists():
raise SignerContractError("contract_output_exists")
try:
signature, key_id = broker.sign(payload_kind, input_path.read_bytes())
except SignerBrokerError as exc:
raise SignerContractError(exc.code) from exc
if not signature or not key_id.strip():
raise SignerContractError("signer_output_empty")
signature_path.write_bytes(signature)
key_id_path.write_text(key_id.strip() + "\n", encoding="utf-8")
os.chmod(signature_path, 0o600)
os.chmod(key_id_path, 0o600)
def _manifest(grant_dir: Path, names: Sequence[str]) -> None:
rows = []
for name in names:
@@ -284,6 +316,8 @@ def run_compatible_signer(
signer_command: Sequence[str],
grant_dir: Path,
result_path: Path,
*,
broker: SignerBroker | None = None,
) -> dict[str, str]:
try:
if not _supports_deployed_signer(signer_command):
@@ -318,22 +352,21 @@ def run_compatible_signer(
"signing_key_identity": signer,
}
prepared = _prepare(grant_dir)
config_path = Path(
os.environ.get("AIOS_GRANT_ROOT_CONFIG", "/etc/aios/grant-root/config.json")
)
config = _read(config_path)
grant_namespace = str(config["grant_namespace"])
approval_namespace = str(config["approval_namespace"])
_sign(
signer_command,
approval_namespace,
active_broker = broker or UnixSignerBroker()
try:
active_broker.self_test()
except SignerBrokerError as exc:
raise SignerContractError(exc.code) from exc
_sign_with_broker(
active_broker,
"approval_plan",
grant_dir / "approval-plan.canonical.json",
grant_dir / "approval-plan.sig",
grant_dir / "approval-plan.key-id",
)
_sign(
signer_command,
grant_namespace,
_sign_with_broker(
active_broker,
"grant_payload",
grant_dir / "grant-payload.canonical.json",
grant_dir / "signed-grant.sig",
grant_dir / "signed-grant.key-id",
@@ -0,0 +1,190 @@
"""V2R2C1 adapter using the existing V2R1C7 deployment primitives.
The service identity and loopback port remain stable for a safe cutover. The
new release is installed under its own immutable directory and keeps the
previous V2R1C7 release available for an explicit, owner-approved rollback.
"""
from __future__ import annotations
import json
import hashlib
import os
import shutil
import sys
from argparse import Namespace
from pathlib import Path
from typing import Any, Mapping
import v2r1c7_production_adapter as base
VERSION = "V2R2C1"
SERVICE = "aios-v2r1c7-production.service"
PORT = 18927
RELEASE_ROOT = Path("/opt/aios/releases")
CURRENT_LINK = Path("/opt/aios/v2r2c1")
ENV_FILE = Path("/etc/aios/aios-v2r2c1.env")
STATE_FILE = Path("/var/lib/aios/v2r2c1/deployment-state.json")
UNIT_FILE = Path("/etc/systemd/system/aios-v2r1c7-production.service")
ROLLBACK_ROOT = Path("/var/lib/aios/v2r2c1/rollback")
REQUIRED_MEMBERS = {
"payload/VERSION",
"payload/versions/V2R2C1.json",
"payload/active-runtime/app/deployment_state.py",
"payload/active-runtime/app/v2r1c6_privacy_tools.py",
"payload/active-runtime/app/v2r2c1_health_service.py",
"payload/deploy/aios-v2/systemd/aios-v2r1c7-production.service",
"payload/deploy/aios-v2/v2r2c1-runtime-config.json",
"payload/deploy/aios-v2/v2r2c1-rollback.json",
"payload/scripts/probe_v2r2c1_health.py",
"payload/scripts/rollback_v2r2c1_release.sh",
"payload/BUILD-METADATA.json",
"payload/ARTIFACT-MANIFEST.json",
"payload/ROLLBACK-METADATA.json",
}
def _configure_base() -> None:
base.VERSION = VERSION
base.SERVICE = SERVICE
base.PORT = PORT
base.REQUIRED_MEMBERS = REQUIRED_MEMBERS
def _args() -> Namespace:
return Namespace(
release_root=RELEASE_ROOT,
current_link=CURRENT_LINK,
env_file=ENV_FILE,
state_file=STATE_FILE,
unit_file=UNIT_FILE,
health_timeout=45,
)
def load_request() -> dict[str, Any]:
_configure_base()
value = base.load_request()
if value.get("version") != VERSION or value.get("production_service") != SERVICE or int(value.get("production_port", -1)) != PORT:
raise ValueError("V2R2C1 production binding mismatch")
return value
def _backup(path: Path, name: str) -> None:
if path.is_file() and not path.is_symlink():
ROLLBACK_ROOT.mkdir(parents=True, exist_ok=True)
destination = ROLLBACK_ROOT / name
if not destination.exists():
shutil.copy2(path, destination)
def execute_request(value: Mapping[str, Any]) -> dict[str, Any]:
_configure_base()
if value.get("schema") != "aios.production-executor-request.v1":
raise ValueError("request_schema_invalid")
if value.get("operation") == "apply":
_backup(UNIT_FILE, "aios-v2r1c7-production.service.previous")
_backup(Path("/etc/aios/aios-v2r1c7.env"), "aios-v2r1c7.env.previous")
_backup(Path("/var/lib/aios/v2r1c7/deployment-state.json"), "v2r1c7-deployment-state.previous")
return base.install_release(_args(), value)
if value.get("operation") == "probe":
result = base.probe(_args(), value)
if result.get("status") == "ACTIVE_VERIFIED":
result.update(_runtime_registry_gate(value))
return result
if value.get("operation") == "rollback":
return rollback(value)
raise ValueError("unsupported_operation")
def _runtime_registry_gate(value: Mapping[str, Any]) -> dict[str, Any]:
"""Prove the deployed immutable runtime contains the exact M:path blob."""
source_digest = value.get("source_registry_sha256")
source_main = value.get("source_registry_main_oid")
if not source_digest or not source_main:
return {}
source_path = str(value.get("source_registry_path") or f"versions/{VERSION}.json")
member = CURRENT_LINK / source_path
if member.is_symlink() or not member.is_file():
raise RuntimeError("RUNTIME_SOURCE_REGISTRY_MEMBER_MISSING")
raw = member.read_bytes()
digest = hashlib.sha256(raw).hexdigest()
if digest != source_digest:
raise RuntimeError("RUNTIME_SOURCE_REGISTRY_DIGEST_MISMATCH")
try:
source = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RuntimeError("RUNTIME_SOURCE_REGISTRY_INVALID") from exc
expected = {
"version": VERSION,
"repository": "aios/aios",
"tag_oid": value.get("tag_oid"),
"target_oid": value.get("target_oid"),
"peeled_target_oid": value.get("target_oid"),
"evidence_closure_oid": value.get("evidence_closure_oid"),
"approval_plan_id": value.get("approval_plan_id"),
}
if not isinstance(source, dict) or any(source.get(key) != expected_value for key, expected_value in expected.items()):
raise RuntimeError("RUNTIME_SOURCE_REGISTRY_IDENTITY_MISMATCH")
if source.get("tag_object") != expected["tag_oid"]:
raise RuntimeError("RUNTIME_SOURCE_REGISTRY_TAG_OBJECT_MISMATCH")
version_name = f"{VERSION}.json"
return {
"runtime_registry_versions": [version_name],
"attested_projection_versions": [version_name],
"effective_registry_versions": [version_name],
# The adapter can only inspect the deployed immutable runtime. It
# must not claim convergence with mutable authoritative Git; the
# executor performs that independent M/C read after deployment.
"source_registry_converged": "not_checked",
"source_registry_convergence_error": "executor_must_recheck_authoritative_git",
"runtime_registry_converged": True,
"runtime_bundle_contains_current_version": True,
"runtime_bundle_contains_version": True,
"registry_consistency_status": "RUNTIME_ONLY_VERIFIED",
"runtime_source_registry_main_oid": source_main,
"runtime_source_registry_sha256": digest,
}
def rollback(value: Mapping[str, Any]) -> dict[str, Any]:
"""Explicit rollback only; this adapter never invokes it automatically."""
import subprocess
subprocess.run(["systemctl", "stop", SERVICE], check=False, capture_output=True, text=True, timeout=45)
previous_unit = ROLLBACK_ROOT / "aios-v2r1c7-production.service.previous"
previous_env = ROLLBACK_ROOT / "aios-v2r1c7.env.previous"
previous_state = ROLLBACK_ROOT / "v2r1c7-deployment-state.previous"
if not previous_unit.is_file() or not previous_env.is_file():
raise RuntimeError("ROLLBACK_RECEIPT_MISSING")
UNIT_FILE.parent.mkdir(parents=True, exist_ok=True)
ENV_FILE.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(previous_unit, UNIT_FILE)
shutil.copy2(previous_env, Path("/etc/aios/aios-v2r1c7.env"))
if previous_state.is_file():
Path("/var/lib/aios/v2r1c7").mkdir(parents=True, exist_ok=True)
shutil.copy2(previous_state, Path("/var/lib/aios/v2r1c7/deployment-state.json"))
subprocess.run(["systemctl", "daemon-reload"], check=True, capture_output=True, text=True, timeout=45)
subprocess.run(["systemctl", "restart", SERVICE], check=True, capture_output=True, text=True, timeout=45)
result = base.health("http://127.0.0.1:18927/health", {
"version": "V2R1C7",
"target_oid": str(json.loads(Path("/var/lib/aios/v2r1c7/deployment-state.json").read_text(encoding="utf-8")).get("commit_oid")),
"artifact_sha256": str(json.loads(Path("/var/lib/aios/v2r1c7/deployment-state.json").read_text(encoding="utf-8")).get("artifact_sha256")),
})
return {"status": "ROLLED_BACK", "rollback_health": result, "automatic_rollback": False}
def main() -> int:
try:
value = load_request()
result = execute_request(value)
print(json.dumps(result, sort_keys=True))
return 0
except Exception as exc:
print(json.dumps({"status": "FAIL_CLOSED", "error": type(exc).__name__, "reason": str(exc)}, sort_keys=True))
return 2
if __name__ == "__main__":
raise SystemExit(main())
+982
View File
@@ -0,0 +1,982 @@
"""Release registry visibility and source-convergence gates.
Two invariants are enforced:
1. listVersions may overlay exactly the current Production Baseline-attested
projection onto the immutable source bundle. Arbitrary projection files
are not surfaced.
2. release_metadata_adapter.baseline_apply may not finalize a production
baseline unless the governed repository/main already contains a source
versions/<version>.json entry matching the release identity.
The source convergence gate intentionally checks release identity, not
post-deployment runtime fields. This allows the source entry to be committed
before deployment while still preventing PRODUCTION_DEPLOYED from being
written for an uncommitted release identity.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
import stat
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
_VERSION_RE = re.compile(r"^V[0-9]+R[0-9]+C[0-9]+(?:-[A-Za-z0-9_.-]+)?$")
_OID_RE = re.compile(r"^[0-9a-f]{40}$")
_PLAN_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
VERSION_RE = _VERSION_RE
OID_RE = _OID_RE
SOURCE_REGISTRY_SCHEMAS = frozenset(
{"aios.source-registry.v1", "aios.production-registry.v2"}
)
SOURCE_REGISTRY_FIELDS = (
"version",
"tag_oid",
"target_oid",
"peeled_target_oid",
"evidence_closure_oid",
"approval_plan_id",
)
REPOSITORY_IDENTITY = "aios/aios"
DEFAULT_PROJECTION_ROOT = Path(
os.getenv(
"AIOS_GITOPS_REGISTRY_PROJECTION_ROOT",
"/var/lib/aios-gitops/runtime/registry-projection",
)
)
DEFAULT_BASELINE_FILE = Path(
os.getenv(
"AIOS_GITOPS_BASELINE_FILE",
"/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
)
)
DEFAULT_MAIN_REF = os.getenv("AIOS_GITOPS_MAIN_REF", "refs/heads/main")
DEFAULT_AUTHORITATIVE_GIT_DIR = Path("/data/aios-gitops/repo.git")
DEFAULT_APPROVAL_AUTHORITY_ROOT = Path("/var/lib/aios-gitops/approval-authority")
TEST_MODE_ENV = "AIOS_TEST_MODE"
AUTHORITATIVE_GIT_ENV = "AIOS_GITOPS_AUTHORITATIVE_GIT_DIR"
APPROVAL_AUTHORITY_ROOT_ENV = "AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT"
REPOSITORY_IDENTITY_ENV = "AIOS_GITOPS_REPOSITORY_IDENTITY"
class RegistryConvergenceError(RuntimeError):
"""Release registry/source convergence is not proven."""
def _load_object(path: Path) -> dict[str, Any]:
obj = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(obj, dict):
raise RegistryConvergenceError(f"json_not_object:{path}")
return obj
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _git_text(repo_git_dir: Path | str, *args: str) -> str:
completed = subprocess.run(
["git", f"--git-dir={Path(repo_git_dir)}", *args],
capture_output=True,
text=True,
check=False,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise RegistryConvergenceError(
completed.stderr.strip() or "authoritative_git_query_failed"
)
return completed.stdout.strip()
def _git_bytes(repo_git_dir: Path | str, *args: str) -> bytes:
completed = subprocess.run(
["git", f"--git-dir={Path(repo_git_dir)}", *args],
capture_output=True,
check=False,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise RegistryConvergenceError(
completed.stderr.decode("utf-8", "replace").strip()
or "authoritative_git_query_failed"
)
return completed.stdout
def test_mode_enabled() -> bool:
"""Return whether explicit dependency injection is allowed for tests."""
return os.environ.get(TEST_MODE_ENV) == "1"
def _configured_override(env_name: str, default: str, label: str) -> Path:
if env_name in os.environ:
if not test_mode_enabled():
raise RegistryConvergenceError(f"{label}_env_override_forbidden")
value = os.environ.get(env_name, "")
if not value:
raise RegistryConvergenceError(f"{label}_env_override_invalid")
return Path(value)
return Path(default)
def configured_authoritative_git_dir() -> Path:
return _configured_override(
AUTHORITATIVE_GIT_ENV,
str(DEFAULT_AUTHORITATIVE_GIT_DIR),
"authoritative_repository",
)
def configured_repository_identity() -> str:
if REPOSITORY_IDENTITY_ENV in os.environ:
if not test_mode_enabled():
raise RegistryConvergenceError("repository_identity_env_override_forbidden")
value = os.environ.get(REPOSITORY_IDENTITY_ENV, "")
if not value:
raise RegistryConvergenceError("repository_identity_env_override_invalid")
return value
return REPOSITORY_IDENTITY
def verify_authoritative_repository(
repo_git_dir: Path | str,
*,
repository_identity: str = REPOSITORY_IDENTITY,
expected_path: Path | str | None = None,
) -> dict[str, str]:
"""Prove that a Git directory is the configured AIOS authority.
A repository that merely contains internally consistent objects is not
sufficient. The resolved path must be the configured authority and the
bare repository must advertise the expected Gitea repository identity.
"""
if repository_identity != REPOSITORY_IDENTITY:
raise RegistryConvergenceError("authoritative_repository_identity_invalid")
candidate = Path(repo_git_dir)
configured = Path(expected_path) if expected_path is not None else configured_authoritative_git_dir()
if test_mode_enabled() and expected_path is None and AUTHORITATIVE_GIT_ENV not in os.environ:
# Explicit test mode may inject a temporary bare repository directly.
# Production mode always compares against the fixed authority path.
configured = candidate
try:
candidate_real = candidate.resolve(strict=True)
configured_real = configured.resolve(strict=True)
except OSError as exc:
raise RegistryConvergenceError("authoritative_repository_path_unavailable") from exc
if candidate_real != configured_real:
raise RegistryConvergenceError("authoritative_repository_path_mismatch")
if not candidate_real.is_dir():
raise RegistryConvergenceError("authoritative_repository_not_directory")
if _git_text(candidate_real, "rev-parse", "--is-bare-repository") != "true":
raise RegistryConvergenceError("authoritative_repository_not_bare")
remotes = subprocess.run(
[
"git",
f"--git-dir={candidate_real}",
"config",
"--get-regexp",
r"^remote\..*\.url$",
],
capture_output=True,
text=True,
check=False,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
urls = [line.split(None, 1)[1] for line in remotes.stdout.splitlines() if " " in line]
identity_suffix = repository_identity.removesuffix(".git")
identity_verified = any(
url.rstrip("/").removesuffix(".git").replace("\\", "/").split(":")[-1]
in {identity_suffix, "/" + identity_suffix}
or url.rstrip("/").removesuffix(".git").replace("\\", "/").split(":")[-1].endswith(
"/" + identity_suffix
)
for url in urls
)
if not test_mode_enabled() and not identity_verified:
raise RegistryConvergenceError("authoritative_repository_identity_unverified")
return {
"repository_identity": repository_identity,
"configured_path": str(configured_real),
"resolved_path": str(candidate_real),
}
def resolve_source_registry_main_oid(
repo_git_dir: Path | str,
*,
main_ref: str = DEFAULT_MAIN_REF,
) -> str:
"""Resolve the authoritative main ref to one immutable commit OID."""
oid = _git_text(repo_git_dir, "rev-parse", "--verify", f"{main_ref}^{{commit}}")
if not _OID_RE.fullmatch(oid):
raise RegistryConvergenceError("source_registry_main_oid_invalid")
if _git_text(repo_git_dir, "cat-file", "-t", oid) != "commit":
raise RegistryConvergenceError("source_registry_main_oid_not_commit")
return oid
def source_registry_snapshot(
repo_git_dir: Path | str,
version: str,
*,
main_ref: str = DEFAULT_MAIN_REF,
source_registry_main_oid: str | None = None,
expected_tag_oid: str | None = None,
expected_target_oid: str | None = None,
expected_evidence_closure_oid: str | None = None,
expected_approval_plan_id: str | None = None,
) -> dict[str, Any]:
"""Read one source entry and its exact bytes from one immutable commit.
Callers building an artifact should resolve ``source_registry_main_oid``
once and pass it back here. The function then never consults the mutable
ref again, so validation and packaging use the same Git blob.
"""
if not isinstance(version, str) or not _VERSION_RE.fullmatch(version):
raise RegistryConvergenceError("source_registry_version_invalid")
main_oid = source_registry_main_oid or resolve_source_registry_main_oid(
repo_git_dir,
main_ref=main_ref,
)
if not _OID_RE.fullmatch(main_oid):
raise RegistryConvergenceError("source_registry_main_oid_invalid")
if _git_text(repo_git_dir, "cat-file", "-t", main_oid) != "commit":
raise RegistryConvergenceError("source_registry_main_oid_not_commit")
path = f"versions/{version}.json"
try:
raw = _git_bytes(repo_git_dir, "show", f"{main_oid}:{path}")
except RegistryConvergenceError as exc:
if "path" in str(exc).lower() or "does not exist" in str(exc).lower():
raise RegistryConvergenceError(
f"source_registry_entry_missing:{main_oid}:{path}"
) from exc
raise
try:
source = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RegistryConvergenceError("source_registry_json_invalid") from exc
record = validate_source_registry_record(
source,
expected_version=version,
expected_tag_oid=expected_tag_oid,
expected_target_oid=expected_target_oid,
expected_evidence_closure_oid=expected_evidence_closure_oid,
expected_approval_plan_id=expected_approval_plan_id,
)
return {
"record": record,
"bytes": raw,
"source_registry_main_oid": main_oid,
"source_registry_path": path,
"source_registry_sha256": hashlib.sha256(raw).hexdigest(),
}
def validate_source_registry_record(
value: Any,
*,
expected_version: str | None = None,
expected_tag_oid: str | None = None,
expected_target_oid: str | None = None,
expected_evidence_closure_oid: str | None = None,
expected_approval_plan_id: str | None = None,
) -> dict[str, Any]:
"""Validate the immutable identity carried by versions/<version>.json."""
if not isinstance(value, dict):
raise RegistryConvergenceError("source_registry_not_object")
if value.get("schema") not in SOURCE_REGISTRY_SCHEMAS:
raise RegistryConvergenceError("source_registry_schema_invalid")
version = value.get("version")
if not isinstance(version, str) or not _VERSION_RE.fullmatch(version):
raise RegistryConvergenceError("source_registry_version_invalid")
if expected_version is not None and version != expected_version:
raise RegistryConvergenceError("source_registry_mismatch:version")
if value.get("repository") != REPOSITORY_IDENTITY:
raise RegistryConvergenceError("source_registry_repository_invalid")
for field in ("tag_oid", "target_oid", "peeled_target_oid", "evidence_closure_oid"):
item = value.get(field)
if not isinstance(item, str) or not _OID_RE.fullmatch(item):
raise RegistryConvergenceError(f"source_registry_{field}_invalid")
if value["peeled_target_oid"] != value["target_oid"]:
raise RegistryConvergenceError("source_registry_peeled_target_mismatch")
approval = value.get("approval_plan_id")
if not isinstance(approval, str) or not _PLAN_RE.fullmatch(approval):
raise RegistryConvergenceError("source_registry_approval_plan_invalid")
if value.get("tag_object", value["tag_oid"]) != value["tag_oid"]:
raise RegistryConvergenceError("source_registry_tag_object_mismatch")
expected = {
"tag_oid": expected_tag_oid,
"target_oid": expected_target_oid,
"evidence_closure_oid": expected_evidence_closure_oid,
"approval_plan_id": expected_approval_plan_id,
}
for field, wanted in expected.items():
if wanted is not None and value.get(field) != wanted:
raise RegistryConvergenceError(f"source_registry_mismatch:{field}")
return dict(value)
def build_source_registry_entry(
*,
version: str,
tag_oid: str,
target_oid: str,
peeled_target_oid: str,
evidence_closure_oid: str,
approval_plan_id: str,
repository: str = REPOSITORY_IDENTITY,
) -> dict[str, Any]:
"""Build the pre-promotion source entry; it performs no Git or runtime write."""
value = {
"schema": "aios.source-registry.v1",
"version": version,
"repository": repository,
"tag": version,
"tag_oid": tag_oid,
"tag_object": tag_oid,
"target_oid": target_oid,
"peeled_target_oid": peeled_target_oid,
"evidence_closure_oid": evidence_closure_oid,
"approval_plan_id": approval_plan_id,
}
return validate_source_registry_record(
value,
expected_version=version,
expected_tag_oid=tag_oid,
expected_target_oid=target_oid,
expected_evidence_closure_oid=evidence_closure_oid,
expected_approval_plan_id=approval_plan_id,
)
def verify_source_registry_git_identity(
repo_git_dir: Path | str,
record: dict[str, Any],
) -> dict[str, Any]:
"""Prove that every Git identity in a source entry exists and is related."""
record = validate_source_registry_record(record)
version = record["version"]
tag_oid = record["tag_oid"]
target_oid = record["target_oid"]
evidence_oid = record["evidence_closure_oid"]
if _git_text(repo_git_dir, "cat-file", "-t", tag_oid) != "tag":
raise RegistryConvergenceError("source_registry_tag_not_annotated")
named_tag = _git_text(
repo_git_dir,
"rev-parse",
"--verify",
f"refs/tags/{version}",
)
if named_tag != tag_oid:
raise RegistryConvergenceError("source_registry_named_tag_mismatch")
if _git_text(repo_git_dir, "cat-file", "-t", target_oid) != "commit":
raise RegistryConvergenceError("source_registry_target_not_commit")
peeled = _git_text(
repo_git_dir,
"rev-parse",
"--verify",
f"{tag_oid}^{{commit}}",
)
if peeled != target_oid or record["peeled_target_oid"] != target_oid:
raise RegistryConvergenceError("source_registry_tag_target_mismatch")
if _git_text(repo_git_dir, "cat-file", "-t", evidence_oid) != "commit":
raise RegistryConvergenceError("source_registry_evidence_not_commit")
ancestor = subprocess.run(
[
"git",
f"--git-dir={Path(repo_git_dir)}",
"merge-base",
"--is-ancestor",
target_oid,
evidence_oid,
],
check=False,
capture_output=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if ancestor.returncode != 0:
raise RegistryConvergenceError("source_registry_evidence_lineage_invalid")
return {
"repository": REPOSITORY_IDENTITY,
"version": version,
"tag_oid": tag_oid,
"tag_object_type": "tag",
"target_oid": target_oid,
"peeled_target_oid": peeled,
"evidence_closure_oid": evidence_oid,
"target_is_ancestor_of_evidence": True,
}
def verify_source_registry_main_convergence(
repo_git_dir: Path | str,
version: str,
*,
main_ref: str = DEFAULT_MAIN_REF,
source_registry_main_oid: str | None = None,
expected_tag_oid: str | None = None,
expected_target_oid: str | None = None,
expected_evidence_closure_oid: str | None = None,
expected_approval_plan_id: str | None = None,
) -> dict[str, Any]:
"""Prove that pinned M is still converged with current authoritative main C."""
current_main_oid = resolve_source_registry_main_oid(repo_git_dir, main_ref=main_ref)
source_main_oid = source_registry_main_oid or current_main_oid
if not _OID_RE.fullmatch(source_main_oid):
raise RegistryConvergenceError("source_registry_main_oid_invalid")
ancestor = subprocess.run(
[
"git",
f"--git-dir={Path(repo_git_dir)}",
"merge-base",
"--is-ancestor",
source_main_oid,
current_main_oid,
],
check=False,
capture_output=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if ancestor.returncode != 0:
raise RegistryConvergenceError("source_registry_main_not_ancestor_of_current_main")
pinned = source_registry_snapshot(
repo_git_dir,
version,
source_registry_main_oid=source_main_oid,
expected_tag_oid=expected_tag_oid,
expected_target_oid=expected_target_oid,
expected_evidence_closure_oid=expected_evidence_closure_oid,
expected_approval_plan_id=expected_approval_plan_id,
)
current = source_registry_snapshot(
repo_git_dir,
version,
source_registry_main_oid=current_main_oid,
expected_tag_oid=expected_tag_oid,
expected_target_oid=expected_target_oid,
expected_evidence_closure_oid=expected_evidence_closure_oid,
expected_approval_plan_id=expected_approval_plan_id,
)
if pinned["bytes"] != current["bytes"]:
raise RegistryConvergenceError("source_registry_main_entry_changed_after_pin")
return {
"source_registry_main_oid": source_main_oid,
"current_main_oid": current_main_oid,
"source_registry_path": pinned["source_registry_path"],
"source_registry_sha256": pinned["source_registry_sha256"],
"current_main_source_registry_sha256": current["source_registry_sha256"],
"source_registry_main_is_ancestor_of_current_main": True,
"source_registry_entry_matches_current_main": True,
}
_APPROVAL_AUTHORITY_STATES = frozenset(
{
"APPROVED",
"GITEA_API_CONFIRMED",
"REMOTE_VERIFIED",
"PRODUCTION_DEPLOYED",
}
)
_APPROVAL_AUTHORITY_SCHEMAS = frozenset(
{
"aios.v2r2c1.tag-approval-request.v1",
"aios.owner-release-approval.v1",
"aios.release-binding-approval.v2",
}
)
def configured_approval_authority_root() -> Path:
return _configured_override(
APPROVAL_AUTHORITY_ROOT_ENV,
str(DEFAULT_APPROVAL_AUTHORITY_ROOT),
"approval_authority_root",
)
def _secure_approval_authority_path(
authority_path: Path | str,
version: str,
) -> tuple[Path, Path]:
root = configured_approval_authority_root()
path = Path(authority_path)
if path.is_dir():
path = path / f"{version}.json"
try:
root_real = root.resolve(strict=True)
path_real = path.resolve(strict=True)
except OSError as exc:
raise RegistryConvergenceError("approval_authority_trusted_root_missing") from exc
try:
path_real.relative_to(root_real)
except ValueError as exc:
raise RegistryConvergenceError("approval_authority_untrusted_path") from exc
metadata = path.lstat()
if not stat.S_ISREG(metadata.st_mode) or path.is_symlink():
raise RegistryConvergenceError("approval_authority_not_regular")
if metadata.st_mode & 0o022:
raise RegistryConvergenceError("approval_authority_permissions_invalid")
if APPROVAL_AUTHORITY_ROOT_ENV not in os.environ and metadata.st_uid != 0:
raise RegistryConvergenceError("approval_authority_owner_invalid")
return path, root_real
def _canonical_json(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode(
"utf-8"
)
def _approval_is_revoked(root: Path, approval_plan_id: str, authority: dict[str, Any]) -> bool:
if any(
authority.get(name) is True
for name in ("revoked", "invalidated", "revocation")
):
return True
state = authority.get("state") or authority.get("status")
if state in {"REVOKED", "INVALIDATED", "EXPIRED", "CANCELLED"}:
return True
for marker in (
root / "revocations" / approval_plan_id,
root / "revocations" / approval_plan_id.replace(":", "_"),
):
if marker.exists():
return True
expires_at = authority.get("expires_at")
if isinstance(expires_at, str) and expires_at:
try:
instant = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
except ValueError as exc:
raise RegistryConvergenceError("approval_expiry_invalid") from exc
if instant <= datetime.now(timezone.utc):
return True
return False
def verify_approval_authority(
authority_path: Path | str,
record: dict[str, Any],
) -> dict[str, Any]:
"""Verify the existing release approval/publication receipt association.
The authority document is intentionally supplied by the governed caller;
this helper never creates or rewrites an approval. The path must be under
the configured canonical Owner-authority root and the document must use a
known release-approval schema. A caller-supplied JSON file elsewhere is
never an authority, even when its fields are internally consistent.
"""
path, trusted_root = _secure_approval_authority_path(authority_path, record["version"])
authority = _load_object(path)
schema = authority.get("schema")
if schema not in _APPROVAL_AUTHORITY_SCHEMAS:
raise RegistryConvergenceError("approval_authority_schema_invalid")
if _approval_is_revoked(trusted_root, record["approval_plan_id"], authority):
raise RegistryConvergenceError("approval_plan_revoked_or_invalidated")
nested = authority.get("release_binding") or authority.get("release_request")
if not isinstance(nested, dict):
nested = authority.get("binding")
if not isinstance(nested, dict):
raise RegistryConvergenceError("approval_authority_binding_missing")
expected = {
"version": record["version"],
"repository": REPOSITORY_IDENTITY,
"tag_oid": record["tag_oid"],
"target_oid": record["target_oid"],
"peeled_target_oid": record["peeled_target_oid"],
"evidence_closure_oid": record["evidence_closure_oid"],
"approval_plan_id": record["approval_plan_id"],
}
if authority.get("approval_plan_id") != expected["approval_plan_id"]:
raise RegistryConvergenceError("approval_plan_not_found_or_mismatched")
state = authority.get("state") or authority.get("status")
if state not in _APPROVAL_AUTHORITY_STATES and authority.get("approved") is not True:
raise RegistryConvergenceError("approval_plan_not_approved")
owner_approval = authority.get("owner_approval")
if schema == "aios.v2r2c1.tag-approval-request.v1":
if not isinstance(owner_approval, dict):
raise RegistryConvergenceError("canonical_owner_approval_missing")
if (
owner_approval.get("approved") is not True
or owner_approval.get("status") != "APPROVED"
or owner_approval.get("approved_by") != "aiman"
or owner_approval.get("approval_source") != "explicit_owner_approval_in_chat"
):
raise RegistryConvergenceError("canonical_owner_approval_invalid")
expected_digest = hashlib.sha256(_canonical_json(owner_approval)).hexdigest()
if authority.get("owner_approval_digest_sha256") != expected_digest:
raise RegistryConvergenceError("canonical_owner_approval_digest_invalid")
for field, wanted in expected.items():
observed = nested.get(field)
if field == "repository" and observed is None:
observed = nested.get("repository_identity") or authority.get(
"repository_identity"
)
if field == "peeled_target_oid" and observed is None:
observed = (
nested.get("gitea_peeled_target_oid")
or authority.get("gitea_peeled_target_oid")
or nested.get("remote_peeled_target_oid")
or authority.get("remote_peeled_target_oid")
)
if observed != wanted:
raise RegistryConvergenceError(f"approval_plan_release_binding_mismatch:{field}")
if state == "GITEA_API_CONFIRMED":
gitea_tag = authority.get("gitea_tag_oid")
gitea_peeled = authority.get("gitea_peeled_target_oid")
if gitea_tag != expected["tag_oid"] or gitea_peeled != expected["target_oid"]:
raise RegistryConvergenceError("approval_plan_remote_tag_binding_mismatch")
return {
"path": str(path),
"schema": schema,
"state": state or "APPROVED",
"approval_plan_id": expected["approval_plan_id"],
"owner_approval_verified": schema == "aios.v2r2c1.tag-approval-request.v1",
"release_binding_verified": True,
}
def source_registry_json(value: dict[str, Any]) -> bytes:
"""Return the canonical bytes used by the governed file writer."""
return (json.dumps(value, ensure_ascii=False, sort_keys=True, indent=2) + "\n").encode(
"utf-8"
)
def _validated_current_projection(
projection_root: Path,
baseline_file: Path,
) -> Path | None:
"""Return only the exact projection attested by the current Baseline."""
if not baseline_file.is_file():
return None
try:
baseline = _load_object(baseline_file)
except Exception:
return None
version = baseline.get("version")
if not isinstance(version, str) or not _VERSION_RE.fullmatch(version):
return None
if baseline.get("status") != "PRODUCTION_DEPLOYED":
return None
if baseline.get("production_deployed") is not True:
return None
source = baseline.get("registry_source")
digest = baseline.get("registry_digest")
if not isinstance(source, str) or not isinstance(digest, str):
return None
candidate = projection_root / f"{version}.json"
try:
if candidate.resolve() != Path(source).resolve():
return None
except Exception:
return None
if not candidate.is_file() or _sha256(candidate) != digest:
return None
try:
record = _load_object(candidate)
except Exception:
return None
try:
validate_source_registry_record(
record,
expected_version=version,
expected_tag_oid=baseline.get("tag_oid"),
expected_target_oid=baseline.get("target_oid"),
expected_evidence_closure_oid=baseline.get("evidence_closure_oid"),
expected_approval_plan_id=baseline.get("approval_plan_id"),
)
except RegistryConvergenceError:
return None
if record.get("schema") != "aios.production-registry.v2":
return None
if record.get("status") != "PRODUCTION_DEPLOYED":
return None
if record.get("version") != version:
return None
if record.get("repository") != REPOSITORY_IDENTITY:
return None
if record.get("production_deployed") is not True:
return None
tag_oid = record.get("tag_oid")
target_oid = record.get("target_oid")
if not isinstance(tag_oid, str) or not _OID_RE.fullmatch(tag_oid):
return None
if not isinstance(target_oid, str) or not _OID_RE.fullmatch(target_oid):
return None
if record.get("peeled_target_oid") != target_oid:
return None
return candidate
def list_registry_versions(
workspace: Path | str,
*,
projection_root: Path = DEFAULT_PROJECTION_ROOT,
baseline_file: Path = DEFAULT_BASELINE_FILE,
) -> list[str]:
"""Return the legacy display list; use the report for provenance."""
return list_registry_versions_report(
workspace,
projection_root=projection_root,
baseline_file=baseline_file,
)["registry_versions"]
def list_registry_versions_report(
workspace: Path | str,
*,
projection_root: Path = DEFAULT_PROJECTION_ROOT,
baseline_file: Path = DEFAULT_BASELINE_FILE,
repo_git_dir: Path | str | None = None,
main_ref: str = DEFAULT_MAIN_REF,
) -> dict[str, Any]:
"""Return version visibility with runtime/projection provenance.
The display list intentionally retains the historical projection overlay
for API compatibility. The explicit booleans make that overlay
non-authoritative: a projection-only version has
``runtime_bundle_contains_version == false`` and can never be reported as
converged.
"""
ws = Path(workspace)
versions_root = ws / "versions"
runtime_names = {
path.name
for path in versions_root.iterdir()
if path.is_file()
and not path.is_symlink()
and path.suffix == ".json"
and _VERSION_RE.fullmatch(path.stem)
} if versions_root.is_dir() else set()
current = _validated_current_projection(projection_root, baseline_file)
attested_version = current.stem if current is not None else None
runtime_bundle_contains = bool(
attested_version is not None
and f"{attested_version}.json" in runtime_names
)
consistency_status = "NO_ATTESTED_PROJECTION"
source_converged: bool | str = "unknown"
source_error: str | None = None
runtime_converged = False
projection_record: dict[str, Any] | None = None
if current is not None:
try:
projection_record = _load_object(current)
except (OSError, RegistryConvergenceError, json.JSONDecodeError) as exc:
source_error = f"projection_invalid:{type(exc).__name__}"
if repo_git_dir is None:
source_converged = "unknown"
source_error = "authoritative_repository_unavailable"
elif projection_record is not None:
try:
verify_authoritative_repository(repo_git_dir)
verify_source_registry_main_convergence(
repo_git_dir,
attested_version or "",
main_ref=main_ref,
expected_tag_oid=projection_record.get("tag_oid"),
expected_target_oid=projection_record.get("target_oid"),
expected_evidence_closure_oid=projection_record.get(
"evidence_closure_oid"
),
expected_approval_plan_id=projection_record.get("approval_plan_id"),
)
source_converged = True
except RegistryConvergenceError as exc:
detail = str(exc)
if detail.startswith("authoritative_repository_"):
source_converged = "unknown"
source_error = (
"authoritative_repository_unavailable"
if detail.endswith("path_unavailable")
else detail
)
else:
source_converged = False
source_error = detail
if current is not None and not runtime_bundle_contains:
consistency_status = "RUNTIME_ARTIFACT_RECONCILIATION_REQUIRED"
elif current is not None and runtime_bundle_contains:
runtime_path = versions_root / current.name
try:
projection_record = projection_record or _load_object(current)
runtime_record = _load_object(runtime_path)
identity_fields = (
"version",
"repository",
"tag_oid",
"target_oid",
"peeled_target_oid",
"evidence_closure_oid",
"approval_plan_id",
)
runtime_converged = all(
runtime_record.get(field) == projection_record.get(field)
for field in identity_fields
)
if not runtime_converged:
consistency_status = "RUNTIME_IDENTITY_MISMATCH"
except (OSError, RegistryConvergenceError, json.JSONDecodeError):
consistency_status = "RUNTIME_ENTRY_INVALID"
runtime_converged = False
if current is not None and runtime_bundle_contains and runtime_converged:
if source_converged is True:
consistency_status = "FULLY_CONSISTENT"
elif source_converged == "unknown":
consistency_status = "SOURCE_GIT_NOT_CHECKED"
else:
consistency_status = "SOURCE_REGISTRY_NOT_CONVERGED"
elif current is not None and not runtime_bundle_contains:
if source_converged is False:
consistency_status = "SOURCE_REGISTRY_NOT_CONVERGED"
else:
consistency_status = "RUNTIME_ARTIFACT_RECONCILIATION_REQUIRED"
names = set(runtime_names)
if current is not None:
names.add(current.name)
effective = sorted(names)
projection_versions = [attested_version + ".json"] if attested_version else []
return {
"registry_versions": effective,
"effective_registry_versions": effective,
"runtime_registry_versions": sorted(runtime_names),
"attested_projection_versions": projection_versions,
"attested_projection_version": attested_version,
"source_registry_converged": source_converged,
"source_registry_convergence_error": source_error,
"runtime_registry_converged": runtime_converged,
"runtime_bundle_contains_current_version": runtime_bundle_contains,
"runtime_bundle_contains_version": runtime_bundle_contains,
"registry_consistency_status": consistency_status,
}
def assert_runtime_registry_convergence(
workspace: Path | str,
version: str,
*,
projection_root: Path = DEFAULT_PROJECTION_ROOT,
baseline_file: Path = DEFAULT_BASELINE_FILE,
) -> dict[str, Any]:
"""Fail closed when a projection-only version is visible but unpackaged."""
report = list_registry_versions_report(
workspace,
projection_root=projection_root,
baseline_file=baseline_file,
)
expected_name = f"{version}.json"
if (
report.get("attested_projection_version") == version
and report.get("runtime_bundle_contains_version") is not True
):
raise RegistryConvergenceError("runtime_bundle_missing_source_registry")
if expected_name not in report.get("runtime_registry_versions", []):
raise RegistryConvergenceError("runtime_bundle_missing_source_registry")
if report.get("source_registry_converged") is not True:
raise RegistryConvergenceError("source_registry_not_converged_or_not_checked")
if report.get("registry_consistency_status") not in {"FULLY_CONSISTENT", "PASS"}:
raise RegistryConvergenceError(
f"runtime_registry_consistency:{report.get('registry_consistency_status')}"
)
return report
def source_registry_from_main(
repo_git_dir: Path | str,
version: str,
*,
main_ref: str = DEFAULT_MAIN_REF,
source_registry_main_oid: str | None = None,
expected_tag_oid: str | None = None,
expected_target_oid: str | None = None,
expected_evidence_closure_oid: str | None = None,
expected_approval_plan_id: str | None = None,
) -> dict[str, Any]:
"""Read and validate the canonical source entry from the supplied main ref."""
try:
return source_registry_snapshot(
repo_git_dir,
version,
main_ref=main_ref,
source_registry_main_oid=source_registry_main_oid,
expected_tag_oid=expected_tag_oid,
expected_target_oid=expected_target_oid,
expected_evidence_closure_oid=expected_evidence_closure_oid,
expected_approval_plan_id=expected_approval_plan_id,
)["record"]
except RegistryConvergenceError as exc:
if str(exc).startswith("fatal:") or "path" in str(exc).lower():
raise RegistryConvergenceError(
f"source_registry_entry_missing:{source_registry_main_oid or main_ref}:versions/{version}.json"
) from exc
raise
def assert_source_registry_convergence(
registry_path: Path | str,
*,
repo_git_dir: Path | str,
main_ref: str = DEFAULT_MAIN_REF,
source_registry_main_oid: str | None = None,
) -> dict[str, Any]:
"""Require governed main to contain a source entry for the release identity.
The source entry is intentionally required to match only fields that are
knowable and governable before production deployment. Post-deployment
fields such as production_service/port/status timestamps are not required.
"""
registry_path = Path(registry_path)
repo_git_dir = Path(repo_git_dir)
verify_authoritative_repository(repo_git_dir)
record = _load_object(registry_path)
version = record.get("version")
tag_oid = record.get("tag_oid")
target_oid = record.get("target_oid")
try:
validate_source_registry_record(
record,
expected_version=version,
expected_tag_oid=tag_oid,
expected_target_oid=target_oid,
expected_evidence_closure_oid=record.get("evidence_closure_oid"),
expected_approval_plan_id=record.get("approval_plan_id"),
)
except RegistryConvergenceError as exc:
raise RegistryConvergenceError(f"registry_identity_invalid:{exc}") from exc
convergence = verify_source_registry_main_convergence(
repo_git_dir,
version,
main_ref=main_ref,
source_registry_main_oid=source_registry_main_oid,
expected_tag_oid=tag_oid,
expected_target_oid=target_oid,
expected_evidence_closure_oid=record["evidence_closure_oid"],
expected_approval_plan_id=record["approval_plan_id"],
)
return {
**convergence,
}
@@ -0,0 +1,23 @@
[Unit]
Description=AIOS ChatGPT Bridge v2 staging control plane
After=network-online.target
[Service]
Type=simple
User=aios-bridge-staging
Group=aios-bridge-staging
WorkingDirectory=/opt/aios/gitops-control-plane/current
Environment=AIOS_BRIDGE_SCHEMA_VERSION=aios.chatgpt-gitops-bridge.v2
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=true
Environment=AIOS_DEPLOYMENT_STATE=STAGING
ExecStart=/usr/bin/python3 /opt/aios/gitops-control-plane/current/bridge-v2-staging-server.py
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/aios-gitops/bridge-v2-staging
Restart=on-failure
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,51 @@
[Unit]
Description=AIOS GitOps Model API V2R2C1 immutable staging runtime
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
WorkingDirectory=@RUNTIME_ROOT@
Environment=PYTHONUNBUFFERED=1
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=AIOS_GITOPS_ROOT=/var/lib/aios-gitops
Environment=AIOS_GITOPS_WORKSPACE=@RUNTIME_ROOT@
Environment=AIOS_GITOPS_REPO=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_BARE_REPO=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_AUTHORITATIVE_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_REPOSITORY_IDENTITY=aios/aios
Environment=AIOS_GITOPS_BASELINE_AUTHORITY=/var/lib/aios-gitops-runtime-anchor/baseline/current.json
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=false
Environment=AIOS_GITOPS_JOB_CONTROLLER_ENABLED=false
Environment=AIOS_GITOPS_WORKSPACE_ROLE=runtime-anchor
Environment=AIOS_GITOPS_RUNTIME_OID=@SOURCE_OID@
Environment=AIOS_GITOPS_ARTIFACT_SHA256=@ARTIFACT_SHA256@
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_BRIDGE_V2_URL=http://127.0.0.1:18892
Environment=AIOS_GITOPS_HOST=127.0.0.1
Environment=AIOS_GITOPS_PORT=18893
Environment=PYTHONPATH=@RUNTIME_ROOT@/app:@RUNTIME_ROOT@/runtime-agent-os
ExecStart=/usr/bin/python3 @RUNTIME_ROOT@/app/aios_model_api_staging.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
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
ReadWritePaths=/var/lib/aios-gitops/model-api-staging
[Install]
WantedBy=multi-user.target
+33
View File
@@ -0,0 +1,33 @@
[Unit]
Description=AIOS durable GitOps writer worker
Requires=aios-gitops-writer.socket
After=aios-gitops-writer.socket network-online.target
[Service]
Type=simple
User=root
Group=root
UMask=0077
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=true
Environment=AIOS_GITOPS_JOB_ROOT=/var/lib/aios-gitops/jobs
Environment=AIOS_GITEA_MACHINE_CREDENTIAL_REF=systemd-credential:gitea-machine-token
Environment=AIOS_GITOPS_AUTHORITY_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_WORKTREE_ROOT=/var/lib/aios-gitops/worktrees
Environment=AIOS_GITEA_API=https://g.cnnetmkt.top/git/api/v1
Environment=AIOS_GITEA_REMOTE=https://g.cnnetmkt.top/git/aios/aios.git
Environment=AIOS_GITEA_MACHINE_ACCOUNT=aios-autonomy-agent
Environment=AIOS_RELEASE_RECOVERY_RECEIPT=/var/lib/aios-gitops/release-campaigns/AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807/authorization-recovery.json
LoadCredentialEncrypted=gitea-machine-token:/etc/aios/credentials/gitea-machine.token.cred
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -B /opt/aios-gitops/active-runtime/app/aios_gitops_writer_worker.py --job-root ${AIOS_GITOPS_JOB_ROOT}
Restart=on-failure
RestartSec=2
NoNewPrivileges=no
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/aios-gitops/jobs /var/lib/aios-gitops/payloads /var/lib/aios-gitops/worktrees /data/aios-gitops/repo.git
[Install]
WantedBy=multi-user.target
+14
View File
@@ -0,0 +1,14 @@
[Unit]
Description=AIOS durable GitOps writer socket
[Socket]
ListenStream=/run/aios-gitops/writer.sock
Accept=no
SocketUser=root
SocketGroup=gitops
SocketMode=0660
DirectoryMode=0750
RemoveOnStop=yes
[Install]
WantedBy=sockets.target
@@ -0,0 +1,26 @@
[Unit]
Description=AIOS Semantic Governance Drift Monitor
After=aios-governance-executor.service
[Service]
Type=oneshot
User=root
Group=gitops
ExecStart=/usr/bin/python3 /usr/local/libexec/aios-governance-drift-monitor
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
IPAddressDeny=any
ReadOnlyPaths=/usr/local/libexec/aios-governance-drift-monitor
ReadOnlyPaths=/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1/protection-drift/semantic-analysis.json
ReadWritePaths=/var/lib/aios-gitops/governance-executor
@@ -0,0 +1,37 @@
[Unit]
Description=AIOS Dedicated Governance Executor
After=aios-gitea.service network-online.target
Wants=network-online.target
ConditionPathExists=/etc/credstore.encrypted/gitea-governance-executor-token
ConditionPathExists=/usr/local/libexec/aios-governance-executor
[Service]
Type=simple
User=root
Group=gitops
LoadCredentialEncrypted=gitea-governance-executor-token:/etc/credstore.encrypted/gitea-governance-executor-token
ExecStart=/usr/local/libexec/aios-governance-executor
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=127.0.0.1
RuntimeDirectory=aios-governance-executor
RuntimeDirectoryMode=0750
StateDirectory=aios-gitops/governance-executor
StateDirectoryMode=0750
ReadOnlyPaths=/usr/local/libexec /etc/credstore.encrypted
ReadWritePaths=/var/lib/aios-gitops/governance-executor /run/aios-governance-executor
UMask=0007
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,33 @@
[Unit]
Description=AIOS Governance Pre-Approval Publication Plane
Requires=aios-governance-preapproval-publisher.socket
After=network-online.target aios-gitea.service
Wants=network-online.target
[Service]
Type=simple
User=root
Group=gitops
UMask=0077
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GOVERNANCE_PUBLISHER_OPERATION_ALLOWLIST=semantic-reconciliation-only
LoadCredentialEncrypted=gitea-machine-token:/etc/aios/credentials/gitea-machine.token.cred
ExecStart=/usr/local/libexec/aios-governance-preapproval-publisher
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
ReadOnlyPaths=/usr/local/libexec/aios-governance-preapproval-publisher
ReadOnlyPaths=/etc/aios/credentials
ReadOnlyPaths=/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1/publication-input
ReadWritePaths=/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1
ReadWritePaths=/run/aios-governance-preapproval-publisher
@@ -0,0 +1,13 @@
[Unit]
Description=AIOS Governance Pre-Approval Publication Plane Socket
[Socket]
ListenStream=/run/aios-governance-preapproval-publisher/publisher.sock
SocketUser=root
SocketGroup=gitops
SocketMode=0660
DirectoryMode=0750
RemoveOnStop=true
[Install]
WantedBy=sockets.target
@@ -0,0 +1,30 @@
# AIOS Autonomous Release Campaign v1
This control plane changes the Owner interaction model from per-transaction
approval to one durable, revocable campaign policy delegation.
The single bootstrap confirmation creates:
1. a narrow task-writer grant for the control-plane implementation branch; and
2. a root-owned campaign authorization in PENDING_CONTROL_PLANE_MERGE state.
After the protected control-plane PR merges, machine activation may move that
same receipt to ACTIVE only when:
- the merged main is a descendant of the campaign lineage anchor;
- the campaign modules and tests are present in merged main;
- the campaign receipt digest is valid;
- no revocation exists;
- global AIOS_GITOPS_WRITES_ENABLED remains false.
Once ACTIVE, routine engineering, protected PRs, exact release binding
acceptance, tag publication, deployment, Registry/Baseline finalization and
recoverable failure handling no longer ask the Owner for another interaction.
Manual Owner review remains mandatory only for:
- formal same-name tag conflict;
- force-push requirement;
- published history rewrite;
- non-fast-forward main divergence;
- private signing material exposure;
- repository identity conflict;
- campaign revocation;
- operation outside campaign scope.
+37
View File
@@ -0,0 +1,37 @@
# Durable GitOps Job Controller
The control-plane write boundary is a durable Job, not a synchronous checkout
or commit request. The source of truth is SQLite WAL at
`/var/lib/aios-gitops/jobs/state.sqlite3`, with `jobs`, `idempotency_keys`,
`branch_leases`, `state_transitions`, and `receipts` tables. Per-job
`request.json`, `state.json`, `receipts/`, and `logs-digest/` files are
durable mirrors/evidence, not a second state machine.
The controller owns only binding validation, idempotency, state transitions,
and recovery classification. A supervised worker owns an isolated detached
worktree and all Git side effects. `/api/file` and `/api/commit-file` adapters
must return `job_id` and `side_effect_state`; they must never perform
checkout, file write, `git add`, or `git commit` inline.
`/var/lib/aios-gitops/payloads/sha256/<digest>` is the content-addressed
payload store. SQLite records only `payload_digest`, `payload_ref`, and size;
the worker verifies both digest and size before consumption. The durable
execution gate is `AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=true`, while
`AIOS_GITOPS_WRITES_ENABLED=false` remains the independent legacy repository
mutation gate.
The idempotency key is the SHA-256 of the canonical tuple:
`campaign_id`, `repository`, `operation`, `branch`, `expected_branch_oid`, and
`payload_digest`.
The writer lease is `CONTINUE_EXISTING` for the recovered campaign. It binds
to lineage anchor `69b45e81dcd2c3707c367003b08c55fd361faae8` and exact initial
head `bba675fab3b7e0c5fba7066720252b3c91b9f20d`. Every ref update is delegated
to a non-Owner machine broker using compare-and-swap; a stale head is a
`BRANCH_LEASE_STALE` condition and never a force push.
Production remains outside this worker contract. The fixed
`aios-gitops-writer.service` uses `Accept=no`, automatic restart, and durable
queue/reconciliation execution; it cannot mutate Git while the legacy global
write gate is false or while the campaign/machine capability is absent.
@@ -0,0 +1,15 @@
# AIOS Mission Source Convergence
This directory contains the exact verified governance runtime sources migrated from the live control plane under `AIOS_AUTONOMOUS_RELEASE_MISSION_COMPLETION_AUTHORIZATION_V1`.
The executables remain capability-separated:
- `aios-governance-executor`: protected branch-policy GET, semantic CAS, and approved Stage-1 delta only.
- `aios-governance-drift-monitor`: read-only semantic/transport drift classification.
- `aios-governance-preapproval-publisher`: governance/request publication only; no protection, main, tag, production, Registry, Baseline, or credential mutation.
- `semantic_protection.py`: canonical `aios.semantic-protection.v1` model.
- `ref_reconciler.py`: Gitea-main to Authority FF-CAS reconciler with idempotent readback.
All credential values remain systemd-projected and are never part of source, logs, arguments, or receipts.
The live source hashes used for this migration are recorded in the mission receipt, while this Git tree is the permanent source of truth for future installs.
@@ -0,0 +1,7 @@
# AIOS Mission Zero-Interaction Self-Test
This marker is intentionally harmless and exists only to exercise the
machine-authored branch, exact-head status publication, campaign approval gate,
protected fast-forward merge, and Authority main CAS recovery path.
Production, tags, Registry, Baseline, and V2R1C7 remain unchanged.
@@ -0,0 +1,15 @@
# Bridge v2.1 Durable Reconciliation Bootstrap
This is the planning-only bootstrap artifact for
`TASK-P0-BRIDGE-V21-DURABLE-RECONCILIATION-20260809`.
The task is bound to the exact base commit
`3f670c387692b8865a10d5b419468d8a0b64d7a4` and the development branch
`agent/p0-bridge-v21-durable-reconciliation-20260809`.
The first governed commit records the design boundary and acceptance contract
only. It does not implement the v2.1 body, mutate main, create tags, change
Baseline, or touch production. Future work must preserve absent-ref CAS,
observation-first recovery, durable JobStore state, exact Authority/Gitea/API
reconciliation, and the existing secret-scanning and credential-projection
boundaries.
@@ -0,0 +1,150 @@
# ChatGPT Bridge v2 Control Plane Convergence Design
Date: 2026-08-09
Task: `TASK-P0-CHATGPT-BRIDGE-V2-CONTROL-PLANE-CONVERGENCE-20260809`
Repository: `aios/aios`
Approved development base: `2e8fa22989808a448065d1d76b5b18948774a637`
Target branch: `agent/p0-chatgpt-bridge-v2-control-plane-convergence-20260809`
## Scope and authority
This design converges the ChatGPT GitOps Bridge, the existing Authorized Task Writer, the continuation lease protocol, the Durable GitOps Job Controller, and immutable control-plane runtime identity.
The development base is exact and must never be replaced with a dynamically resolved `HEAD`, `latest`, or `main`. If Authority `refs/heads/main` differs from the approved OID before the first governed writer push, execution fails with `BASE_OID_CHANGED`.
This task does not authorize production changes. The following remain immutable:
- Production Baseline `V2R1C7`.
- Production target OID `c7adece1cc8beffe7c718d53c775898b45d6c7f8`.
- Production tag OID `82792347937c0df59a8e02d6162775252bcad2e2`.
- Production Registry, services, deployment state, runtime, tags, and release evidence.
- Authority `main`.
Force push, history rewrite, direct main push, production mutation, tag mutation, baseline mutation, and secret commits are always denied.
## Immutable control-plane runtime identity
A control-plane release is installed into a versioned immutable directory equivalent to:
```text
/opt/aios/gitops-control-plane/<release-id-or-oid>/
```
The release manifest binds:
```text
control_plane_release_version
control_plane_target_oid
artifact_sha256
runtime_path
bridge_schema_version
repository_identity
authoritative_git_dir
deployment_state
```
The validator is independent from the mutable Git workspace. It rejects execution when the runtime path is a workspace/worktree, the observed runtime commit or artifact digest differs from the manifest, the repository identity is not `aios/aios`, the authoritative Git directory is absent or mismatched, or the Bridge schema version is absent or unsupported.
Every failure is fail closed with `CONTROL_PLANE_RUNTIME_DRIFT`. No fallback to a mutable workspace is permitted. The installer and systemd template are staging-safe; this task may install and restart only a staging/test service.
## Bridge v2 adapter
The Bridge schema identity is `aios.chatgpt-gitops-bridge.v2`. It is independent from the production baseline, control-plane release version, and runtime release commit.
The Bridge exposes:
- `getRuntimeStatus`
- `getGovernedWriteCapabilities`
- `createGovernedTask`
- `acquireWriterLease`
- `commitFileGoverned`
- `commitFilesGoverned`
- `getGitOpsJob`
- `resumeGitOpsJob`
- `reconcileGitOpsJob`
The adapter reuses existing mechanisms and does not create a second writer, lease protocol, branch state store, job database, or direct Git mutation path.
## Governed task and lease flow
`createGovernedTask` binds an exact task ID, repository, approved base OID, non-main branch, risk, and description. Existing branches are read back and bound to their observed full OID. Idempotent replay returns the original binding; conflicting replay fails closed.
`acquireWriterLease` uses `WriterLeaseRequestV2`. Continuation uses:
```text
branch_mode=CONTINUE_EXISTING
expected_branch_oid=<full OID>
generation=<monotonic integer>
create_target_branch=false
worktree_mode=detached_at_expected_head
cas_ref_update_required=true
```
Credentials remain server-side and never appear in responses, Git, durable job metadata, or logs.
## Governed commit flow
`commitFileGoverned` and `commitFilesGoverned` submit work to the existing `GitOpsJobController` and `JobStore`. The server computes payload digests. Raw file content is stored only in the content-addressed payload domain. Secret scanning, protected-path policy, repository binding, lease validation, and branch authorization run before side effects.
`commitFilesGoverned` is one atomic Git commit and enforces bounded file count, per-file size, and total payload size. Any failure leaves no partial commit or ref update.
All ref updates use compare-and-swap. Stale `expected_branch_oid` returns conflict and never triggers automatic rebase. After push, Authority and remote Gitea refs are independently read and must equal the resulting commit OID.
## Durable job lifecycle
Job reads return only non-sensitive metadata: `job_id`, `state`, `revision`, `side_effect_state`, `retryable`, `binding`, and `receipt_summary`.
Terminal jobs cannot be changed by ordinary resume. Resume requires `job_id` and `expected_revision` and uses revision CAS. Reconciliation is observation-first: for `UNKNOWN`, `RECONCILIATION_REQUIRED`, or `REMOTE_PUSH_PENDING`, local, Authority, and remote refs are read before state changes. A blind repeated push is never reconciliation.
## Legacy behavior
Legacy writes remain disabled while governed writes are enabled:
```text
legacy_writes_enabled=false
governed_writes_enabled=true
```
The global `AIOS_GITOPS_WRITES_ENABLED` flag is never enabled as the solution. Legacy operations remain temporarily visible for compatibility and diagnostics, but return:
```json
{
"error": "LEGACY_WRITE_DISABLED",
"replacement": "createGovernedTask",
"governed_writes_available": true
}
```
## OpenAPI convergence
`docs/gitops/openapi-chatgpt-actions.yaml` is the canonical Bridge v2 schema and integrates the Authorized Task Writer fragment. The canonical CAS field is `expected_branch_oid`. `expected_head_oid` may be accepted only at an explicit deprecated adapter boundary.
Contract tests require exact equality between OpenAPI required fields, runtime parser required fields, and generated GPT Action schema required fields.
## Acceptance
Automated coverage includes runtime path/OID/digest/repository drift, exact task/branch/OID/lease/CAS behavior, job DB failure, restart during remote verification, remote timeout, response loss after remote success, idempotency replay/conflict, concurrent branch movement, unauthorized or main branch writes, protected paths, secret-like payloads, disabled governed writer, missing/expired lease, wrong repository identity, and observation-first reconciliation.
The staging sequence is:
```text
getRuntimeStatus
getGovernedWriteCapabilities
createGovernedTask
acquireWriterLease
commitFilesGoverned
getGitOpsJob
verify Authority branch ref
verify remote Gitea branch ref
restart staging control-plane service
getRuntimeStatus
acquire continuation lease
second governed commit
verify remote ref again
```
Completion requires all tests, staging restart continuation, Authority/remote OID equality, unchanged production invariants, no force push, no tag rewrite, no direct main push, and no secret commit.
## Implementation boundaries
Implementation is limited to the Bridge, existing writer and job-controller adapters, runtime identity/installer, staging systemd template, OpenAPI documents, tests, and engineering evidence. Hermes Adapter integration and production cutover are outside this task and require separate approval.
+4
View File
@@ -0,0 +1,4 @@
[pytest]
testpaths = tests
python_files = test_*.py
addopts = -ra --import-mode=importlib
@@ -0,0 +1,243 @@
{
"authority_proof": {
"branch_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"commit_visible": true,
"main_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"v2r2c1_tag_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
},
"baseline_authority": {
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"authority_sha256": "d8e23bad49bdb3e4192ba708c3dfe7fc5b7cdc6a513c2f87acd6412a51867231",
"current_baseline": "V2R2C1",
"raw_byte_sidecar_verification": "PASS",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
},
"control_plane_source_oid": "b781129bd5a8b53c05ab648361424d2800166ee4",
"diff_stat": "active-runtime/app/aios_authority_git_executor.py | 24 +\n active-runtime/app/aios_chatgpt_actions_api.py | 48 +-\n active-runtime/app/aios_chatgpt_bridge_v2.py | 167 ++++\n .../app/aios_chatgpt_bridge_v2_server.py | 431 +++++++++\n active-runtime/app/aios_continuation_executor.py | 594 ++++++++++++\n active-runtime/app/aios_continuation_lease.py | 6 +\n active-runtime/app/aios_control_plane_snapshot.py | 26 +\n active-runtime/app/aios_credential_broker.py | 78 ++\n active-runtime/app/aios_gitea_publisher.py | 93 ++\n active-runtime/app/aios_gitops_job_controller.py | 16 +-\n .../app/aios_gitops_reconciliation_core.py | 80 ++\n active-runtime/app/aios_governed_policy.py | 59 ++\n active-runtime/app/aios_governed_task_bootstrap.py | 84 ++\n active-runtime/app/aios_model_api_staging.py | 1022 ++++++++++++++++++++\n .../app/authorized_task_writer_provisioning.py | 4 +-\n active-runtime/app/baseline_authority.py | 84 ++\n active-runtime/app/governed_production_release.py | 568 +++++++++++\n active-runtime/app/v2r2c1_production_adapter.py | 135 +++\n .../systemd/aios-chatgpt-bridge-v2-staging.service | 23 +\n .../aios-gitops-model-api-v2r2c1-staging.service | 51 +\n ...09-bridge-v2.1-durable-reconciliation-design.md | 15 +\n ...t-bridge-v2-control-plane-convergence-design.md | 150 +++\n pytest.ini | 4 +\n scripts/build_v2r2c1_model_api_staging.py | 145 +++\n .../aios_release_controller/manifest.py | 4 +-\n .../test_v2r1c6_release_consistency.py | 4 +-\n .../aios_v2_candidate/test_v2r1c6_release_ready.py | 16 +-\n tests/conftest.py | 65 ++\n tests/test_bridge_v2_governed_http_bootstrap.py | 90 ++\n tests/test_governed_local_writer.py | 6 +-\n tests/test_model_api_immutable_staging.py | 35 +\n 31 files changed, 4108 insertions(+), 19 deletions(-)",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"forbidden_operations": [
"direct_main_push",
"main_merge_without_owner_approval",
"force_push",
"tag_mutation",
"baseline_mutation",
"registry_mutation",
"production_restart",
"production_cutover",
"live_model_api_cutover",
"history_rewrite",
"secret_commit"
],
"governance": {
"baseline_modified": false,
"direct_main_push": false,
"force_push_used": false,
"history_rewritten": false,
"live_model_api_cutover": false,
"main_merged": false,
"owner_approval_respected": true,
"production_changed": false,
"production_restarted": false,
"registry_modified": false,
"secrets_committed": false,
"tag_rewritten": false
},
"included_path_count": 31,
"integration_branch": "agent/v2r2c1-post-release-mainline-convergence-20260809",
"integration_head_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"logical_commits": [
{
"message": "feat(v2r2c1): converge durable control-plane core",
"oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae",
"parent_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
{
"message": "docs(v2r2c1): converge durable runtime and design sources",
"oid": "5f616311bab38b0dac296ed8b0e69660e031a20e",
"parent_oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae"
},
{
"message": "feat(v2r2c1): integrate governed release overlay",
"oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5",
"parent_oid": "5f616311bab38b0dac296ed8b0e69660e031a20e"
},
{
"message": "feat(v2r2c1): wire governed release operations into durable bridge",
"oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe",
"parent_oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5"
},
{
"message": "test: repair canonical pytest repository entrypoint",
"oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0",
"parent_oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe"
},
{
"message": "test: make whole-suite collection deterministic",
"oid": "c6a1693efaede9dace9b929d884a09e162a02c89",
"parent_oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0"
},
{
"message": "test: preserve legacy suite imports under canonical collection",
"oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e",
"parent_oid": "c6a1693efaede9dace9b929d884a09e162a02c89"
},
{
"message": "test: expose repository test helpers to canonical collection",
"oid": "85897d3f6d708271dff16767265f9d14b2f29801",
"parent_oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e"
},
{
"message": "fix(v2r2c1): close slash-branch and suite contract regressions",
"oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02",
"parent_oid": "85897d3f6d708271dff16767265f9d14b2f29801"
},
{
"message": "test: align writer regression with durable idempotent replay",
"oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3",
"parent_oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02"
},
{
"message": "feat(v2r2c1): source baseline authority for model-api staging",
"oid": "f969aedaf61501a866435c712be01ff2cfe43fb1",
"parent_oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3"
},
{
"message": "feat(v2r2c1): add immutable model api staging runtime",
"oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b",
"parent_oid": "f969aedaf61501a866435c712be01ff2cfe43fb1"
},
{
"message": "fix(v2r2c1): avoid builder secret-scan false positives",
"oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d",
"parent_oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b"
},
{
"message": "fix(v2r2c1): isolate model api staging port",
"oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543",
"parent_oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d"
},
{
"message": "fix(v2r2c1): bind baseline authority and openapi staging path",
"oid": "2c6293160ed567a2098715edbb296741577c5bff",
"parent_oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543"
}
],
"main_base_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"mandatory_gates": {
"baseline_registry_readback_staging_pass": true,
"durable_source_path_manifest_complete": true,
"existing_v2r2c1_suite_pass": true,
"governed_release_suite_pass": true,
"integration_authority_git_api_equal": true,
"integration_branch_governed": true,
"logical_slice_commits_remote_verified": true,
"main_base_exact_before_branch_create": true,
"model_api_immutable_runtime_staging_pass": true,
"owner_merge_request_generated_execute_false": true,
"production_unchanged": true,
"pytest_collection_entrypoints_fixed": true,
"pytest_tests_results_recorded": true
},
"model_api_staging": {
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"baseline": {
"authority_sha256": "d8e23bad49bdb3e4192ba708c3dfe7fc5b7cdc6a513c2f87acd6412a51867231",
"current_baseline": "V2R2C1",
"deployment_state": "ACTIVE",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"generation": 4,
"previous_baseline": "V2R1C7",
"production_deployed": true,
"status": "PRODUCTION_DEPLOYED",
"tag_oid": "07fc2746b688f5f243f29b39de39f32edc3dd9d2",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
},
"baseline_status": 200,
"deployment_state": "STAGING",
"health": {
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"commit_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"deployment_state": "STAGING",
"governed_release_routing": "bridge-v2",
"legacy_global_writes_enabled": false,
"production_deployed": false,
"runtime_mode": "IMMUTABLE_STAGING",
"status": "ok",
"version": "V2R2C1"
},
"health_status": 200,
"legacy_write_probe": "423_BLOCKED",
"port": 18893,
"restart_acceptance": "PASS",
"runtime_root": "/opt/aios/gitops-model-api/2c6293160ed567a2098715edbb296741577c5bff/runtime",
"source_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"systemd": "MainPID=788782\nExecStart={ path=/usr/bin/python3 ; argv[]=/usr/bin/python3 /opt/aios/gitops-model-api/2c6293160ed567a2098715edbb296741577c5bff/runtime/app/aios_model_api_staging.py ; ignore_errors=no ; start_time=[Sun 2026-08-09 19:11:19 CST] ; stop_time=[n/a] ; pid=788782 ; code=(null) ; status=0/0 }\nActiveState=active\nSubState=running",
"unit": "aios-gitops-model-api-v2r2c1-staging.service",
"versions": {
"authoritative_head_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"baseline_generation": 4,
"baseline_sha256": "d8e23bad49bdb3e4192ba708c3dfe7fc5b7cdc6a513c2f87acd6412a51867231",
"baseline_version": "V2R2C1",
"query_error": null
},
"versions_status": 200
},
"owner_merge_request": {
"control_plane_source_oid": "b781129bd5a8b53c05ab648361424d2800166ee4",
"execute": false,
"integration_head_oid": "b2cf510e11b712bb99f20ded81459b6524257394",
"main_expected_old_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"release_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"report_commit_oid": "b2cf510e11b712bb99f20ded81459b6524257394",
"status": "OWNER_APPROVAL_REQUIRED"
},
"placeholder_fields": [],
"production": {
"baseline_mutation": false,
"health": {
"artifact_sha256": "ab4ae257346c82863f05abf5e309381a1c86612957608c3a4f4ab5cc9233f052",
"commit_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"deployment_state": "ACTIVE",
"health_timestamp": "2026-08-09T11:21:16.872581+00:00",
"production_deployed": true,
"status": "ok",
"version": "V2R2C1"
},
"health_status": 200,
"live_model_api_show": "MainPID=2816107\nActiveState=active\nSubState=running",
"live_model_api_unchanged": true,
"production_cutover": false,
"production_restart": false,
"registry_mutation": false,
"shadow_service_unchanged": true,
"shadow_show": "MainPID=1505658\nActiveState=active\nSubState=running",
"tag_mutation": false
},
"release_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"remote_proof": {
"api_branch_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"api_verification": "paginated_exact_name",
"commit_visible": true,
"git_remote_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"main_api_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"main_git_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
"report_commit_parent_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"repository": "aios/aios",
"schema": "aios.v2r2c1.post-release-mainline-convergence.acceptance.v1",
"status": "V2R2C1_MAINLINE_CONVERGENCE_READY_FOR_OWNER_MERGE",
"success_status": "V2R2C1_MAINLINE_CONVERGENCE_READY_FOR_OWNER_MERGE",
"task_id": "TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809",
"tests": {
"collection": "PASS",
"command": "pytest tests",
"failed": 0,
"passed": 709,
"skipped": 2,
"skipped_tests": [
"tests/test_gitea_tag_publication_live.py (live opt-in)",
"tests/test_signer_contract_compatibility.py (live integration opt-in)"
],
"subtests": 154
}
}
@@ -0,0 +1,89 @@
# V2R2C1 post-release mainline convergence diff summary
- task: `TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809`
- repository: `aios/aios`
- main base: `2e8fa22989808a448065d1d76b5b18948774a637`
- release target: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
- control-plane source: `b781129bd5a8b53c05ab648361424d2800166ee4`
- implementation integration head before this report commit: `2c6293160ed567a2098715edbb296741577c5bff`
- branch: `agent/v2r2c1-post-release-mainline-convergence-20260809`
## Logical governed commits
| OID | Parent | Message |
|---|---|---|
| `40b685d1c70c767c7a79a2b184348e58e3e319ae` | `2e8fa22989808a448065d1d76b5b18948774a637` | feat(v2r2c1): converge durable control-plane core |
| `5f616311bab38b0dac296ed8b0e69660e031a20e` | `40b685d1c70c767c7a79a2b184348e58e3e319ae` | docs(v2r2c1): converge durable runtime and design sources |
| `57780e316e320a5128cc2c9f12adbb19ae57aef5` | `5f616311bab38b0dac296ed8b0e69660e031a20e` | feat(v2r2c1): integrate governed release overlay |
| `e41b8d323a3e7a40440f5d32221f524074bea5fe` | `57780e316e320a5128cc2c9f12adbb19ae57aef5` | feat(v2r2c1): wire governed release operations into durable bridge |
| `b20cb8130e4c68267d5d85b02eed51cb8d4f41c0` | `e41b8d323a3e7a40440f5d32221f524074bea5fe` | test: repair canonical pytest repository entrypoint |
| `c6a1693efaede9dace9b929d884a09e162a02c89` | `b20cb8130e4c68267d5d85b02eed51cb8d4f41c0` | test: make whole-suite collection deterministic |
| `91f2ea0591787d3dbf8c2e27ab0460629d86900e` | `c6a1693efaede9dace9b929d884a09e162a02c89` | test: preserve legacy suite imports under canonical collection |
| `85897d3f6d708271dff16767265f9d14b2f29801` | `91f2ea0591787d3dbf8c2e27ab0460629d86900e` | test: expose repository test helpers to canonical collection |
| `f1110d24f30d8441f2df04e0774ee230b95c2e02` | `85897d3f6d708271dff16767265f9d14b2f29801` | fix(v2r2c1): close slash-branch and suite contract regressions |
| `c10d2a5dfcc394dde7a7022952d7b423725747a3` | `f1110d24f30d8441f2df04e0774ee230b95c2e02` | test: align writer regression with durable idempotent replay |
| `f969aedaf61501a866435c712be01ff2cfe43fb1` | `c10d2a5dfcc394dde7a7022952d7b423725747a3` | feat(v2r2c1): source baseline authority for model-api staging |
| `4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b` | `f969aedaf61501a866435c712be01ff2cfe43fb1` | feat(v2r2c1): add immutable model api staging runtime |
| `7c4e86bfc1f39e643e672968d1fdedbd72ace56d` | `4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b` | fix(v2r2c1): avoid builder secret-scan false positives |
| `7564363a30bfa7fa81ac5c7c545ec704984ec543` | `7c4e86bfc1f39e643e672968d1fdedbd72ace56d` | fix(v2r2c1): isolate model api staging port |
| `2c6293160ed567a2098715edbb296741577c5bff` | `7564363a30bfa7fa81ac5c7c545ec704984ec543` | fix(v2r2c1): bind baseline authority and openapi staging path |
## Exact diff stat
```text
active-runtime/app/aios_authority_git_executor.py | 24 +
active-runtime/app/aios_chatgpt_actions_api.py | 48 +-
active-runtime/app/aios_chatgpt_bridge_v2.py | 167 ++++
.../app/aios_chatgpt_bridge_v2_server.py | 431 +++++++++
active-runtime/app/aios_continuation_executor.py | 594 ++++++++++++
active-runtime/app/aios_continuation_lease.py | 6 +
active-runtime/app/aios_control_plane_snapshot.py | 26 +
active-runtime/app/aios_credential_broker.py | 78 ++
active-runtime/app/aios_gitea_publisher.py | 93 ++
active-runtime/app/aios_gitops_job_controller.py | 16 +-
.../app/aios_gitops_reconciliation_core.py | 80 ++
active-runtime/app/aios_governed_policy.py | 59 ++
active-runtime/app/aios_governed_task_bootstrap.py | 84 ++
active-runtime/app/aios_model_api_staging.py | 1022 ++++++++++++++++++++
.../app/authorized_task_writer_provisioning.py | 4 +-
active-runtime/app/baseline_authority.py | 84 ++
active-runtime/app/governed_production_release.py | 568 +++++++++++
active-runtime/app/v2r2c1_production_adapter.py | 135 +++
.../systemd/aios-chatgpt-bridge-v2-staging.service | 23 +
.../aios-gitops-model-api-v2r2c1-staging.service | 51 +
...09-bridge-v2.1-durable-reconciliation-design.md | 15 +
...t-bridge-v2-control-plane-convergence-design.md | 150 +++
pytest.ini | 4 +
scripts/build_v2r2c1_model_api_staging.py | 145 +++
.../aios_release_controller/manifest.py | 4 +-
.../test_v2r1c6_release_consistency.py | 4 +-
.../aios_v2_candidate/test_v2r1c6_release_ready.py | 16 +-
tests/conftest.py | 65 ++
tests/test_bridge_v2_governed_http_bootstrap.py | 90 ++
tests/test_governed_local_writer.py | 6 +-
tests/test_model_api_immutable_staging.py | 35 +
31 files changed, 4108 insertions(+), 19 deletions(-)
```
## Included durable source domains
- Bridge v2 adapter/server and governed HTTP contracts.
- Durable reconciliation core, continuation executor/lease, Authority Git CAS adapter, job controller compatibility.
- Gitea publisher/verifier, credential broker, governed policy, control-plane snapshot, release routing.
- Canonical pytest entrypoint/conftest and regression coverage.
- Source-controlled immutable model-api staging runtime, builder and systemd template.
- Selected design specifications required for durable mainline context.
## Explicitly excluded
- `main` ref mutation or merge without Owner approval.
- Production service, Registry, Baseline body/sidecar, production tags and mutable `/var/lib` state.
- Installed `/opt` artifacts, temporary staging transition state and task-local receipts/credentials.
- Production cutover/restart, force push, history rewrite and plaintext secrets.
## Verification
- `pytest tests`: **709 passed, 2 skipped, 154 subtests, 0 failed**.
- Staging model-api: immutable OID/digest bound, port 18893, health/base/ver/OpenAPI readback PASS, restart recovery PASS.
- Authority/Gitea Git/Gitea API commit equality and commit visibility PASS.
- Production remains V2R2C1 and was not restarted or cut over.
@@ -0,0 +1,254 @@
{
"control_plane_source_oid": "b781129bd5a8b53c05ab648361424d2800166ee4",
"diff_stat": "active-runtime/app/aios_authority_git_executor.py | 24 +\n active-runtime/app/aios_chatgpt_actions_api.py | 48 +-\n active-runtime/app/aios_chatgpt_bridge_v2.py | 167 ++++\n .../app/aios_chatgpt_bridge_v2_server.py | 431 +++++++++\n active-runtime/app/aios_continuation_executor.py | 594 ++++++++++++\n active-runtime/app/aios_continuation_lease.py | 6 +\n active-runtime/app/aios_control_plane_snapshot.py | 26 +\n active-runtime/app/aios_credential_broker.py | 78 ++\n active-runtime/app/aios_gitea_publisher.py | 93 ++\n active-runtime/app/aios_gitops_job_controller.py | 16 +-\n .../app/aios_gitops_reconciliation_core.py | 80 ++\n active-runtime/app/aios_governed_policy.py | 59 ++\n active-runtime/app/aios_governed_task_bootstrap.py | 84 ++\n active-runtime/app/aios_model_api_staging.py | 1022 ++++++++++++++++++++\n .../app/authorized_task_writer_provisioning.py | 4 +-\n active-runtime/app/baseline_authority.py | 84 ++\n active-runtime/app/governed_production_release.py | 568 +++++++++++\n active-runtime/app/v2r2c1_production_adapter.py | 135 +++\n .../systemd/aios-chatgpt-bridge-v2-staging.service | 23 +\n .../aios-gitops-model-api-v2r2c1-staging.service | 51 +\n ...09-bridge-v2.1-durable-reconciliation-design.md | 15 +\n ...t-bridge-v2-control-plane-convergence-design.md | 150 +++\n pytest.ini | 4 +\n scripts/build_v2r2c1_model_api_staging.py | 145 +++\n .../aios_release_controller/manifest.py | 4 +-\n .../test_v2r1c6_release_consistency.py | 4 +-\n .../aios_v2_candidate/test_v2r1c6_release_ready.py | 16 +-\n tests/conftest.py | 65 ++\n tests/test_bridge_v2_governed_http_bootstrap.py | 90 ++\n tests/test_governed_local_writer.py | 6 +-\n tests/test_model_api_immutable_staging.py | 35 +\n 31 files changed, 4108 insertions(+), 19 deletions(-)",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"excluded_paths": [
{
"path": "refs/heads/main",
"reason": "main merge requires separate Owner approval; unchanged"
},
{
"path": "refs/tags/*",
"reason": "formal tags are outside engineering convergence scope; unchanged"
},
{
"path": "baseline/current.json and sidecar",
"reason": "production mutable authority and sidecar; read-only verified"
},
{
"path": "/var/lib/aios-gitops/**",
"reason": "host runtime state and receipts excluded from repository source"
},
{
"path": "/opt/aios/**",
"reason": "installed immutable staging artifacts excluded; digest recorded in reports"
},
{
"path": "credentials and secrets",
"reason": "availability-only; no plaintext credential committed"
},
{
"path": "production runtime and Registry",
"reason": "production cutover/registry mutation forbidden"
}
],
"included_paths": [
{
"path": "active-runtime/app/aios_authority_git_executor.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_chatgpt_actions_api.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_chatgpt_bridge_v2.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_chatgpt_bridge_v2_server.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_continuation_executor.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_continuation_lease.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_control_plane_snapshot.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_credential_broker.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_gitea_publisher.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_gitops_job_controller.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_gitops_reconciliation_core.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_governed_policy.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_governed_task_bootstrap.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_model_api_staging.py",
"status": "A"
},
{
"path": "active-runtime/app/authorized_task_writer_provisioning.py",
"status": "M"
},
{
"path": "active-runtime/app/baseline_authority.py",
"status": "A"
},
{
"path": "active-runtime/app/governed_production_release.py",
"status": "A"
},
{
"path": "active-runtime/app/v2r2c1_production_adapter.py",
"status": "A"
},
{
"path": "deploy/systemd/aios-chatgpt-bridge-v2-staging.service",
"status": "A"
},
{
"path": "deploy/systemd/aios-gitops-model-api-v2r2c1-staging.service",
"status": "A"
},
{
"path": "docs/superpowers/specs/2026-08-09-bridge-v2.1-durable-reconciliation-design.md",
"status": "A"
},
{
"path": "docs/superpowers/specs/2026-08-09-chatgpt-bridge-v2-control-plane-convergence-design.md",
"status": "A"
},
{
"path": "pytest.ini",
"status": "A"
},
{
"path": "scripts/build_v2r2c1_model_api_staging.py",
"status": "A"
},
{
"path": "services/aios-release-controller/aios_release_controller/manifest.py",
"status": "M"
},
{
"path": "tests/aios_v2_candidate/test_v2r1c6_release_consistency.py",
"status": "M"
},
{
"path": "tests/aios_v2_candidate/test_v2r1c6_release_ready.py",
"status": "M"
},
{
"path": "tests/conftest.py",
"status": "A"
},
{
"path": "tests/test_bridge_v2_governed_http_bootstrap.py",
"status": "A"
},
{
"path": "tests/test_governed_local_writer.py",
"status": "M"
},
{
"path": "tests/test_model_api_immutable_staging.py",
"status": "A"
}
],
"integration_branch": "agent/v2r2c1-post-release-mainline-convergence-20260809",
"integration_head_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"logical_commits": [
{
"message": "feat(v2r2c1): converge durable control-plane core",
"oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae",
"parent_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
{
"message": "docs(v2r2c1): converge durable runtime and design sources",
"oid": "5f616311bab38b0dac296ed8b0e69660e031a20e",
"parent_oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae"
},
{
"message": "feat(v2r2c1): integrate governed release overlay",
"oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5",
"parent_oid": "5f616311bab38b0dac296ed8b0e69660e031a20e"
},
{
"message": "feat(v2r2c1): wire governed release operations into durable bridge",
"oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe",
"parent_oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5"
},
{
"message": "test: repair canonical pytest repository entrypoint",
"oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0",
"parent_oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe"
},
{
"message": "test: make whole-suite collection deterministic",
"oid": "c6a1693efaede9dace9b929d884a09e162a02c89",
"parent_oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0"
},
{
"message": "test: preserve legacy suite imports under canonical collection",
"oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e",
"parent_oid": "c6a1693efaede9dace9b929d884a09e162a02c89"
},
{
"message": "test: expose repository test helpers to canonical collection",
"oid": "85897d3f6d708271dff16767265f9d14b2f29801",
"parent_oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e"
},
{
"message": "fix(v2r2c1): close slash-branch and suite contract regressions",
"oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02",
"parent_oid": "85897d3f6d708271dff16767265f9d14b2f29801"
},
{
"message": "test: align writer regression with durable idempotent replay",
"oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3",
"parent_oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02"
},
{
"message": "feat(v2r2c1): source baseline authority for model-api staging",
"oid": "f969aedaf61501a866435c712be01ff2cfe43fb1",
"parent_oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3"
},
{
"message": "feat(v2r2c1): add immutable model api staging runtime",
"oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b",
"parent_oid": "f969aedaf61501a866435c712be01ff2cfe43fb1"
},
{
"message": "fix(v2r2c1): avoid builder secret-scan false positives",
"oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d",
"parent_oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b"
},
{
"message": "fix(v2r2c1): isolate model api staging port",
"oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543",
"parent_oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d"
},
{
"message": "fix(v2r2c1): bind baseline authority and openapi staging path",
"oid": "2c6293160ed567a2098715edbb296741577c5bff",
"parent_oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543"
}
],
"main_base_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"release_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"remote_proof": {
"api_branch_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"api_verification": "paginated_exact_name",
"commit_visible": true,
"git_remote_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"main_api_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"main_git_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
"report_commit_parent_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"repository": "aios/aios",
"schema": "aios.v2r2c1.post-release-mainline-convergence.path-manifest.v1",
"task_id": "TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809"
}
@@ -0,0 +1,37 @@
{
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"baseline_authority_path": "/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
"checks": {
"artifact_sha256": "PASS",
"baseline_registry_readback": "PASS",
"health_contract": "PASS",
"legacy_write_rejection": "PASS",
"live_service_unchanged": "PASS",
"manifest_binding": "PASS",
"openapi_readback": "PASS",
"restart_recovery": "PASS"
},
"current_live_port": 18890,
"current_live_service": "aios-gitops-model-api.service",
"deployment_state": "STAGING",
"execute": false,
"governed_release_routing": "bridge-v2",
"legacy_global_writes_enabled": false,
"live_cutover": false,
"owner_actions_required": [
"review exact source OID and artifact SHA",
"approve a separate future live model-api cutover",
"approve any production service transition"
],
"production_restart": false,
"repository_identity": "aios/aios",
"runtime_mode": "IMMUTABLE_STAGING",
"schema": "aios.v2r2c1.model-api-runtime-activation-plan.v1",
"source_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"staging_port": 18893,
"staging_runtime_root": "/opt/aios/gitops-model-api/2c6293160ed567a2098715edbb296741577c5bff/runtime",
"staging_service": "aios-gitops-model-api-v2r2c1-staging.service",
"status": "OWNER_APPROVAL_REQUIRED",
"target_version": "V2R2C1",
"task_id": "TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809"
}
@@ -0,0 +1,24 @@
{
"schema": "aios.source-registry-reconciliation.v1",
"version": "V2R2C1",
"root_cause": "PROMOTION_STATE_MACHINE_FINALIZED_BEFORE_SOURCE_REGISTRY_CONVERGENCE",
"main_before": "3244f0f17f8845592ac94d585f5ebd65693c9f29",
"tag_oid": "07fc2746b688f5f243f29b39de39f32edc3dd9d2",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"projection_sha256": "924dfc0ec58afc4e31638f6538e551c5bc8858f804cdbee5af7dce6eba7c90fe",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"approval_plan_id": "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e",
"remote_tag_verified": true,
"remote_target_verified": true,
"gitea_api_verified_by_owner_acceptance": true,
"source_entry_before": "missing",
"source_entry_added": "versions/V2R2C1.json",
"source_entry_is_exact_attested_projection": true,
"release_manifest_added": false,
"release_manifest_reason": "Historical release-manifest schema differs; no canonical V2R2C1 source-manifest generator was proven. Do not synthesize one by copying old release values.",
"runtime_backing": "immutable WS/versions bundle",
"runtime_mutated": false,
"tag_rewritten": false,
"baseline_modified": false,
"production_modified": false
}
+19 -1
View File
@@ -32,18 +32,36 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument("--tree-oid", required=True)
parser.add_argument("--output-directory", required=True, type=Path)
parser.add_argument("--name", default="aios-autonomous-release-candidate")
parser.add_argument("--release-version")
parser.add_argument("--release-binding-json", type=Path)
parser.add_argument("--artifact-mode", choices=("release", "general"))
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
release_binding = (
json.loads(args.release_binding_json.read_text(encoding="utf-8"))
if args.release_binding_json is not None
else None
)
if release_binding is not None and not isinstance(release_binding, dict):
raise ValueError("release binding JSON must be an object")
output = args.output_directory
output.mkdir(parents=True, exist_ok=True, mode=0o750)
if output.is_symlink():
raise RuntimeError("artifact output directory symlink rejected")
os.chmod(output, output.stat().st_mode & 0o750)
builder = ArtifactBuilder()
built = builder.build_from_directory(args.source_oid, args.tree_oid, args.source)
built = builder.build_from_directory(
args.source_oid,
args.tree_oid,
args.source,
release_version=args.release_version,
release_binding=release_binding,
release_mode=args.release_version is not None,
artifact_mode=args.artifact_mode,
)
builder.verify(built)
artifact_path = output / f"{args.name}.tar"
manifest_path = output / f"{args.name}.manifest.json"
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import tarfile
import tempfile
SCHEMA = 'aios.v2r2c1.model-api-staging-manifest.v1'
REQUIRED_APP = (
'aios_model_api_staging.py',
'aios_gitops_write_guard.py',
'authoritative_git.py',
'baseline_authority.py',
'gitea_tag_publication.py',
'silent_release_controller.py',
'silent_release_hardening.py',
'aios_gitops_job_controller.py',
)
OPTIONAL_APP = (
'aios_chatgpt_actions_api.py',
'aios_continuation_executor.py',
'aios_continuation_lease.py',
'aios_control_plane_snapshot.py',
)
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open('rb') as fh:
for chunk in iter(lambda: fh.read(1024 * 1024), b''):
h.update(chunk)
return h.hexdigest()
def private_marker(path: Path) -> bool:
import re
text = path.read_text(encoding='utf-8', errors='ignore')
patterns = (
r'-----BEGIN [A-Z ]*PRIVATE KEY-----',
r'(?<![A-Za-z0-9])sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])',
r'(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{20,}(?![A-Za-z0-9])',
r'(?i)aws_secret_access_key\s*[:=]\s*[A-Za-z0-9/+=]{16,}',
)
return any(re.search(pattern, text) for pattern in patterns)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument('--source-root', required=True, type=Path)
ap.add_argument('--output-root', required=True, type=Path)
ap.add_argument('--source-oid', required=True)
args = ap.parse_args()
oid = args.source_oid.lower()
if len(oid) != 40 or any(c not in '0123456789abcdef' for c in oid):
raise SystemExit('source oid must be exact 40 hex')
source = args.source_root.resolve(strict=True)
output = args.output_root.resolve()
app = source / 'active-runtime' / 'app'
runtime = source / 'runtime' / 'agent-os'
docs = source / 'docs' / 'gitops'
if not app.is_dir():
raise SystemExit('active-runtime/app missing')
paths = []
for name in REQUIRED_APP:
path = app / name
if not path.is_file():
raise SystemExit(f'required app dependency missing: {name}')
paths.append((Path('app') / name, path))
for name in OPTIONAL_APP:
path = app / name
if path.is_file():
paths.append((Path('app') / name, path))
for rel in ('orchestrator/__init__.py', 'orchestrator/action_monitor.py'):
path = runtime / rel
if not path.is_file():
raise SystemExit(f'required runtime dependency missing: {rel}')
paths.append((Path('runtime-agent-os') / rel, path))
openapi = docs / 'openapi-chatgpt-actions.yaml'
if openapi.is_file():
paths.append((Path('docs') / 'gitops' / 'openapi-chatgpt-actions.yaml', openapi))
for folder in ('config/model_registry', 'config/model_priority', 'patches', 'versions'):
root = source / folder
if root.is_dir():
for path in sorted(root.rglob('*')):
if path.is_file() and path.suffix.lower() in {'.json', '.yaml', '.yml', '.md'}:
paths.append((Path(folder) / path.relative_to(root), path))
for rel, path in paths:
if private_marker(path):
raise SystemExit(f'private marker rejected: {rel}')
included = [str(rel).replace('\\', '/') for rel, _ in paths]
manifest = {
'schema': SCHEMA,
'target_version': 'V2R2C1',
'source_oid': oid,
'repository_identity': 'aios/aios',
'deployment_state': 'STAGING',
'runtime_mode': 'IMMUTABLE_STAGING',
'runtime_entrypoint': 'app/aios_model_api_staging.py',
'legacy_global_writes_enabled': False,
'governed_release_routing': 'bridge-v2',
'artifact_sha256': 'provided-by-build-receipt-and-install-manifest',
'included_paths': included,
}
output.mkdir(parents=True, exist_ok=True)
archive = output / f'aios-v2r2c1-model-api-{oid}.tar.gz'
with tempfile.TemporaryDirectory(prefix='v2r2c1-model-api-', dir=str(output)) as tmp:
root = Path(tmp) / 'runtime'
for rel, path in paths:
target = root / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(path.read_bytes())
(root / 'manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + '\n', encoding='utf-8')
with tarfile.open(archive, 'w:gz', format=tarfile.PAX_FORMAT) as tf:
for path in sorted(root.rglob('*')):
rel = path.relative_to(root.parent)
info = tf.gettarinfo(str(path), arcname=str(rel).replace('\\', '/'))
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = 'root'
info.gname = 'root'
if path.is_file():
with path.open('rb') as fh:
tf.addfile(info, fh)
else:
tf.addfile(info)
digest = sha256(archive)
receipt = {
'schema': 'aios.v2r2c1.model-api-staging-build-receipt.v1',
'target_version': 'V2R2C1',
'source_oid': oid,
'repository_identity': 'aios/aios',
'artifact_path': str(archive),
'artifact_sha256': digest,
'deployment_state': 'STAGING',
'manifest_schema': SCHEMA,
'included_paths': included,
}
receipt_path = output / f'aios-v2r2c1-model-api-{oid}.receipt.json'
receipt_path.write_text(json.dumps(receipt, ensure_ascii=False, indent=2, sort_keys=True) + '\n', encoding='utf-8')
print(json.dumps(receipt, ensure_ascii=False, indent=2, sort_keys=True))
return 0
if __name__ == '__main__':
raise SystemExit(main())
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Generate one canonical source entry for a governed non-main change.
This utility only creates the file in the caller's worktree. It never commits,
pushes, changes main, rewrites a tag, or updates a production projection. The
caller must submit the resulting file through the existing governed writer and
approval flow.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
APP = Path(__file__).resolve().parents[1] / "active-runtime" / "app"
sys.path.insert(0, str(APP))
from version_registry import ( # noqa: E402
build_source_registry_entry,
configured_authoritative_git_dir,
configured_repository_identity,
source_registry_json,
validate_source_registry_record,
verify_approval_authority,
verify_authoritative_repository,
verify_source_registry_git_identity,
)
def parser() -> argparse.ArgumentParser:
value = argparse.ArgumentParser()
value.add_argument("--output", required=True, type=Path)
value.add_argument("--version", required=True)
value.add_argument("--tag-oid", required=True)
value.add_argument("--target-oid", required=True)
value.add_argument("--peeled-target-oid", required=True)
value.add_argument("--evidence-closure-oid", required=True)
value.add_argument("--approval-plan-id", required=True)
value.add_argument(
"--repository",
type=Path,
default=configured_authoritative_git_dir(),
help="authoritative bare repository; required for release generation",
)
value.add_argument(
"--approval-authority",
type=Path,
default=(
Path(os.environ["AIOS_GITOPS_APPROVAL_AUTHORITY"])
if os.environ.get("AIOS_GITOPS_APPROVAL_AUTHORITY")
else None
),
help="canonical approval/publication receipt for this release request",
)
value.add_argument(
"--repository-identity",
default=configured_repository_identity(),
)
return value
def main() -> int:
args = parser().parse_args()
if args.approval_authority is None:
raise ValueError("canonical approval authority is required")
verify_authoritative_repository(
args.repository,
repository_identity=args.repository_identity,
)
tag_oid = args.tag_oid.lower()
target_oid = args.target_oid.lower()
peeled_target_oid = args.peeled_target_oid.lower()
evidence_closure_oid = args.evidence_closure_oid.lower()
expected = build_source_registry_entry(
version=args.version,
tag_oid=tag_oid,
target_oid=target_oid,
peeled_target_oid=peeled_target_oid,
evidence_closure_oid=evidence_closure_oid,
approval_plan_id=args.approval_plan_id,
repository=args.repository_identity,
)
verify_source_registry_git_identity(args.repository, expected)
verify_approval_authority(args.approval_authority, expected)
destination = args.output.resolve()
if destination.name != f"{args.version}.json" or destination.parent.name != "versions":
raise ValueError("output must be versions/<version>.json")
payload = source_registry_json(expected)
destination.parent.mkdir(parents=True, exist_ok=True)
if destination.is_symlink():
raise ValueError("existing source registry path must not be a symlink")
if destination.exists() and not destination.is_file():
raise ValueError("existing source registry path must be a regular file")
if destination.exists():
current = json.loads(destination.read_text(encoding="utf-8"))
validate_source_registry_record(
current,
expected_version=args.version,
expected_tag_oid=tag_oid,
expected_target_oid=target_oid,
expected_evidence_closure_oid=evidence_closure_oid,
expected_approval_plan_id=args.approval_plan_id,
)
print(json.dumps({"status": "EXISTING_IDENTITY_VERIFIED", "path": str(destination)}))
return 0
descriptor = os.open(
destination,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
0o640,
)
try:
os.write(descriptor, payload)
os.fsync(descriptor)
finally:
os.close(descriptor)
print(json.dumps({"status": "GENERATED", "path": str(destination)}))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -8,6 +8,7 @@ import io
import json
import os
from pathlib import Path, PurePosixPath
import re
import stat
import tarfile
from typing import Mapping
@@ -75,6 +76,30 @@ class BuiltArtifact:
manifest: ArtifactManifest
@dataclass(frozen=True, slots=True)
class ReleaseIdentityBinding:
"""Immutable release identity required for a release artifact build."""
version: str
repository: str
tag_oid: str
target_oid: str
evidence_closure_oid: str
approval_plan_id: str
peeled_target_oid: str | None = None
def as_dict(self) -> dict[str, str]:
return {
"version": self.version,
"repository": self.repository,
"tag_oid": self.tag_oid,
"target_oid": self.target_oid,
"peeled_target_oid": self.peeled_target_oid or self.target_oid,
"evidence_closure_oid": self.evidence_closure_oid,
"approval_plan_id": self.approval_plan_id,
}
class ArtifactBuilder:
MAX_FILE_BYTES = 8 * 1024 * 1024
MAX_ARTIFACT_BYTES = 64 * 1024 * 1024
@@ -92,14 +117,188 @@ class ArtifactBuilder:
if not is_git_oid(value):
raise ValueError(f"{name} must be a full lowercase Git OID")
@staticmethod
def _binding_dict(
binding: ReleaseIdentityBinding | Mapping[str, str],
) -> dict[str, str]:
if isinstance(binding, ReleaseIdentityBinding):
return binding.as_dict()
values = {str(key): str(value) for key, value in binding.items()}
values.setdefault("peeled_target_oid", values.get("target_oid", ""))
return values
@classmethod
def _validate_source_registry(
cls,
root: Path,
release_version: str,
release_binding: ReleaseIdentityBinding | Mapping[str, str] | None = None,
*,
release_mode: bool = False,
) -> dict[str, str]:
if not re.fullmatch(r"V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?", release_version):
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_INVALID", "release version is not canonical"
)
path = root / "versions" / f"{release_version}.json"
if path.is_symlink() or not path.is_file():
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_MISSING",
f"artifact source is missing versions/{release_version}.json",
)
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_INVALID",
f"artifact source registry cannot be read: {path}",
) from exc
if not isinstance(value, dict):
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_INVALID", "source registry is not an object"
)
if value.get("version") != release_version or value.get("repository") != "aios/aios":
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_IDENTITY_MISMATCH",
"source registry version or repository does not match the artifact",
)
if value.get("schema") not in {
"aios.source-registry.v1",
"aios.production-registry.v2",
}:
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_INVALID", "source registry schema is invalid"
)
for field in ("tag_oid", "target_oid", "peeled_target_oid", "evidence_closure_oid"):
item = value.get(field)
if not isinstance(item, str) or not re.fullmatch(r"[0-9a-f]{40}", item):
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_INVALID",
f"source registry field is invalid: {field}",
)
if value["peeled_target_oid"] != value["target_oid"]:
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_IDENTITY_MISMATCH",
"source registry peeled target differs from target",
)
if not isinstance(value.get("approval_plan_id"), str) or not re.fullmatch(
r"sha256:[0-9a-f]{64}", value["approval_plan_id"]
):
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_INVALID", "source registry approval plan is invalid"
)
if release_mode and release_binding is None:
raise ReleaseBlocked(
"ARTIFACT_RELEASE_BINDING_REQUIRED",
"release artifact builds require an expected release identity",
)
if release_binding is not None:
expected = cls._binding_dict(release_binding)
required = (
"version",
"repository",
"tag_oid",
"target_oid",
"peeled_target_oid",
"evidence_closure_oid",
"approval_plan_id",
)
if any(not expected.get(name) for name in required):
raise ReleaseBlocked(
"ARTIFACT_RELEASE_BINDING_INVALID",
"expected release identity is incomplete",
)
for name in ("tag_oid", "target_oid", "peeled_target_oid", "evidence_closure_oid"):
if not re.fullmatch(r"[0-9a-f]{40}", expected[name]):
raise ReleaseBlocked(
"ARTIFACT_RELEASE_BINDING_INVALID",
f"expected release identity field is invalid: {name}",
)
if not re.fullmatch(r"sha256:[0-9a-f]{64}", expected["approval_plan_id"]):
raise ReleaseBlocked(
"ARTIFACT_RELEASE_BINDING_INVALID",
"expected release identity approval plan is invalid",
)
for name in required:
if value.get(name) != expected[name]:
raise ReleaseBlocked(
"ARTIFACT_SOURCE_REGISTRY_IDENTITY_MISMATCH",
f"source registry does not match expected release identity: {name}",
)
return {
"version": str(value["version"]),
"repository": str(value["repository"]),
"tag_oid": str(value["tag_oid"]),
"target_oid": str(value["target_oid"]),
"peeled_target_oid": str(value["peeled_target_oid"]),
"evidence_closure_oid": str(value["evidence_closure_oid"]),
"approval_plan_id": str(value["approval_plan_id"]),
}
def build_from_directory(
self, source_oid: str, tree_oid: str, source: str | Path
self,
source_oid: str,
tree_oid: str,
source: str | Path,
release_version: str | None = None,
release_binding: ReleaseIdentityBinding | Mapping[str, str] | None = None,
release_mode: bool | None = None,
artifact_mode: str | None = None,
) -> BuiltArtifact:
root = Path(source)
if root.is_symlink() or not root.is_dir():
raise ReleaseBlocked(
"ARTIFACT_SOURCE_LINK_REJECTED", "artifact source must be a real directory"
)
if artifact_mode not in {None, "release", "general"}:
raise ValueError("artifact_mode must be release, general, or omitted")
if artifact_mode == "release" and release_mode is False:
raise ValueError("artifact_mode=release conflicts with release_mode=False")
if artifact_mode == "general" and release_mode is True:
raise ValueError("artifact_mode=general conflicts with release_mode=True")
explicit_release_version = release_version is not None
detected_canonical_version = False
if release_version is None:
version_file = root / "VERSION"
if version_file.is_file() and not version_file.is_symlink():
release_version = version_file.read_text(encoding="utf-8").strip()
detected_canonical_version = bool(
re.fullmatch(r"V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?", release_version)
)
elif re.fullmatch(r"V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?", release_version):
detected_canonical_version = True
binding_expected: dict[str, str] | None = None
if release_binding is not None:
binding_expected = self._binding_dict(release_binding)
if not binding_expected.get("version"):
raise ReleaseBlocked(
"ARTIFACT_RELEASE_BINDING_INVALID",
"release identity binding must include version",
)
release_version = release_version or binding_expected["version"]
release_requested = bool(
explicit_release_version
or detected_canonical_version
or release_binding is not None
or release_mode is True
or artifact_mode == "release"
)
if artifact_mode == "general" and release_requested:
raise ReleaseBlocked(
"ARTIFACT_GENERAL_MODE_RELEASE_VERSION",
"general artifacts cannot contain canonical release metadata",
)
if release_requested:
release_mode = True
self._validate_source_registry(
root,
release_version or "",
release_binding,
release_mode=True,
)
files: list[ArtifactFile] = []
for directory, dirnames, filenames in os.walk(root, followlinks=False):
current = Path(directory)
@@ -61,6 +61,9 @@ def build_parser() -> argparse.ArgumentParser:
artifact_build.add_argument("--source-oid", required=True)
artifact_build.add_argument("--tree-oid", required=True)
artifact_build.add_argument("--output-directory", required=True, type=Path)
artifact_build.add_argument("--release-version")
artifact_build.add_argument("--release-binding-json", type=Path)
artifact_build.add_argument("--artifact-mode", choices=("release", "general"))
artifact_verify = artifact_sub.add_parser("verify")
artifact_verify.add_argument("--artifact", required=True, type=Path)
artifact_verify.add_argument("--manifest", required=True, type=Path)
@@ -145,7 +148,20 @@ def main(argv: list[str] | None = None) -> int:
if args.command == "artifact":
builder = ArtifactBuilder()
if args.artifact_command == "build":
built = builder.build_from_directory(args.source_oid, args.tree_oid, args.source)
release_binding = (
load_json(args.release_binding_json)
if args.release_binding_json is not None
else None
)
built = builder.build_from_directory(
args.source_oid,
args.tree_oid,
args.source,
release_version=args.release_version,
release_binding=release_binding,
release_mode=args.release_version is not None,
artifact_mode=args.artifact_mode,
)
builder.verify(built)
artifact_path = args.output_directory / "candidate.tar"
manifest_path = args.output_directory / "manifest.json"
@@ -2,7 +2,7 @@ from __future__ import annotations
import hashlib,json,re,tarfile
from pathlib import Path
from typing import Any
OID_RE=re.compile(r"^[0-9a-f]{40}$"); SHA_RE=re.compile(r"^[0-9a-f]{64}$"); VERSION_RE=re.compile(r"^V2R1C[0-9]+$")
OID_RE=re.compile(r"^[0-9a-f]{40}$"); SHA_RE=re.compile(r"^[0-9a-f]{64}$"); VERSION_RE=re.compile(r"^V[0-9]+R[0-9]+C[0-9]+(?:-[A-Za-z0-9_.-]+)?$")
REQUIRED_SOURCE={"schema","version","repository","base_ref","base_oid","binding_mode","release_readiness","production_deployed","tag_created","baseline_changed","production_service","production_port","rollback_metadata","controller_schema","required_checks"}
REQUEST_KEYS={"schema","version","repository","base_oid","target_oid","artifact_uri","artifact_sha256","artifact_size","evidence_closure_oid","approval_plan_id","execute","production_deployed","production_service","production_port","rollback_metadata","current_tag_oid","source_manifest_sha256"}
class ManifestValidationError(ValueError):
@@ -62,7 +62,7 @@ def validate_release_request(source_manifest:dict[str,Any],request_path:Path,exp
if not isinstance(request["approval_plan_id"],str) or not request["approval_plan_id"].startswith("sha256:"): raise ManifestValidationError("APPROVAL_PLAN","invalid approval plan")
if request["execute"] is not False or request["production_deployed"] is not False: raise ManifestValidationError("EXECUTION_NOT_ALLOWED","must be non-executing")
if request["production_service"]!=source_manifest["production_service"] or request["production_port"]!=source_manifest["production_port"]: raise ManifestValidationError("SERVICE_IDENTITY","request service mismatch")
if not isinstance(request["current_tag_oid"],str) or request["current_tag_oid"] not in {"V2R1C6","9e65bd59baea392294c0f253046d3ab116f63145"}: raise ManifestValidationError("PROTECTED_TAG","previous tag binding changed")
if not isinstance(request["current_tag_oid"],str) or request["current_tag_oid"] not in {"V2R1C7","82792347937c0df59a8e02d6162775252bcad2e2"}: raise ManifestValidationError("PROTECTED_TAG","previous production tag binding changed")
_sha(request["source_manifest_sha256"],"source_manifest_sha256"); return request
def build_approval_request(*,source_manifest_path:Path,target_oid:str,artifact_uri:str,artifact_sha256:str,artifact_size:int,evidence_closure_oid:str,rollback_metadata:str,current_tag_oid:str)->dict[str,Any]:
source=_json_object(source_manifest_path); _oid(target_oid,"target_oid"); _sha(artifact_sha256,"artifact_sha256"); _oid(evidence_closure_oid,"evidence_closure_oid"); request={"schema":"aios.release-request.v2","version":source["version"],"repository":source["repository"],"base_oid":source["base_oid"],"target_oid":target_oid,"artifact_uri":artifact_uri,"artifact_sha256":artifact_sha256,"artifact_size":artifact_size,"evidence_closure_oid":evidence_closure_oid,"execute":False,"production_deployed":False,"production_service":source["production_service"],"production_port":source["production_port"],"rollback_metadata":rollback_metadata,"current_tag_oid":current_tag_oid,"source_manifest_sha256":sha256_file(source_manifest_path)}; request["approval_plan_id"]="sha256:"+sha256_bytes(_canonical(request)); return request
@@ -3,13 +3,13 @@ from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).parents[2]
def test_version_and_candidate_binding():
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "V2R1C6"
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "V2R1C7"
metadata = json.loads((ROOT / "versions/V2R1C6.json").read_text(encoding="utf-8"))
assert metadata["version"] == "V2R1C6"
assert metadata["base_oid"] == "a33e2593967a180c89d44454350ccaeaf3f79b25"
assert metadata["candidate_branch"] == "agent/v2r1c6-mainline-reconciliation-20260806"
assert metadata["production_deployed"] is False
@@ -7,17 +7,19 @@ import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from deployment_state import RuntimeDeploymentState
from v2r1c6_privacy_tools import health_payload, serve # noqa: E402
def test_health_is_engineering_only() -> None:
result = health_payload()
assert result == {
"status": "ok",
"version": "V2R1C6",
"production_deployed": False,
"gitops_writes_enabled": False,
}
result = health_payload(RuntimeDeploymentState("V2R1C7", "a" * 40, "b" * 64, "STAGED"))
assert result["status"] == "ok"
assert result["version"] == "V2R1C7"
assert result["commit_oid"] == "a" * 40
assert result["artifact_sha256"] == "b" * 64
assert result["deployment_state"] == "STAGED"
assert result["production_deployed"] is False
assert result["health_timestamp"]
def test_listener_rejects_non_loopback() -> None:
@@ -1,11 +1,35 @@
import json
from pathlib import Path
import pytest
from aios_autonomous_release_candidate.artifact import ArtifactBuilder, ArtifactFile, BuiltArtifact
from aios_autonomous_release_candidate.artifact import (
ArtifactBuilder,
ArtifactFile,
BuiltArtifact,
ReleaseIdentityBinding,
)
from aios_autonomous_release_candidate.errors import EvidenceIncomplete, ReleaseBlocked
def _source_registry(root: Path) -> None:
target = "2" * 40
value = {
"schema": "aios.source-registry.v1",
"version": "V2R2C1",
"repository": "aios/aios",
"tag_oid": "1" * 40,
"tag_object": "1" * 40,
"target_oid": target,
"peeled_target_oid": target,
"evidence_closure_oid": "3" * 40,
"approval_plan_id": "sha256:" + "4" * 64,
}
path = root / "versions" / "V2R2C1.json"
path.parent.mkdir(parents=True)
path.write_text(json.dumps(value) + "\n", encoding="utf-8")
def test_artifact_is_deterministic_and_reverified() -> None:
builder = ArtifactBuilder()
files = (
@@ -32,6 +56,143 @@ def test_source_tree_rejects_symlink_and_hardlink(tmp_path: Path) -> None:
ArtifactBuilder().build_from_directory("a" * 40, "b" * 40, root)
def test_release_source_tree_requires_source_registry_entry(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "VERSION").write_text("V2R2C1\n", encoding="utf-8")
with pytest.raises(ReleaseBlocked, match="ARTIFACT_SOURCE_REGISTRY_MISSING"):
ArtifactBuilder().build_from_directory("a" * 40, "b" * 40, root)
def test_canonical_version_without_binding_fails_closed(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "VERSION").write_text("V2R2C1\n", encoding="utf-8")
_source_registry(root)
with pytest.raises(ReleaseBlocked, match="ARTIFACT_RELEASE_BINDING_REQUIRED"):
ArtifactBuilder().build_from_directory("a" * 40, "b" * 40, root)
def test_release_source_tree_requires_expected_release_identity(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "VERSION").write_text("V2R2C1\n", encoding="utf-8")
_source_registry(root)
binding = ReleaseIdentityBinding(
version="V2R2C1",
repository="aios/aios",
tag_oid="1" * 40,
target_oid="2" * 40,
evidence_closure_oid="3" * 40,
approval_plan_id="sha256:" + "4" * 64,
)
built = ArtifactBuilder().build_from_directory(
"a" * 40,
"b" * 40,
root,
release_binding=binding,
)
ArtifactBuilder().verify(built)
def test_canonical_version_with_malformed_binding_fails_closed(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "VERSION").write_text("V2R2C1\n", encoding="utf-8")
_source_registry(root)
with pytest.raises(ReleaseBlocked, match="ARTIFACT_RELEASE_BINDING_INVALID"):
ArtifactBuilder().build_from_directory(
"a" * 40,
"b" * 40,
root,
release_binding={
"version": "V2R2C1",
"repository": "aios/aios",
"tag_oid": "not-an-oid",
},
)
def test_explicit_general_artifact_without_release_version_passes(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "README.txt").write_text("general candidate\n", encoding="utf-8")
built = ArtifactBuilder().build_from_directory(
"a" * 40,
"b" * 40,
root,
artifact_mode="general",
)
assert "README.txt" in built.manifest.files
def test_explicit_general_mode_rejects_canonical_version(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "VERSION").write_text("V2R2C1\n", encoding="utf-8")
_source_registry(root)
with pytest.raises(ReleaseBlocked, match="ARTIFACT_GENERAL_MODE_RELEASE_VERSION"):
ArtifactBuilder().build_from_directory(
"a" * 40,
"b" * 40,
root,
artifact_mode="general",
)
@pytest.mark.parametrize(
"field,wrong",
[
("repository", "other/repository"),
("version", "V2R2C2"),
("tag_oid", "9" * 40),
("target_oid", "8" * 40),
("peeled_target_oid", "7" * 40),
("evidence_closure_oid", "6" * 40),
("approval_plan_id", "sha256:" + "5" * 64),
],
)
def test_release_source_tree_rejects_every_identity_mismatch(
tmp_path: Path, field: str, wrong: str
) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "VERSION").write_text("V2R2C1\n", encoding="utf-8")
_source_registry(root)
binding = {
"version": "V2R2C1",
"repository": "aios/aios",
"tag_oid": "1" * 40,
"target_oid": "2" * 40,
"peeled_target_oid": "2" * 40,
"evidence_closure_oid": "3" * 40,
"approval_plan_id": "sha256:" + "4" * 64,
}
binding[field] = wrong
with pytest.raises(ReleaseBlocked, match="ARTIFACT_SOURCE_REGISTRY"):
ArtifactBuilder().build_from_directory(
"a" * 40,
"b" * 40,
root,
release_binding=binding,
)
def test_release_mode_without_binding_fails_closed(tmp_path: Path) -> None:
root = tmp_path / "source"
root.mkdir()
(root / "VERSION").write_text("V2R2C1\n", encoding="utf-8")
_source_registry(root)
with pytest.raises(ReleaseBlocked, match="ARTIFACT_RELEASE_BINDING_REQUIRED"):
ArtifactBuilder().build_from_directory(
"a" * 40,
"b" * 40,
root,
release_version="V2R2C1",
release_mode=True,
)
def test_artifact_rejects_secret_markers() -> None:
with pytest.raises(ReleaseBlocked, match="ARTIFACT_SECRET_MARKER"):
ArtifactBuilder().build(
+65
View File
@@ -0,0 +1,65 @@
from __future__ import annotations
import importlib.util
import sys
import types
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
TEST_ROOT = REPOSITORY_ROOT / "tests"
SERVICE_PACKAGE_ROOT = REPOSITORY_ROOT / "services" / "aios-autonomous-release-candidate"
ACTIVE_RUNTIME_ROOT = REPOSITORY_ROOT / "active-runtime"
ACTIVE_RUNTIME_APP = ACTIVE_RUNTIME_ROOT / "app"
for path in (TEST_ROOT, SERVICE_PACKAGE_ROOT, ACTIVE_RUNTIME_APP):
value = str(path)
if value not in sys.path:
sys.path.insert(0, value)
def load_module(relative: str):
path = REPOSITORY_ROOT / relative
spec = importlib.util.spec_from_file_location("v2r1c6_privacy_tools", path)
if spec is None or spec.loader is None:
raise RuntimeError("module_load_failed")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def _install_active_runtime_namespace() -> None:
package = sys.modules.get("active_runtime")
if package is None:
package = types.ModuleType("active_runtime")
package.__path__ = [str(ACTIVE_RUNTIME_ROOT)]
package.__package__ = "active_runtime"
sys.modules["active_runtime"] = package
app = sys.modules.get("active_runtime.app")
if app is None:
app = types.ModuleType("active_runtime.app")
app.__path__ = [str(ACTIVE_RUNTIME_APP)]
app.__package__ = "active_runtime"
sys.modules["active_runtime.app"] = app
package.app = app
def _install_legacy_test_module(name: str, relative: str) -> None:
if name in sys.modules:
return
path = REPOSITORY_ROOT / relative
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError("legacy_test_module_load_failed:" + name)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
_install_active_runtime_namespace()
sys.modules.setdefault("conftest", sys.modules[__name__])
_install_legacy_test_module(
"test_native_adapter_contracts",
"tests/autonomous_release/test_native_adapter_contracts.py",
)
+118
View File
@@ -0,0 +1,118 @@
from __future__ import annotations
import hashlib
import json
import pytest
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore
HEAD = "a" * 40
BASE = "b" * 40
CURRENT = "c" * 40
def _controller(tmp_path):
store = JobStore(tmp_path / "jobs", payload_root=tmp_path / "payloads")
return GitOpsJobController(store), store
def _request():
payload = b"mission-recovery-test"
return {
"campaign_id": "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807",
"repository": "aios-gitops",
"operation": "PROTECTED_MERGE",
"branch": "agent/recovery-test",
"expected_branch_oid": HEAD,
"expected_main_oid": BASE,
"payload_digest": hashlib.sha256(payload).hexdigest(),
"pr_number": 53,
"task_id": "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1",
}, payload
def _proof(proof_job_id: str, **overrides):
proof = {
"proof_schema": "aios.job-recovery.v1",
"job_id": proof_job_id,
"operation": "PROTECTED_MERGE",
"pr_number": 53,
"pr_merged": True,
"protected_merge": True,
"head_oid": HEAD,
"base_oid": BASE,
"main_oid_after_protected_merge": HEAD,
"gitea_main_oid_after_protected_merge": HEAD,
"authority_main_oid_after_protected_merge": HEAD,
"current_gitea_main_oid": CURRENT,
"current_authority_main_oid": CURRENT,
"current_main_descends_from_protected_merge": True,
"force_push": False,
"machine_actor": "aios-autonomy-agent",
}
proof.update(overrides)
return proof
def _manual_job(tmp_path):
controller, store = _controller(tmp_path)
request, payload = _request()
submitted = controller.submit(request, payload=payload)
manual = controller.advance(
submitted.job_id,
JobState.MANUAL_OWNER_REVIEW_REQUIRED,
side_effect_state="MANUAL_OWNER_REVIEW_REQUIRED",
receipt={"historical": "preserve-me"},
expected_revision=submitted.revision,
)
return controller, store, manual
def test_verified_external_recovery_preserves_history_and_is_idempotent(tmp_path):
controller, store, manual = _manual_job(tmp_path)
proof = _proof(manual.job_id)
completed = controller.complete_verified_external_side_effect(
manual.job_id, proof, expected_revision=manual.revision
)
assert completed.state is JobState.COMPLETED
assert completed.side_effect_state == "COMPLETED"
receipt = json.loads(store.read(manual.job_id)["receipt_json"])
assert receipt["recovered_from_historical_manual"] is True
assert receipt["historical_receipt"] == {"historical": "preserve-me"}
assert receipt["proof"] == proof
duplicate = controller.complete_verified_external_side_effect(manual.job_id, proof)
assert duplicate.state is JobState.COMPLETED
assert duplicate.revision == completed.revision
@pytest.mark.parametrize(
"field,value",
[
("job_id", "job-wrong"),
("head_oid", "d" * 40),
("main_oid_after_protected_merge", "d" * 40),
("force_push", True),
("operation", "PUSH"),
],
)
def test_verified_external_recovery_rejects_mismatched_proof(tmp_path, field, value):
controller, _store, manual = _manual_job(tmp_path)
proof_job_id = value if field == "job_id" else manual.job_id
overrides = {} if field == "job_id" else {field: value}
with pytest.raises(JobError):
controller.complete_verified_external_side_effect(
manual.job_id, _proof(proof_job_id, **overrides)
)
assert controller.store.read(manual.job_id)["state"] == JobState.MANUAL_OWNER_REVIEW_REQUIRED.value
def test_ordinary_terminal_advance_and_resume_remain_denied(tmp_path):
controller, _store, manual = _manual_job(tmp_path)
with pytest.raises(JobError, match="JOB_TERMINAL"):
controller.advance(manual.job_id, JobState.COMPLETED, side_effect_state="COMPLETED")
with pytest.raises(JobError, match="JOB_TERMINAL"):
controller.resume(manual.job_id)
@@ -0,0 +1,78 @@
import subprocess
from pathlib import Path
import pytest
from governance.ref_reconciler import RefReconciler, RefReconciliationError
from governance.semantic_protection import semantic_digest, semantic_policy, transport_digest
def protection(**overrides):
value = {
"branch_name": "main",
"enable_push": False,
"enable_force_push": False,
"enable_merge_whitelist": True,
"merge_whitelist_usernames": ["owner"],
"required_approvals": 0,
"enable_status_check": True,
"status_check_contexts": ["owner-approval-gate", "py-compile"],
"block_admin_merge_override": True,
"block_on_official_review_requests": True,
"block_on_outdated_branch": True,
"block_on_rejected_reviews": True,
"dismiss_stale_approvals": True,
"ignore_stale_approvals": False,
"enable_approvals_whitelist": True,
"approvals_whitelist_username": ["owner"],
"enable_bypass_allowlist": False,
"enable_force_push_allowlist": False,
"enable_push_whitelist": False,
"protected_file_patterns": "[]",
"unprotected_file_patterns": "[]",
}
value.update(overrides)
return value
def test_transport_metadata_does_not_change_semantic_digest():
before = protection(updated_at="old")
after = protection(updated_at="new")
assert transport_digest(before) != transport_digest(after)
assert semantic_digest(before) == semantic_digest(after)
def test_stage1_whitelist_is_a_semantic_change():
before = protection()
after = protection(merge_whitelist_usernames=["owner", "aios-autonomy-agent"])
assert semantic_policy(before)["merge_whitelist_user_ids"] == [2]
assert semantic_policy(after)["merge_whitelist_user_ids"] == [2, 153]
assert semantic_digest(before) != semantic_digest(after)
def test_ref_reconciler_rejects_gitea_mismatch(tmp_path: Path):
reconciler = RefReconciler(tmp_path / "missing.git")
with pytest.raises(RefReconciliationError, match="GITEA_MAIN_EXPECTATION_MISMATCH"):
reconciler.reconcile_main(expected_oid="a" * 40, merged_oid="b" * 40, gitea_main_oid="c" * 40)
def test_ref_reconciler_ff_cas_and_idempotency(tmp_path: Path):
repo = tmp_path / "repo.git"
subprocess.run(["git", "init", "--bare", str(repo)], check=True, capture_output=True)
worktree = tmp_path / "work"
subprocess.run(["git", "clone", str(repo), str(worktree)], check=True, capture_output=True)
subprocess.run(["git", "-C", str(worktree), "config", "user.email", "test@example.invalid"], check=True)
subprocess.run(["git", "-C", str(worktree), "config", "user.name", "test"], check=True)
(worktree / "one.txt").write_text("one\n", encoding="utf-8")
subprocess.run(["git", "-C", str(worktree), "add", "one.txt"], check=True)
subprocess.run(["git", "-C", str(worktree), "commit", "-m", "one"], check=True, capture_output=True)
old = subprocess.check_output(["git", "-C", str(worktree), "rev-parse", "HEAD"], text=True).strip()
subprocess.run(["git", "-C", str(worktree), "push", "origin", "HEAD:refs/heads/main"], check=True, capture_output=True)
(worktree / "two.txt").write_text("two\n", encoding="utf-8")
subprocess.run(["git", "-C", str(worktree), "add", "two.txt"], check=True)
subprocess.run(["git", "-C", str(worktree), "commit", "-m", "two"], check=True, capture_output=True)
new = subprocess.check_output(["git", "-C", str(worktree), "rev-parse", "HEAD"], text=True).strip()
subprocess.run(["git", "-C", str(worktree), "push", "origin", "HEAD:refs/heads/main"], check=True, capture_output=True)
reconciler = RefReconciler(repo)
assert reconciler.reconcile_main(expected_oid=old, merged_oid=new, gitea_main_oid=new) == new
assert reconciler.reconcile_main(expected_oid=old, merged_oid=new, gitea_main_oid=new) == new
+169
View File
@@ -0,0 +1,169 @@
from __future__ import annotations
import copy
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from campaign_activation import CampaignActivationError, build_active_policy
from release_campaign_bootstrap import campaign_policy
import governance.stage2_gate_migration as stage2
ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
MAIN = "2a32e30c5cbd894cf007326917776893b3020231"
def protection(statuses=None):
return {
"branch_name": "main",
"enable_push": False,
"enable_force_push": False,
"enable_merge_whitelist": True,
"merge_whitelist_usernames": ["aios-autonomy-agent", "owner"],
"required_approvals": 0,
"enable_status_check": True,
"status_check_contexts": statuses or [
"autonomy-unit-tests", "owner-approval-gate", "py-compile"
],
"block_admin_merge_override": True,
"block_on_official_review_requests": True,
"block_on_outdated_branch": True,
"block_on_rejected_reviews": True,
"dismiss_stale_approvals": True,
"ignore_stale_approvals": False,
"enable_approvals_whitelist": True,
"approvals_whitelist_username": ["owner"],
"enable_bypass_allowlist": False,
"enable_force_push_allowlist": False,
"enable_push_whitelist": False,
"protected_file_patterns": "[]",
"unprotected_file_patterns": "[]",
"updated_at": "transport-only",
}
def facts(**overrides):
value = {
"campaign_id": stage2.CAMPAIGN_ID,
"campaign_active": True,
"campaign_revoked": False,
"machine_actor": "aios-autonomy-agent",
"required_checks_all_success": True,
"required_checks_same_head": True,
"unresolved_conversations": 0,
"mergeable": True,
"protected_target": "main",
"current_main_oid": MAIN,
"gitea_main_oid": MAIN,
"current_main_lineage_verified": True,
}
value.update(overrides)
return value
def test_stage2_replaces_only_gate_and_is_idempotent(monkeypatch):
before = protection()
monkeypatch.setattr(stage2, "STAGE1_SEMANTIC_SHA", stage2.semantic_digest(before))
status, target = stage2.build_target(before)
assert status == "READY"
assert "owner-approval-gate" not in target["status_check_contexts"]
assert "campaign-approval-gate" in target["status_check_contexts"]
for key in before:
if key != "status_check_contexts":
assert target[key] == before[key]
status2, target2 = stage2.build_target(target)
assert status2 == "IDEMPOTENT_SUCCESS"
assert target2 == target
@pytest.mark.parametrize(
"field,value",
[
("campaign_active", False),
("campaign_revoked", True),
("required_checks_all_success", False),
("required_checks_same_head", False),
("unresolved_conversations", 1),
("mergeable", False),
("current_main_oid", "f" * 40),
("machine_actor", "owner"),
],
)
def test_stage2_fails_closed_on_gate_fact_drift(field, value):
with pytest.raises(stage2.Stage2GateError):
stage2.validate_stage2_facts(facts(**{field: value}))
def test_stage2_canonicalizer_accepts_live_empty_pattern_fields():
before = protection()
before["protected_file_patterns"] = ""
before["unprotected_file_patterns"] = ""
monkeypatch = pytest.MonkeyPatch()
try:
monkeypatch.setattr(stage2, "STAGE1_SEMANTIC_SHA", stage2.semantic_digest(before))
status, target = stage2.build_target(before)
assert status == "READY"
assert target["protected_file_patterns"] == ""
assert target["unprotected_file_patterns"] == ""
finally:
monkeypatch.undo()
def test_stage2_fails_closed_on_semantic_drift(monkeypatch):
before = protection()
monkeypatch.setattr(stage2, "STAGE1_SEMANTIC_SHA", "f" * 64)
with pytest.raises(stage2.Stage2GateError, match="SEMANTIC_PRECONDITION_INVALID"):
stage2.build_target(before)
def activation_facts(**overrides):
value = {
"campaign_id": stage2.CAMPAIGN_ID,
"repository": "aios/aios",
"source_main_oid": MAIN,
"gitea_main_oid": MAIN,
"authority_main_oid": MAIN,
"runtime_source_oid": MAIN,
"durable_job_state": "COMPLETED",
"writer_ready": True,
"gitea_machine_authenticated": True,
"governance_executor_ready": True,
"preapproval_publisher_ready": True,
"runtime_exact_main": True,
"main_lineage_verified": True,
"campaign_revoked": False,
}
value.update(overrides)
return value
def test_campaign_activation_changes_only_active_state():
pending = campaign_policy(stage2.CAMPAIGN_ID, ANCHOR)
active = build_active_policy(pending, activation_facts())
assert active["activation_state"] == "ACTIVE"
assert active["active"] is True
for key in pending:
if key not in {"activation_state", "active"}:
assert active[key] == pending[key]
@pytest.mark.parametrize(
"field,value",
[
("source_main_oid", "e" * 40),
("gitea_main_oid", "e" * 40),
("durable_job_state", "MANUAL_OWNER_REVIEW_REQUIRED"),
("writer_ready", False),
("governance_executor_ready", False),
("campaign_revoked", True),
],
)
def test_campaign_activation_fails_closed(field, value):
pending = campaign_policy(stage2.CAMPAIGN_ID, ANCHOR)
with pytest.raises(CampaignActivationError):
build_active_policy(pending, activation_facts(**{field: value}))
+1 -1
View File
@@ -158,7 +158,7 @@ def test_execute_orders_registry_before_baseline_and_baseline_last(
)
monkeypatch.setattr(
"approved_release_executor.verify_artifact",
lambda binding: {"verified": True},
lambda binding, **kwargs: {"verified": True},
)
calls = []
+1
View File
@@ -73,6 +73,7 @@ class AuthoritativeGitContextTests(unittest.TestCase):
json.dumps({"branch": "patch/context"}), encoding="utf-8"
)
self.env = {
"AIOS_TEST_MODE": "1",
"AIOS_GITOPS_AUTHORITATIVE_GIT_DIR": str(self.bare),
"AIOS_GITOPS_REPOSITORY_IDENTITY": "aios/aios",
}
+95
View File
@@ -0,0 +1,95 @@
from types import SimpleNamespace
import json
from pathlib import Path
import sys
import tempfile
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
import release_campaign_authorization as a
import release_campaign_bootstrap as b
import campaign_release_acceptance as c
import release_campaign_orchestrator as o
ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
def policy(state="ACTIVE", active=True):
value = b.campaign_policy("AIOS-AUTONOMOUS-RELEASE-V1", ANCHOR)
value["activation_state"] = state
value["active"] = active
return value
def test_campaign_policy_exact():
assert a.validate_policy(policy())["normal_owner_interaction_required"] is False
def test_denied_operation_fails():
obj = a.CampaignAuthorization(Path("/tmp/x"), policy(), "0"*64)
with pytest.raises(a.CampaignError):
obj.assert_operation("force_push")
def test_release_acceptance_has_no_second_owner_interaction():
obj = a.CampaignAuthorization(Path("/tmp/x"), policy(), "0"*64)
binding = {
"version": "V2R1C8",
"target_oid": "a"*40,
"evidence_closure_oid": "b"*40,
"artifact_sha256": "c"*64,
"artifact_size": 123,
"approval_plan_id": "sha256:" + "d"*64,
}
receipt = c.evaluate(
obj,
binding,
{"object_type": "commit", "target_is_ancestor": True, "closure_oid": "b"*40},
{"sha256": "c"*64, "size": 123, "retained": True},
{"install": "PASS", "health": "PASS", "restart": "PASS", "rollback_readiness": "PASS"},
{"repository": "aios/aios", "target_in_main_lineage": True},
{"conflict": False},
)
assert receipt["accepted"] is True
assert receipt["owner_interaction_required"] is False
def test_changed_binding_rejected():
obj = a.CampaignAuthorization(Path("/tmp/x"), policy(), "0"*64)
with pytest.raises(c.AcceptanceError):
c.evaluate(
obj,
{
"version": "V2R1C8",
"target_oid": "a"*40,
"evidence_closure_oid": "b"*40,
"artifact_sha256": "c"*64,
"artifact_size": 123,
"approval_plan_id": "sha256:" + "d"*64,
},
{"object_type": "commit", "target_is_ancestor": True, "closure_oid": "b"*40},
{"sha256": "e"*64, "size": 123, "retained": True},
{"install": "PASS", "health": "PASS", "restart": "PASS", "rollback_readiness": "PASS"},
{"repository": "aios/aios", "target_in_main_lineage": True},
{"conflict": False},
)
def test_orchestrator_recoverable_failure_resumes_without_owner():
state = o.ReleaseState("V2R1C8", state="STAGING", last_stable_state="STAGING")
state.recoverable_failure("transient_502")
assert state.as_dict()["owner_interaction_required"] is False
state.resume()
assert state.state == "STAGING"
def test_only_red_line_can_enter_manual_review():
state = o.ReleaseState("V2R1C8")
with pytest.raises(o.OrchestratorError):
state.manual_review("pytest_failed")
state.manual_review("formal_same_name_tag_conflict")
assert state.as_dict()["owner_interaction_required"] is True
@@ -0,0 +1,91 @@
import base64
from pathlib import Path
from aios_authority_git_executor import AuthorityGitExecutor
from aios_chatgpt_bridge_v2_server import BridgeHandler, RuntimeConfig
from aios_continuation_executor import GiteaContinuationRemote, ServerSideCredentialProjection
from aios_continuation_lease import WriterLeaseRequestV2
class Probe:
def __init__(self, present=False):
self.present = present
def branch_exists(self, _branch):
return self.present
def branch_head(self, _branch):
return "a" * 40
def is_ancestor(self, _older, _newer):
return True
def commit_exists(self, _oid):
return True
def test_create_new_lease_is_exact_base_and_absent():
request = WriterLeaseRequestV2(
task_id="TASK-P0-BRIDGE-V21-DURABLE-RECONCILIATION-20260809",
repository="aios/aios",
branch_mode="CREATE_NEW",
base_oid="a" * 40,
expected_branch_oid="a" * 40,
target_branch="agent/p0-bridge-v21-durable-reconciliation-20260809",
generation=1,
)
request.validate(Probe())
def test_create_new_lease_rejects_existing_target():
request = WriterLeaseRequestV2(
task_id="task",
repository="aios/aios",
branch_mode="CREATE_NEW",
base_oid="a" * 40,
expected_branch_oid="a" * 40,
target_branch="agent/existing-target",
generation=1,
)
try:
request.validate(Probe(True))
except Exception as exc:
assert str(exc) == "TARGET_BRANCH_ALREADY_EXISTS"
else:
raise AssertionError("existing CREATE_NEW target must fail")
def test_slash_branch_uses_paginated_collection():
credential = ServerSideCredentialProjection(Path("/tmp/key"), Path("/tmp/token"), active=True)
calls = []
class FakeRemote(GiteaContinuationRemote):
def _api(self, path):
calls.append(path)
if "page=1" in path:
return [{"name": f"agent/other-{i}", "commit": {"id": "b" * 40}} for i in range(50)]
return [{"name": "agent/slash/branch", "commit": {"id": "c" * 40}}]
remote = FakeRemote(Path("/tmp"), credential)
assert remote.read_api_branch("agent/slash/branch") == "c" * 40
assert calls[0].endswith("branches?page=1&limit=50")
assert calls[1].endswith("branches?page=2&limit=50")
def test_http_route_contract_is_complete():
expected = {
"/v2/runtime", "/v2/capabilities", "/v2/governed-task",
"/v2/writer-lease", "/v2/commit-file", "/v2/commit-files",
"/v2/branch/publish",
"/v2/job", "/v2/job/resume", "/v2/job/reconcile",
"/v2/tag/approval-request",
"/v2/tag/publish",
"/v2/tag/status",
"/v2/release/artifact",
"/v2/release/execute",
"/v2/release/consistency",
"/v2/release/acceptance",
}
from aios_chatgpt_bridge_v2_server import ROUTES
assert set(ROUTES) == expected
@@ -0,0 +1,265 @@
from __future__ import annotations
from datetime import datetime, timezone
import tempfile
from concurrent.futures import ThreadPoolExecutor
import ast
from pathlib import Path
import pytest
from aios_continuation_lease import (
CAMPAIGN_HEAD,
LINEAGE_ANCHOR,
ContinuationError,
ContinuationLeaseManager,
WriterLeaseRequestV2,
)
from aios_gitops_job_controller import JobState, JobStore, GitOpsJobController, JobError, PayloadStore
from aios_gitea_machine_broker import MachineBrokerError, MachineCredentialProjection, reconcile_exact_branch, reconcile_fast_forward
from aios_gitops_writer_worker import runtime_policy
from aios_control_plane_runtime_status import RuntimeStatus
from aios_control_plane_sentinel import SentinelInputs, evaluate
from authorized_task_writer_provisioning import validate_writer_lease_request_v2
class Probe:
def __init__(self):
self.head = CAMPAIGN_HEAD
def branch_exists(self, branch):
return branch == "agent/autonomous-release-campaign-v1-20260807"
def branch_head(self, branch):
return self.head
def is_ancestor(self, older, newer):
return (older == LINEAGE_ANCHOR and newer == CAMPAIGN_HEAD) or (
older != newer and older in {CAMPAIGN_HEAD, "c" * 40} and newer in {"c" * 40, "d" * 40}
)
class CAS:
def __init__(self):
self.calls = []
self.stale = False
def compare_and_swap_branch(self, branch, expected, new):
if self.stale:
raise RuntimeError("stale")
self.calls.append((branch, expected, new))
return new
def request(generation=1):
return WriterLeaseRequestV2(
task_id="AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1",
repository="aios/aios",
branch_mode="CONTINUE_EXISTING",
base_oid=LINEAGE_ANCHOR,
expected_branch_oid=CAMPAIGN_HEAD,
target_branch="agent/autonomous-release-campaign-v1-20260807",
generation=generation,
)
def test_continue_existing_accepts_exact_head_and_lineage():
manager = ContinuationLeaseManager(
Probe(), CAS(), now=lambda tz: datetime(2026, 8, 8, tzinfo=timezone.utc)
)
lease = manager.open(request())
assert lease.current_head_oid == CAMPAIGN_HEAD
assert lease.multi_commit and lease.append_only and lease.recoverable
def test_continue_existing_rejects_branch_head_conflict():
probe = Probe()
probe.head = "c" * 40
with pytest.raises(ContinuationError, match="EXISTING_BRANCH_OID_CONFLICT"):
request().validate(probe)
def test_provisioning_contract_v2_never_creates_existing_branch():
raw = {
"schema": "aios.task-writer-lease-request.v2",
"task_id": request().task_id,
"repository": request().repository,
"branch_mode": request().branch_mode,
"base_oid": request().base_oid,
"expected_branch_oid": request().expected_branch_oid,
"target_branch": request().target_branch,
"generation": 1,
"lease_seconds": 900,
}
result = validate_writer_lease_request_v2(raw, Probe())
assert result["create_target_branch"] is False
assert result["cas_ref_update_required"] is True
def test_append_uses_compare_and_swap_and_allows_multiple_commits():
cas = CAS()
manager = ContinuationLeaseManager(Probe(), cas)
lease = manager.open(request())
next_lease = manager.append_commit(lease, "c" * 40)
manager.append_commit(next_lease, "d" * 40)
assert cas.calls == [
(request().target_branch, CAMPAIGN_HEAD, "c" * 40),
(request().target_branch, "c" * 40, "d" * 40),
]
def test_stale_cas_fails_closed():
cas = CAS()
cas.stale = True
manager = ContinuationLeaseManager(Probe(), cas)
with pytest.raises(ContinuationError, match="BRANCH_LEASE_STALE"):
manager.append_commit(manager.open(request()), "c" * 40)
def test_job_idempotency_and_recovery():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
req = {
"campaign_id": "campaign",
"repository": "aios/aios",
"operation": "commit-file",
"branch": request().target_branch,
"expected_branch_oid": CAMPAIGN_HEAD,
"payload_digest": "b" * 64,
"payload": {"files": ["active-runtime/app/x.py"]},
}
first = controller.submit(req)
duplicate = controller.submit(req)
assert first.job_id == duplicate.job_id
assert duplicate.side_effect_state == "SAME_JOB"
failed = controller.advance(first.job_id, JobState.RECOVERABLE_FAILURE, side_effect_state="UNKNOWN")
assert failed.side_effect_state == "RESUME_SAME_JOB"
resumed = controller.resume(first.job_id)
assert resumed.job_id == first.job_id and resumed.state == JobState.REQUESTED
tables = {"jobs", "idempotency_keys", "branch_leases", "state_transitions", "receipts"}
with JobStore(temp)._connect() as connection:
assert tables.issubset({row["name"] for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")})
assert connection.execute("PRAGMA journal_mode").fetchone()[0].lower() == "wal"
def test_job_state_cannot_regress():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
req = {
"campaign_id": "campaign",
"repository": "aios/aios",
"operation": "commit-file",
"branch": request().target_branch,
"expected_branch_oid": CAMPAIGN_HEAD,
"payload_digest": "b" * 64,
}
job = controller.submit(req)
controller.advance(job.job_id, JobState.AUTHORIZED, side_effect_state="AUTHORIZED")
with pytest.raises(JobError):
controller.advance(job.job_id, JobState.REQUESTED, side_effect_state="BAD")
def test_machine_broker_rejects_owner_identity_and_plaintext_reference():
with pytest.raises(MachineBrokerError):
MachineCredentialProjection("owner-token-value", "aiman")
projection = MachineCredentialProjection.from_mapping({"scheme": "systemd-credential", "name": "gitea-machine-token", "account": "aios-machine"})
assert projection.reference == "systemd-credential:gitea-machine-token"
def test_ref_reconciler_is_fail_closed():
assert reconcile_exact_branch("a" * 40, None, "a" * 40) == "CREATE_REQUIRED"
assert reconcile_exact_branch("a" * 40, "b" * 40, "a" * 40) == "MANUAL_OWNER_REVIEW_REQUIRED"
assert reconcile_fast_forward("a" * 40, "a" * 40, "b" * 40, old_is_ancestor_of_new=True) == "FAST_FORWARD_REQUIRED"
def test_payload_store_is_content_addressed_and_fail_closed():
with tempfile.TemporaryDirectory() as temp:
store = PayloadStore(temp)
payload = b"approved-payload"
digest = __import__("hashlib").sha256(payload).hexdigest()
reference, size = store.put(payload, digest)
assert store.read_verified(reference, digest, size) == payload
with pytest.raises(JobError, match="PAYLOAD_DIGEST_MISMATCH"):
store.put(b"changed", digest)
def test_concurrent_duplicate_submit_collapses_to_one_job():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
req = {"campaign_id": "campaign", "repository": "aios/aios", "operation": "commit-file", "branch": request().target_branch, "expected_branch_oid": CAMPAIGN_HEAD, "payload_digest": "b" * 64}
with ThreadPoolExecutor(max_workers=4) as pool:
receipts = list(pool.map(lambda _: controller.submit(req), range(8)))
assert len({item.job_id for item in receipts}) == 1
def test_gate_split_keeps_legacy_mutation_false():
assert runtime_policy({"AIOS_GITOPS_WRITES_ENABLED": "false", "AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED": "true"}) == {
"legacy_global_write": False,
"durable_job_execution": True,
"repository_mutation_allowed": True,
}
def test_revision_cas_and_receipt_are_transactional():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
req = {"campaign_id": "campaign", "repository": "aios/aios", "operation": "commit-file", "branch": request().target_branch, "expected_branch_oid": CAMPAIGN_HEAD, "payload_digest": "b" * 64}
job = controller.submit(req)
authorized = controller.advance(job.job_id, JobState.AUTHORIZED, side_effect_state="AUTHORIZED", expected_revision=0)
with pytest.raises(JobError, match="REVISION_CAS_CONFLICT"):
controller.advance(job.job_id, JobState.LEASED, side_effect_state="LEASED", expected_revision=0)
completed = controller.advance(job.job_id, JobState.COMPLETED, side_effect_state="REMOTE_VERIFIED", expected_revision=authorized.revision, receipt={"remote_verified": True})
assert completed.http_status == 200
assert controller.submit(req).http_status == 200
with JobStore(temp)._connect() as connection:
assert connection.execute("SELECT COUNT(*) FROM receipts").fetchone()[0] == 1
assert connection.execute("SELECT COUNT(*) FROM state_transitions").fetchone()[0] == 3
def test_job_lease_mutation_is_transactional_and_revision_bound():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
req = {"campaign_id": "campaign", "repository": "aios/aios", "operation": "commit-file", "branch": request().target_branch, "expected_branch_oid": CAMPAIGN_HEAD, "payload_digest": "b" * 64}
job = controller.submit(req)
leased = controller.lease_job(job.job_id, lease_id="lease-1", lineage_anchor_oid=LINEAGE_ANCHOR, current_head_oid=CAMPAIGN_HEAD, generation=1, lease_expires_at="2026-08-08T00:00:00Z", expected_revision=0)
assert leased.state == JobState.LEASED
controller.update_lease("lease-1", expected_revision=0, current_head_oid="c" * 40, generation=1)
with pytest.raises(JobError, match="LEASE_REVISION_CAS_CONFLICT"):
controller.update_lease("lease-1", expected_revision=0, current_head_oid="d" * 40, generation=1)
def test_reconcile_failure_is_durable_and_not_directly_advanced():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
req = {"campaign_id": "campaign", "repository": "aios/aios", "operation": "commit-file", "branch": request().target_branch, "expected_branch_oid": CAMPAIGN_HEAD, "payload_digest": "b" * 64}
job = controller.submit(req)
controller.advance(job.job_id, JobState.RECOVERABLE_FAILURE, side_effect_state="UNKNOWN")
with pytest.raises(JobError, match="RECONCILIATION_REQUIRED"):
controller.reconcile(job.job_id, lambda _row: (_ for _ in ()).throw(RuntimeError("probe failed")))
assert JobStore(temp).read(job.job_id)["state"] == JobState.RECONCILIATION_REQUIRED.value
def test_worker_gate_closed_and_safe_dry_execution():
assert runtime_policy({"AIOS_GITOPS_WRITES_ENABLED": "false", "AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED": "false"})["durable_job_execution"] is False
def test_runtime_planes_do_not_conflate_readiness():
status = RuntimeStatus({"model_api": True}, {"job_controller": True, "gitea_machine_credential": False}, {"artifact_store": True})
assert status.release_control_plane_ready is False
assert status.as_dict()["WRITE_PLANE"]["gitea_machine_credential"] is False
def test_sentinel_is_workspace_independent_and_fail_closed():
inputs = SentinelInputs(tempfile.gettempdir() + "/missing-authority.git", "gitea", "https://gitea/api", "refs/heads/campaign", "a" * 40)
result = evaluate(inputs, network_reader=lambda *_: "a" * 40, gitea_reader=lambda *_: "a" * 40)
assert result.equal is False and result.error == "AUTHORITY_REF_UNAVAILABLE"
def test_api_file_method_has_no_inline_git_mutation_calls():
source = Path(__file__).parents[1] / "active-runtime" / "app" / "aios_chatgpt_actions_api.py"
if not source.exists():
pytest.skip("candidate source is remote-only in this local test runner")
tree = ast.parse(source.read_text(encoding="utf-8"))
method = next(node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef) and node.name == "file")
calls = {node.func.id for node in ast.walk(method) if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)}
assert not calls.intersection({"checkout", "write_file", "commit", "sh"})
+166
View File
@@ -0,0 +1,166 @@
from __future__ import annotations
import json
from pathlib import Path
import tempfile
from unittest.mock import patch
import pytest
from aios_existing_branch_publisher import ExistingBranchPublicationState, ExistingBranchPublisher
from gitea_tag_publication import GiteaConfigurationError, GiteaTagClient, InvalidReleaseVersion, PublicationRequest, TagPublisher, validate_version
from signer_contract_compatibility import run_compatible_signer
class Authority:
def __init__(self, oid: str):
self.oid = oid
def read_ref(self, ref: str) -> str | None:
return self.oid if ref.startswith("refs/heads/") else None
class Remote:
def __init__(self, oid: str | None = None, api_oid: str | None = None, visible: bool = True):
self.oid = oid
self.api_oid = api_oid
self.visible = visible
self.pushes: list[tuple[str, str | None, str]] = []
def repository_identity(self) -> str:
return "aios/aios"
def read_git_ref(self, branch: str) -> str | None:
return self.oid
def read_api_branch(self, branch: str) -> str | None:
return self.api_oid
def commit_visible(self, oid: str) -> bool:
return self.visible
def push_exact(self, branch: str, expected: str | None, new: str) -> None:
assert expected is None
self.pushes.append((branch, expected, new))
self.oid = new
self.api_oid = new
self.visible = True
def request(oid: str, key: str = "gate-closure-test-0001") -> dict[str, str]:
return {
"repository": "aios/aios",
"branch": "agent/gate-closure-test",
"expected_branch_oid": oid,
"owner_approval_reference": "owner-authorization:test",
"idempotency_key": key,
}
def test_existing_branch_publisher_creates_exact_missing_remote_and_replays_idempotently(tmp_path: Path):
oid = "a" * 40
remote = Remote()
publisher = ExistingBranchPublisher(Authority(oid), remote, tmp_path / "state")
first = publisher.publish(request(oid))
assert first["publication_state"] == ExistingBranchPublicationState.COMPLETED.value
assert remote.pushes == [("agent/gate-closure-test", None, oid)]
second = publisher.publish(request(oid))
assert second["publication_state"] == ExistingBranchPublicationState.COMPLETED.value
assert second["idempotent"] is True
def test_existing_branch_publisher_conflict_is_fail_closed(tmp_path: Path):
oid = "a" * 40
remote = Remote(oid="b" * 40, api_oid="b" * 40)
result = ExistingBranchPublisher(Authority(oid), remote, tmp_path / "state").publish(request(oid, "gate-closure-test-0002"))
assert result["publication_state"] == ExistingBranchPublicationState.MANUAL_OWNER_REVIEW_REQUIRED.value
assert remote.pushes == []
def test_existing_branch_publisher_rejects_main_and_wrong_authority_oid(tmp_path: Path):
oid = "a" * 40
with pytest.raises(Exception, match="BRANCH_NAMESPACE_REJECTED"):
ExistingBranchPublisher(Authority(oid), Remote(), tmp_path / "state").publish({**request(oid), "branch": "main", "idempotency_key": "gate-closure-test-0003"})
with pytest.raises(Exception, match="AUTHORITY_BRANCH_OID_MISMATCH"):
ExistingBranchPublisher(Authority("b" * 40), Remote(), tmp_path / "state").publish(request(oid, "gate-closure-test-0004"))
def test_signer_broker_path_has_no_direct_root_signer_call():
class FakeBroker:
def self_test(self):
return None
def sign(self, payload_kind, payload):
return b"signature", "test-broker-key"
request_doc = {
"schema": "aios.task-scoped-writer-grant-request.v1",
"task_id": "gate-closure-signer-unit",
"repository": "aios/aios",
"base": {"ref": "refs/heads/main", "oid": "a" * 40},
"target": {"branch": "agent/gate-closure-signer-unit"},
"owner_authorization": {"identity": "aiman", "reference": "owner-authorization:test"},
"authorization": {
"production_change_authorized": False,
"tag_change_authorized": False,
"baseline_change_authorized": False,
"main_write_authorized": False,
},
"allowed_operations": ["create_worktree"],
"denied_operations": ["direct_main_push", "force_push"],
"one_time_use": True,
"not_before": "2026-08-10T00:00:00Z",
"expires_at": "2026-08-10T00:00:00Z",
"nonce": "unit-nonce",
}
with tempfile.TemporaryDirectory() as td:
root = Path(td)
grant = root / "grant"
grant.mkdir()
(grant / "grant-request.json").write_text(json.dumps(request_doc), encoding="utf-8")
(grant / "approval-plan.json").write_text(json.dumps({"approval_plan_id": "sha256:" + "a" * 64}), encoding="utf-8")
with patch("signer_contract_compatibility._supports_deployed_signer", return_value=True):
result = run_compatible_signer(["unused-signer-command"], grant, root / "result.json", broker=FakeBroker())
assert result["signing_key_identity"] == "test-broker-key"
def test_gitea_configuration_has_explicit_error_codes():
with pytest.raises(GiteaConfigurationError, match="GITEA_API_URL_MISSING"):
GiteaTagClient.from_authoritative_environment({})
with pytest.raises(GiteaConfigurationError, match="GITEA_REPOSITORY_MISSING"):
GiteaTagClient.from_authoritative_environment({"AIOS_GITOPS_GITEA_API_URL": "https://gitea.invalid/api/v1"})
with pytest.raises(GiteaConfigurationError, match="GITEA_TOKEN_REFERENCE_MISSING"):
GiteaTagClient.from_authoritative_environment({"AIOS_GITOPS_GITEA_API_URL": "https://gitea.invalid/api/v1", "AIOS_GITOPS_GITEA_REPOSITORY": "aios/aios"})
assert validate_version("V2R2C1") == "V2R2C1"
with pytest.raises(InvalidReleaseVersion, match="invalid release version"):
validate_version("not-a-release")
def test_release_consistency_reports_tag_target_mismatch(tmp_path: Path):
target = "a" * 40
evidence = "b" * 40
tag_oid = "c" * 40
mismatched_target = "d" * 40
request = PublicationRequest("V2R2C1", target, evidence, "approval-plan:test")
(tmp_path / "baseline").mkdir()
(tmp_path / "versions").mkdir()
common = {
"version": request.version,
"target_oid": request.target_oid,
"evidence_closure_oid": request.evidence_closure_oid,
"approval_plan_id": request.approval_plan_id,
"tag_oid": tag_oid,
}
(tmp_path / "baseline/current.json").write_text(
json.dumps({**common, "current_baseline": request.version}), encoding="utf-8"
)
(tmp_path / "versions/V2R2C1.json").write_text(
json.dumps({**common, "registered": True, "enabled": True}), encoding="utf-8"
)
verified = {
**common,
"remote_tag_oid": tag_oid,
"remote_peeled_target_oid": mismatched_target,
"gitea_tag_oid": tag_oid,
"gitea_peeled_target_oid": target,
"gitea_listing_visible": True,
"repository_identity": "aios/aios",
}
result = TagPublisher(tmp_path, object(), object(), "gitea").get_release_consistency(
request, verified=verified
)
assert result["status"] == "FAIL"
assert result["error"] == "TAG_TARGET_MISMATCH"
+3 -3
View File
@@ -250,7 +250,7 @@ class GovernedLocalWriterTests(unittest.TestCase):
}
self.assertEqual(service.process_request(secret_request, peer)["status"], "error")
def test_one_shot_consumption(self) -> None:
def test_idempotent_replay_returns_receipt(self) -> None:
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
auth = self._load_auth(self._make_auth_payload(), directory)
@@ -283,8 +283,8 @@ class GovernedLocalWriterTests(unittest.TestCase):
self.assertEqual(service.process_request(create_request, peer)["status"], "ok")
self.assertEqual(service.process_request(append_request, peer)["status"], "ok")
self.assertEqual(service.process_request(append_request, peer)["status"], "error")
self.assertEqual(len(sink.records), 3)
self.assertEqual(service.process_request(append_request, peer)["status"], "receipt")
self.assertEqual(len(sink.records), 2)
self.assertEqual(len(handler.create_calls), 1)
self.assertEqual(len(handler.append_calls), 1)
+120
View File
@@ -0,0 +1,120 @@
from __future__ import annotations
import io
import json
import shutil
import subprocess
import tarfile
from pathlib import Path
import sys
import pytest
APP = Path(__file__).resolve().parents[1] / "active-runtime" / "app"
sys.path.insert(0, str(APP))
import governed_production_release as release # noqa: E402
from version_registry import build_source_registry_entry, source_registry_json # noqa: E402
def _git(work: Path, *args: str) -> str:
return subprocess.run(
["git", "-C", str(work), *args],
capture_output=True,
text=True,
check=True,
).stdout.strip()
def _commit(work: Path, message: str) -> str:
subprocess.run(["git", "-C", str(work), "add", "-A"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", message], check=True)
return _git(work, "rev-parse", "HEAD")
def test_artifact_uses_pinned_source_registry_commit_when_main_advances(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
if shutil.which("zstd") is None:
pytest.skip("zstd is required for governed artifact integration")
work = tmp_path / "work"
bare = tmp_path / "repo.git"
subprocess.run(["git", "init", "-q", "-b", "main", str(work)], check=True)
_git(work, "config", "user.name", "test")
_git(work, "config", "user.email", "test@example.invalid")
(work / "active-runtime/app").mkdir(parents=True)
(work / "active-runtime/app/deployment_state.py").write_text("state = 'candidate'\n")
(work / "active-runtime/app/v2r1c6_privacy_tools.py").write_text("def main(): return 0\n")
target_oid = _commit(work, "release target")
subprocess.run(
["git", "-C", str(work), "tag", "-a", "V2R2C1", target_oid, "-m", "release"],
check=True,
)
tag_oid = _git(work, "rev-parse", "refs/tags/V2R2C1")
(work / "evidence.txt").write_text("evidence\n")
evidence_oid = _commit(work, "evidence closure")
record = build_source_registry_entry(
version="V2R2C1",
tag_oid=tag_oid,
target_oid=target_oid,
peeled_target_oid=target_oid,
evidence_closure_oid=evidence_oid,
approval_plan_id="sha256:" + "4" * 64,
)
source_path = work / "versions/V2R2C1.json"
source_path.parent.mkdir(parents=True)
source_path.write_bytes(source_registry_json(record))
main_oid = _commit(work, "source registry convergence")
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
subprocess.run(["git", "-C", str(work), "remote", "add", "authority", str(bare)], check=True)
monkeypatch.setenv("AIOS_TEST_MODE", "1")
monkeypatch.setenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", str(bare))
monkeypatch.setattr(release, "TAG_OID", tag_oid)
monkeypatch.setattr(release, "TARGET_OID", target_oid)
monkeypatch.setattr(release, "EVIDENCE_CLOSURE_OID", evidence_oid)
monkeypatch.setattr(release, "APPROVAL_PLAN_ID", record["approval_plan_id"])
binding = release.ReleaseBinding(
tag_oid=tag_oid,
target_oid=target_oid,
evidence_closure_oid=evidence_oid,
approval_plan_id=record["approval_plan_id"],
)
original_payload = release._payload_file
advanced = False
def advance_after_first_payload(git_dir: Path, oid: str, path: str) -> bytes:
nonlocal advanced
result = original_payload(git_dir, oid, path)
if not advanced and path == "active-runtime/app/deployment_state.py":
updated = dict(record)
updated["review_marker"] = "main-advanced-after-validation"
source_path.write_bytes(source_registry_json(updated))
new_main = _commit(work, "advance main after source validation")
subprocess.run(["git", "-C", str(work), "push", "-q", "authority", "main"], check=True)
assert new_main != main_oid
advanced = True
return result
monkeypatch.setattr(release, "_payload_file", advance_after_first_payload)
receipt = release.build_production_artifact(bare, binding, tmp_path / "artifacts")
assert advanced is True
assert receipt["release_target_oid"] == target_oid
assert receipt["source_registry_main_oid"] == main_oid
assert receipt["source_registry_path"] == "versions/V2R2C1.json"
expected_bytes = subprocess.run(
["git", f"--git-dir={bare}", "show", f"{main_oid}:versions/V2R2C1.json"],
capture_output=True,
check=True,
).stdout
assert receipt["source_registry_sha256"] == __import__("hashlib").sha256(expected_bytes).hexdigest()
compressed = Path(receipt["artifact"]).read_bytes()
raw_tar = subprocess.run(["zstd", "-dc", "-"], input=compressed, capture_output=True, check=True).stdout
with tarfile.open(fileobj=io.BytesIO(raw_tar), mode="r:") as archive:
packaged = archive.extractfile("payload/versions/V2R2C1.json").read()
metadata = json.loads(archive.extractfile("payload/BUILD-METADATA.json").read())
assert packaged == expected_bytes
assert metadata["source_registry_main_oid"] == main_oid
assert metadata["source_registry_sha256"] == receipt["source_registry_sha256"]
+164
View File
@@ -0,0 +1,164 @@
from __future__ import annotations
import tempfile
from pathlib import Path
import pytest
import aios_gitops_job_executor as executor_module
from aios_authority_git_executor import AuthorityGitError, AuthorityGitExecutor
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
from aios_gitops_writer_worker import handle_request
OLD = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
NEW = "3fbbc18fb2131bbbd9e511004a59ee95cf3e86b7"
class FakeAuthority:
def __init__(self, descendant=True):
self.head = OLD
self.descendant = descendant
self.cas_calls = []
def read_ref(self, ref):
return self.head
def ancestry_check(self, older, newer):
return self.descendant and older == OLD and newer == NEW
def advance_task_branch_CAS(self, branch, expected, new):
if self.head != expected:
raise AuthorityGitError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
self.cas_calls.append((branch, expected, new))
self.head = new
return new
class FakeBroker:
def __init__(self):
self.remote = None
self.push_calls = []
def verify_identity(self):
return {"login": "aios-autonomy-agent", "repository": "aios/aios"}
def readback(self):
return self.remote
def push_branch(self, new):
self.push_calls.append(new)
self.remote = new
return new
def create_pr(self, *args):
return {"number": 1}
def status(self, *args):
return {"status": "success"}
def merge(self, *args):
return {"merged": True}
def _request(**extra):
value = {
"campaign_id": executor_module.CAMPAIGN_ID,
"repository": executor_module.REPOSITORY,
"operation": "ADVANCE_EXISTING_CAMPAIGN_BRANCH",
"branch": executor_module.BRANCH,
"expected_branch_oid": OLD,
"payload_digest": "0" * 64,
"task_id": executor_module.TASK_ID,
"new_branch_oid": NEW,
"authorization_recovery_digest": executor_module.AUTHORIZATION_DIGEST,
}
value.update(extra)
return value
def _executor(temp, authority=None, broker=None):
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request())
executor = executor_module.DurableJobExecutor(controller, authority or FakeAuthority(), broker or FakeBroker(), Path(temp) / "recovery.json")
return controller, job, executor
def _recovery():
return {
"digest": executor_module.AUTHORIZATION_DIGEST,
"campaign_id": executor_module.CAMPAIGN_ID,
"task_id": executor_module.TASK_ID,
"repository": executor_module.REPOSITORY,
"branch": executor_module.BRANCH,
"expected_branch_oid": OLD,
}
def test_advance_existing_branch_success_and_duplicate_execute(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
controller, job, executor = _executor(temp)
first = executor.execute(job.job_id)
second = executor.execute(job.job_id)
assert first["state"] == JobState.COMPLETED.value
assert second["state"] == JobState.COMPLETED.value
assert executor.authority.cas_calls == [(f"refs/heads/{executor_module.BRANCH}", OLD, NEW)]
assert executor.broker.push_calls == [NEW]
def test_authorization_digest_required(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request(authorization_recovery_digest="bad"))
executor = executor_module.DurableJobExecutor(controller, FakeAuthority(), FakeBroker(), Path(temp) / "recovery.json")
with pytest.raises(executor_module.JobExecutionError, match="AUTHORIZATION_RECOVERY_DIGEST_MISMATCH"):
executor.dry_run(job.job_id)
def test_wrong_branch_rejected(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request(branch="agent/not-the-campaign"))
executor = executor_module.DurableJobExecutor(controller, FakeAuthority(), FakeBroker(), Path(temp) / "recovery.json")
with pytest.raises(executor_module.JobExecutionError, match="BRANCH_MISMATCH"):
executor.dry_run(job.job_id)
def test_non_descendant_new_oid_rejected(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
authority = FakeAuthority(descendant=False)
controller, job, executor = _executor(temp, authority=authority)
with pytest.raises(executor_module.JobExecutionError, match="NEW_OID_NOT_DESCENDANT"):
executor.dry_run(job.job_id)
def test_worker_execute_action_consumes_job():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request())
class Stub:
def execute(self, job_id, *, dry_run=False):
assert job_id == job.job_id
assert dry_run is True
return {"decision": "SAFE_TO_ADVANCE"}
result = handle_request(controller, {"action": "execute", "job_id": job.job_id, "dry_run": True}, Stub())
assert result["job"]["decision"] == "SAFE_TO_ADVANCE"
def test_machine_transport_branch_cas_is_not_force_push(monkeypatch):
transport = GiteaMachineTransport("http://127.0.0.1:3000/api/v1", "aios/aios", "https://g.cnnetmkt.top/git/aios/aios.git", Path("/tmp/repo.git"))
calls = []
def read_branch(_self, _branch):
return NEW if calls else OLD
monkeypatch.setattr(GiteaMachineTransport, "read_branch", read_branch)
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)]
+47
View File
@@ -0,0 +1,47 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_model_api_source_is_immutable_and_authority_bound():
source = (ROOT / 'active-runtime' / 'app' / 'aios_model_api_staging.py').read_text(encoding='utf-8')
assert 'BaselineAuthority' in source
assert 'BASELINE_AUTHORITY = BaselineAuthority()' in source
assert 'LEGACY_GIT_WRITE_COMMANDS' in source
assert 'BaselineAuthorityError' in source
assert 'IMMUTABLE_STAGING' in source
assert 'LEGACY_GLOBAL_WRITES_DISABLED' in source
assert "'production_deployed': False" in source
assert "'governed_release_routing': 'bridge-v2'" in source
def test_model_api_staging_unit_is_non_production_and_read_only():
unit = (ROOT / 'deploy' / 'systemd' / 'aios-gitops-model-api-v2r2c1-staging.service').read_text(encoding='utf-8')
assert 'aios-gitops-model-api-v2r2c1-staging.service' in unit
assert 'AIOS_GITOPS_WRITES_ENABLED=false' in unit
assert 'AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=false' in unit
assert 'AIOS_GITOPS_PORT=18893' in unit
assert 'aios_model_api_staging.py' in unit
assert 'production' not in unit.lower().replace('model-api-v2r2c1-staging', '')
def test_model_api_source_convergence_never_falls_back_to_generic_git_dir():
source = (ROOT / 'active-runtime' / 'app' / 'aios_model_api_staging.py').read_text(encoding='utf-8')
assert "repo_git_dir=AUTHORITATIVE_GIT_DIR or GIT_DIR" not in source
assert "repo_git_dir=AUTHORITATIVE_GIT_DIR" in source
def test_production_adapter_never_claims_source_git_convergence():
source = (ROOT / 'active-runtime' / 'app' / 'v2r2c1_production_adapter.py').read_text(encoding='utf-8')
assert '"source_registry_converged": True' not in source
assert '"source_registry_converged": "not_checked"' in source
def test_model_api_builder_manifest_contract():
builder = (ROOT / 'scripts' / 'build_v2r2c1_model_api_staging.py').read_text(encoding='utf-8')
assert 'aios.v2r2c1.model-api-staging-manifest.v1' in builder
assert "'target_version': 'V2R2C1'" in builder
assert "'repository_identity': 'aios/aios'" in builder
assert "'deployment_state': 'STAGING'" in builder
assert "'legacy_global_writes_enabled': False" in builder
assert 'private marker rejected' in builder
+2
View File
@@ -47,6 +47,8 @@ def old_baseline(path: Path) -> str:
def test_baseline_record_uses_real_previous_production_baseline(tmp_path, monkeypatch):
# This test isolates previous-baseline semantics; convergence is tested separately.
monkeypatch.setattr(adapter, "assert_source_registry_convergence", lambda *a, **kw: None)
baseline = tmp_path / "baseline.json"
previous_sha = old_baseline(baseline)
registry_root = tmp_path / "versions"
+120
View File
@@ -0,0 +1,120 @@
from __future__ import annotations
import base64
from concurrent.futures import ThreadPoolExecutor
import hashlib
import json
from pathlib import Path
import threading
from aios_signer_broker_service import (
REQUEST_SCHEMA,
RESPONSE_SCHEMA,
SignerBrokerPolicy,
SignerBrokerService,
SignerBrokerServiceError,
)
class FakeRunner:
def __init__(self, failure: str | None = None):
self.failure = failure
self.calls: list[tuple[str, str]] = []
self.lock = threading.Lock()
def self_test(self) -> None:
with self.lock:
self.calls.append(("self_test", ""))
if self.failure:
raise SignerBrokerServiceError(self.failure)
def sign(self, payload_kind: str, payload: bytes) -> tuple[bytes, str]:
with self.lock:
self.calls.append(("sign", payload_kind))
if self.failure:
raise SignerBrokerServiceError(self.failure)
return b"signature:" + payload, "test-broker-key"
def make_policy(tmp_path: Path, *, allowed_uids: frozenset[int] = frozenset({1000})) -> SignerBrokerPolicy:
return SignerBrokerPolicy(
socket_path=tmp_path / "signer.sock",
signer_path=Path("/usr/local/libexec/aios-grant-signer-adapter"),
audit_path=tmp_path / "audit.jsonl",
state_root=tmp_path / "state",
allowed_uids=allowed_uids,
)
def sign_request(payload: bytes = b"canonical payload") -> dict[str, str]:
return {
"schema": REQUEST_SCHEMA,
"operation": "sign",
"payload_kind": "approval_plan",
"payload_sha256": hashlib.sha256(payload).hexdigest(),
"payload_base64": base64.b64encode(payload).decode("ascii"),
}
def read_audit(path: Path) -> list[dict[str, object]]:
return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines()]
def test_authorized_request_and_self_test_emit_metadata_only_audit(tmp_path: Path):
service = SignerBrokerService(make_policy(tmp_path), FakeRunner())
self_test = service.handle_request({"schema": REQUEST_SCHEMA, "operation": "self_test"}, 1000)
signed = service.handle_request(sign_request(b"secret-shaped canonical input"), 1000)
assert self_test == {"schema": RESPONSE_SCHEMA, "status": "PASS", "operation": "self_test"}
assert signed["status"] == "PASS"
assert base64.b64decode(signed["signature_base64"]) == b"signature:secret-shaped canonical input"
assert signed["key_id"] == "test-broker-key"
audit_text = (tmp_path / "audit.jsonl").read_text(encoding="utf-8")
assert "secret-shaped canonical input" not in audit_text
assert "payload_base64" not in audit_text
allowed = {"schema", "timestamp", "peer_uid", "operation", "status", "error_code", "payload_sha256"}
assert all(set(event) <= allowed for event in read_audit(tmp_path / "audit.jsonl"))
def test_unauthorized_caller_is_rejected_before_signer_use(tmp_path: Path):
runner = FakeRunner()
service = SignerBrokerService(make_policy(tmp_path), runner)
result = service.handle_request(sign_request(), 1001)
assert result["status"] == "FAIL"
assert result["error_code"] == "CALLER_UNAUTHORIZED"
assert runner.calls == []
def test_arbitrary_command_and_invalid_operation_are_rejected(tmp_path: Path):
runner = FakeRunner()
service = SignerBrokerService(make_policy(tmp_path), runner)
injected = {"schema": REQUEST_SCHEMA, "operation": "sign", "command": ["/bin/sh", "-c", "id"]}
invalid = {"schema": REQUEST_SCHEMA, "operation": "run", "command": ["id"]}
assert service.handle_request(injected, 1000)["error_code"] == "REQUEST_FIELDS_REJECTED"
assert service.handle_request(invalid, 1000)["error_code"] == "SIGNER_OPERATION_REJECTED"
assert runner.calls == []
def test_invalid_payload_and_hash_fail_closed(tmp_path: Path):
service = SignerBrokerService(make_policy(tmp_path), FakeRunner())
invalid = sign_request()
invalid["payload_base64"] = "not-base64"
assert service.handle_request(invalid, 1000)["error_code"] == "SIGNER_PAYLOAD_INVALID"
mismatch = sign_request()
mismatch["payload_sha256"] = "0" * 64
assert service.handle_request(mismatch, 1000)["error_code"] == "SIGNER_PAYLOAD_HASH_MISMATCH"
def test_signer_unavailable_and_timeout_are_stable_failures(tmp_path: Path):
for code in ("SIGNER_UNAVAILABLE", "SIGNER_TIMEOUT"):
result = SignerBrokerService(make_policy(tmp_path / code), FakeRunner(code)).handle_request(
{"schema": REQUEST_SCHEMA, "operation": "self_test"}, 1000
)
assert result == {"schema": RESPONSE_SCHEMA, "status": "FAIL", "error_code": code}
def test_concurrent_calls_are_safe_and_audited(tmp_path: Path):
service = SignerBrokerService(make_policy(tmp_path), FakeRunner())
with ThreadPoolExecutor(max_workers=8) as pool:
results = list(pool.map(lambda index: service.handle_request(sign_request(f"payload-{index}".encode()), 1000), range(24)))
assert all(result["status"] == "PASS" for result in results)
assert len(read_audit(tmp_path / "audit.jsonl")) == 24
+8 -3
View File
@@ -20,6 +20,7 @@ from signer_contract_compatibility import ( # noqa: E402
canonical_json_bytes,
deployed_signer_help,
run_compatible_signer,
run_signer_self_test,
resolve_ref_oid,
run_compatible_validator,
sha256_json,
@@ -113,9 +114,13 @@ class SignerContractCompatibilityTests(unittest.TestCase):
help_result = subprocess.run([signer, "sign", "--help"], capture_output=True, text=True, check=False)
self.assertEqual(help_result.returncode, 0)
self.assertTrue(deployed_signer_help(help_result.stdout, help_result.stderr))
self_test = subprocess.run([signer, "self-test"], capture_output=True, text=True, check=False)
self.assertEqual(self_test.returncode, 0)
self.assertIn("SIGNER_SELF_TEST=PASS", self_test.stdout)
try:
broker_result = run_signer_self_test()
except SignerContractError as exc:
if str(exc) == "SIGNER_BROKER_UNAVAILABLE":
self.skipTest("signer broker is not deployed in this engineering phase")
raise
self.assertEqual(broker_result["status"], "PASS")
def test_resolve_ref_oid_uses_full_synthetic_commit_and_fails_closed(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
+392
View File
@@ -0,0 +1,392 @@
from __future__ import annotations
import hashlib
import io
import json
from pathlib import Path
import shutil
import subprocess
import tarfile
import pytest
from approved_release_executor import (
Binding,
ManualOwnerReviewRequired,
ReleaseBlocked,
ReconciliationRequired,
deploy_step,
execute,
tag_step,
verify_artifact,
verify_authorization,
)
def _binding(tmp_path: Path, **overrides) -> Binding:
artifact = tmp_path / "artifact.tar.zst"
artifact.write_bytes(b"x")
values = {
"version": "VTEST",
"target_oid": "a" * 40,
"evidence_closure_oid": "b" * 40,
"artifact_sha256": "c" * 64,
"approval_plan_id": "sha256:" + "d" * 64,
"artifact_path": str(artifact),
"artifact_size": 1,
"production_service": "test.service",
"production_port": 18927,
}
values.update(overrides)
return Binding(**values)
def _authorization(binding: Binding) -> dict[str, object]:
result: dict[str, object] = {
"accepted": True,
"binding_replaced": False,
"version": binding.version,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
}
if binding.tag_oid is not None:
result.update(
{
"binding_schema": binding.binding_schema or "aios.release-binding.v2",
"tag_oid": binding.tag_oid,
}
)
if binding.source_registry_main_oid is not None:
result.update(
{
"source_registry_main_oid": binding.source_registry_main_oid,
"source_registry_path": binding.source_registry_path
or f"versions/{binding.version}.json",
"source_registry_sha256": binding.source_registry_sha256,
}
)
return result
def test_legacy_v2r2c1_style_authorization_remains_compatible(tmp_path: Path) -> None:
binding = _binding(tmp_path)
assert verify_authorization(_authorization(binding), binding)["accepted"] is True
def test_future_binding_requires_explicit_tag_oid(tmp_path: Path) -> None:
binding = _binding(
tmp_path,
version="V2R2C2",
tag_oid="1" * 40,
binding_schema="aios.release-binding.v2",
)
assert verify_authorization(_authorization(binding), binding)["tag_oid"] == "1" * 40
wrong = _authorization(binding)
wrong["tag_oid"] = "2" * 40
with pytest.raises(ManualOwnerReviewRequired, match="tag_oid"):
verify_authorization(wrong, binding)
def test_future_same_name_different_tag_object_requires_manual_review(monkeypatch, tmp_path: Path) -> None:
binding = _binding(
tmp_path,
version="V2R2C2",
tag_oid="1" * 40,
binding_schema="aios.release-binding.v2",
)
def fake(_command, _request):
return {
"status": "APPLIED_VERIFIED",
"version": binding.version,
"target_oid": binding.target_oid,
"artifact_sha256": binding.artifact_sha256,
"tag_oid": "2" * 40,
"peeled_target_oid": binding.target_oid,
}
monkeypatch.setattr("approved_release_executor.run_adapter", fake)
with pytest.raises(ManualOwnerReviewRequired, match="same-name tag object"):
tag_step(["/tag"], binding)
def test_executor_rechecks_source_registry_digest_and_build_provenance(
monkeypatch, tmp_path: Path
) -> None:
if shutil.which("zstd") is None:
pytest.skip("zstd is required for archive verification")
source = {
"schema": "aios.source-registry.v1",
"version": "V2R2C2",
"repository": "aios/aios",
"tag": "V2R2C2",
"tag_oid": "1" * 40,
"tag_object": "1" * 40,
"target_oid": "2" * 40,
"peeled_target_oid": "2" * 40,
"evidence_closure_oid": "3" * 40,
"approval_plan_id": "sha256:" + "4" * 64,
}
source_bytes = (json.dumps(source, ensure_ascii=False, sort_keys=True, indent=2) + "\n").encode()
source_digest = hashlib.sha256(source_bytes).hexdigest()
repo_work = tmp_path / "authority-work"
repo_bare = tmp_path / "authority.git"
(repo_work / "versions").mkdir(parents=True)
(repo_work / "versions" / "V2R2C2.json").write_bytes(source_bytes)
subprocess.run(["git", "init", "-q", str(repo_work)], check=True)
subprocess.run(["git", "-C", str(repo_work), "branch", "-M", "main"], check=True)
subprocess.run(["git", "-C", str(repo_work), "config", "user.name", "test"], check=True)
subprocess.run(
["git", "-C", str(repo_work), "config", "user.email", "test@example.invalid"],
check=True,
)
subprocess.run(["git", "-C", str(repo_work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(repo_work), "commit", "-qm", "source"], check=True)
source_main_oid = subprocess.run(
["git", "-C", str(repo_work), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(repo_work), str(repo_bare)], check=True)
subprocess.run(
["git", f"--git-dir={repo_bare}", "config", "remote.gitea.url", "https://g.cnnetmkt.top/aios/aios.git"],
check=True,
)
monkeypatch.setenv("AIOS_TEST_MODE", "1")
monkeypatch.setenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", str(repo_bare))
metadata = {
"release_target_oid": "2" * 40,
"source_registry_main_oid": source_main_oid,
"source_registry_path": "versions/V2R2C2.json",
"source_registry_sha256": source_digest,
}
tar_path = tmp_path / "release.tar"
with tarfile.open(tar_path, "w") as archive:
for name, content in (
("payload/versions/V2R2C2.json", source_bytes),
("payload/BUILD-METADATA.json", (json.dumps(metadata, sort_keys=True) + "\n").encode()),
):
info = tarfile.TarInfo(name)
info.size = len(content)
archive.addfile(info, io.BytesIO(content))
artifact = tmp_path / "release.tar.zst"
subprocess.run(["zstd", "-q", "-19", "-f", str(tar_path), "-o", str(artifact)], check=True)
binding = _binding(
tmp_path,
version="V2R2C2",
tag_oid="1" * 40,
binding_schema="aios.release-binding.v2",
target_oid="2" * 40,
evidence_closure_oid="3" * 40,
approval_plan_id="sha256:" + "4" * 64,
source_registry_main_oid=source_main_oid,
source_registry_path="versions/V2R2C2.json",
source_registry_sha256=source_digest,
artifact_path=str(artifact),
artifact_size=artifact.stat().st_size,
artifact_sha256=hashlib.sha256(artifact.read_bytes()).hexdigest(),
)
result = verify_artifact(binding)
assert result["source_registry_sha256"] == source_digest
assert result["build_metadata_provenance_verified"] is True
bad_metadata = dict(metadata)
bad_metadata["source_registry_sha256"] = "0" * 64
bad_tar = tmp_path / "bad-metadata.tar"
with tarfile.open(bad_tar, "w") as archive:
for name, content in (
("payload/versions/V2R2C2.json", source_bytes),
("payload/BUILD-METADATA.json", (json.dumps(bad_metadata, sort_keys=True) + "\n").encode()),
):
info = tarfile.TarInfo(name)
info.size = len(content)
archive.addfile(info, io.BytesIO(content))
bad_artifact = tmp_path / "bad-metadata.tar.zst"
subprocess.run(["zstd", "-q", "-19", "-f", str(bad_tar), "-o", str(bad_artifact)], check=True)
bad_binding = _binding(
tmp_path,
version="V2R2C2",
tag_oid="1" * 40,
binding_schema="aios.release-binding.v2",
target_oid="2" * 40,
evidence_closure_oid="3" * 40,
approval_plan_id="sha256:" + "4" * 64,
source_registry_main_oid=source_main_oid,
source_registry_path="versions/V2R2C2.json",
source_registry_sha256=source_digest,
artifact_path=str(bad_artifact),
artifact_size=bad_artifact.stat().st_size,
artifact_sha256=hashlib.sha256(bad_artifact.read_bytes()).hexdigest(),
)
with pytest.raises(ReleaseBlocked, match="BUILD-METADATA"):
verify_artifact(bad_binding)
subprocess.run(["git", "-C", str(repo_work), "remote", "add", "origin", str(repo_bare)], check=True)
changed = dict(source)
changed["review_marker"] = "changed-after-M"
changed_bytes = (json.dumps(changed, ensure_ascii=False, sort_keys=True, indent=2) + "\n").encode()
(repo_work / "versions" / "V2R2C2.json").write_bytes(changed_bytes)
subprocess.run(["git", "-C", str(repo_work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(repo_work), "commit", "-qm", "change-source"], check=True)
subprocess.run(["git", "-C", str(repo_work), "push", "-q", "origin", "HEAD:main"], check=True)
with pytest.raises(ManualOwnerReviewRequired, match="current Authority main source registry"):
verify_artifact(binding)
def test_executor_requires_runtime_bundle_convergence_for_provenance_binding(tmp_path: Path):
binding = _binding(
tmp_path,
version="V2R2C2",
tag_oid="1" * 40,
binding_schema="aios.release-binding.v2",
source_registry_main_oid="5" * 40,
source_registry_path="versions/V2R2C2.json",
source_registry_sha256="6" * 64,
)
def fake_missing(_command, _request):
return {
"status": "ACTIVE_VERIFIED",
"version": binding.version,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
"deployment_state": "ACTIVE",
"production_deployed": True,
}
monkeypatch = pytest.MonkeyPatch()
monkeypatch.setattr("approved_release_executor.run_adapter", fake_missing)
try:
with pytest.raises(ReconciliationRequired, match="runtime bundle/runtime registry"):
deploy_step(["/deploy"], binding)
finally:
monkeypatch.undo()
def fake_converged(_command, _request):
return {
"status": "ACTIVE_VERIFIED",
"version": binding.version,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
"deployment_state": "ACTIVE",
"production_deployed": True,
"runtime_bundle_contains_current_version": True,
"runtime_registry_converged": True,
}
monkeypatch.setattr("approved_release_executor.run_adapter", fake_converged)
try:
assert deploy_step(["/deploy"], binding)["runtime_registry_converged"] is True
finally:
monkeypatch.undo()
def test_execute_rechecks_authoritative_git_after_runtime_before_registry(
monkeypatch, tmp_path: Path
):
work = tmp_path / "authority-work"
bare = tmp_path / "authority.git"
(work / "versions").mkdir(parents=True)
subprocess.run(["git", "init", "-q", "-b", "main", str(work)], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.name", "test"], check=True)
subprocess.run(
["git", "-C", str(work), "config", "user.email", "test@example.invalid"],
check=True,
)
source = {
"schema": "aios.source-registry.v1",
"version": "VTEST",
"repository": "aios/aios",
"tag_oid": "1" * 40,
"tag_object": "1" * 40,
"target_oid": "a" * 40,
"peeled_target_oid": "a" * 40,
"evidence_closure_oid": "b" * 40,
"approval_plan_id": "sha256:" + "d" * 64,
}
source_path = work / "versions" / "VTEST.json"
source_path.write_bytes((json.dumps(source, sort_keys=True, indent=2) + "\n").encode())
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "source"], check=True)
pinned_main = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
source_sha = hashlib.sha256(source_path.read_bytes()).hexdigest()
binding = _binding(
tmp_path,
source_registry_main_oid=pinned_main,
source_registry_path="versions/VTEST.json",
source_registry_sha256=source_sha,
)
monkeypatch.setenv("AIOS_TEST_MODE", "1")
monkeypatch.setenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", str(bare))
monkeypatch.setattr("approved_release_executor.secure_json", lambda _path: _authorization(binding))
monkeypatch.setattr("approved_release_executor.verify_git_lineage", lambda *_args: {"verified": True})
monkeypatch.setattr("approved_release_executor.verify_artifact", lambda *_args, **_kwargs: {"verified": True})
deployed = False
calls = []
def fake_adapter(command, request):
nonlocal deployed
name = command[0]
operation = request["operation"]
calls.append((name, operation))
common = {
"version": binding.version,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
}
if name == "/tag":
return {
**common,
"status": "APPLIED_VERIFIED",
"tag_oid": "1" * 40,
"peeled_target_oid": binding.target_oid,
}
if name == "/deploy":
if operation == "probe" and not deployed:
changed = dict(source)
changed["review_marker"] = "changed-after-runtime"
source_path.write_bytes((json.dumps(changed, sort_keys=True, indent=2) + "\n").encode())
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "advance-main"], check=True)
subprocess.run(["git", "-C", str(work), "push", "-q", str(bare), "HEAD:main"], check=True)
deployed = True
return {
**common,
"status": "ACTIVE_VERIFIED",
"deployment_state": "ACTIVE",
"production_deployed": True,
"runtime_bundle_contains_current_version": True,
"runtime_registry_converged": True,
}
raise AssertionError(f"unexpected adapter call: {name}")
monkeypatch.setattr("approved_release_executor.run_adapter", fake_adapter)
with pytest.raises(ManualOwnerReviewRequired, match="current Authority main source registry"):
execute(
binding=binding,
authorization_path=tmp_path / "authorization.json",
repository=bare,
state_root=tmp_path / "state",
tag_command=["/tag"],
deploy_command=["/deploy"],
registry_command=["/registry"],
baseline_command=["/baseline"],
)
assert deployed is True
assert not any(name in {"/registry", "/baseline"} for name, _ in calls)
+850
View File
@@ -0,0 +1,850 @@
from __future__ import annotations
import hashlib
import inspect
import json
import os
import subprocess
import sys
from pathlib import Path
import pytest
APP = Path(__file__).resolve().parents[1] / "active-runtime" / "app"
sys.path.insert(0, str(APP))
import release_metadata_adapter as adapter # noqa: E402
import version_registry as registry_module # noqa: E402
from version_registry import ( # noqa: E402
RegistryConvergenceError,
assert_runtime_registry_convergence,
assert_source_registry_convergence,
build_source_registry_entry,
configured_approval_authority_root,
configured_authoritative_git_dir,
list_registry_versions,
list_registry_versions_report,
source_registry_snapshot,
verify_authoritative_repository,
verify_approval_authority,
verify_source_registry_main_convergence,
verify_source_registry_git_identity,
)
def _write_json(path: Path, obj: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(obj, sort_keys=True) + "\n", encoding="utf-8")
def _record(
*,
version: str = "V2R2C1",
tag_oid: str = "1" * 40,
target_oid: str = "2" * 40,
) -> dict:
return {
"schema": "aios.production-registry.v2",
"version": version,
"repository": "aios/aios",
"status": "PRODUCTION_DEPLOYED",
"production_deployed": True,
"tag_oid": tag_oid,
"target_oid": target_oid,
"peeled_target_oid": target_oid,
"evidence_closure_oid": "3" * 40,
"approval_plan_id": "sha256:" + "4" * 64,
}
def _canonical_approval(root: Path, record: dict) -> Path:
owner = {
"approved": True,
"status": "APPROVED",
"approved_by": "aiman",
"approval_source": "explicit_owner_approval_in_chat",
}
binding = {
key: record[key]
for key in (
"version",
"repository",
"tag_oid",
"target_oid",
"peeled_target_oid",
"evidence_closure_oid",
"approval_plan_id",
)
}
authority = {
"schema": "aios.v2r2c1.tag-approval-request.v1",
"state": "APPROVED",
"status": "APPROVED",
"execute": False,
"approval_plan_id": record["approval_plan_id"],
"binding": binding,
"owner_approval": owner,
"owner_approval_digest_sha256": hashlib.sha256(
(json.dumps(owner, sort_keys=True, separators=(",", ":")) + "\n").encode()
).hexdigest(),
}
path = root / f"{record['version']}.json"
_write_json(path, authority)
path.chmod(0o640)
return path
@pytest.fixture(autouse=True)
def _explicit_test_authority_mode(monkeypatch: pytest.MonkeyPatch):
"""Permit only these unit tests to inject temporary authority roots."""
monkeypatch.setenv("AIOS_TEST_MODE", "1")
def test_list_registry_versions_adds_only_baseline_attested_projection(tmp_path: Path):
ws = tmp_path / "ws"
(ws / "versions").mkdir(parents=True)
(ws / "versions" / "V2R1C7.json").write_text("{}\n")
projection_root = tmp_path / "projection"
projection = projection_root / "V2R2C1.json"
_write_json(projection, _record())
digest = hashlib.sha256(projection.read_bytes()).hexdigest()
baseline = tmp_path / "baseline" / "current.json"
_write_json(
baseline,
{
"version": "V2R2C1",
"status": "PRODUCTION_DEPLOYED",
"production_deployed": True,
"registry_source": str(projection),
"registry_digest": digest,
},
)
assert list_registry_versions(
ws,
projection_root=projection_root,
baseline_file=baseline,
) == ["V2R1C7.json", "V2R2C1.json"]
def test_list_registry_versions_rejects_projection_digest_drift(tmp_path: Path):
ws = tmp_path / "ws"
(ws / "versions").mkdir(parents=True)
projection_root = tmp_path / "projection"
projection = projection_root / "V2R2C1.json"
_write_json(projection, _record())
baseline = tmp_path / "baseline.json"
_write_json(
baseline,
{
"version": "V2R2C1",
"status": "PRODUCTION_DEPLOYED",
"production_deployed": True,
"registry_source": str(projection),
"registry_digest": "0" * 64,
},
)
assert list_registry_versions(
ws,
projection_root=projection_root,
baseline_file=baseline,
) == []
def test_projection_only_version_is_not_runtime_artifact_convergence(tmp_path: Path):
ws = tmp_path / "ws"
(ws / "versions").mkdir(parents=True)
projection_root = tmp_path / "projection"
projection = projection_root / "V2R2C1.json"
_write_json(projection, _record())
baseline = tmp_path / "baseline.json"
_write_json(
baseline,
{
"version": "V2R2C1",
"status": "PRODUCTION_DEPLOYED",
"production_deployed": True,
"registry_source": str(projection),
"registry_digest": hashlib.sha256(projection.read_bytes()).hexdigest(),
},
)
report = list_registry_versions_report(
ws,
projection_root=projection_root,
baseline_file=baseline,
)
assert report["registry_versions"] == ["V2R2C1.json"]
assert report["attested_projection_version"] == "V2R2C1"
assert report["runtime_bundle_contains_version"] is False
assert report["source_registry_converged"] == "unknown"
assert report["runtime_registry_converged"] is False
assert report["runtime_bundle_contains_current_version"] is False
assert report["registry_consistency_status"] == "RUNTIME_ARTIFACT_RECONCILIATION_REQUIRED"
with pytest.raises(RegistryConvergenceError, match="runtime_bundle_missing"):
assert_runtime_registry_convergence(
ws,
"V2R2C1",
projection_root=projection_root,
baseline_file=baseline,
)
def _attested_projection(tmp_path: Path, record: dict) -> tuple[Path, Path]:
projection_root = tmp_path / "projection"
projection = projection_root / f"{record['version']}.json"
_write_json(projection, record)
baseline = tmp_path / "baseline.json"
_write_json(
baseline,
{
"version": record["version"],
"status": "PRODUCTION_DEPLOYED",
"production_deployed": True,
"registry_source": str(projection),
"registry_digest": hashlib.sha256(projection.read_bytes()).hexdigest(),
},
)
return projection_root, baseline
def test_list_report_source_good_runtime_missing_is_not_fully_consistent(tmp_path: Path):
record = _record()
bare, branch = _init_bare_repo(tmp_path, record)
projection_root, baseline = _attested_projection(tmp_path, record)
ws = tmp_path / "runtime"
(ws / "versions").mkdir(parents=True)
report = list_registry_versions_report(
ws,
projection_root=projection_root,
baseline_file=baseline,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
assert report["source_registry_converged"] is True
assert report["runtime_registry_converged"] is False
assert report["runtime_bundle_contains_current_version"] is False
assert report["registry_consistency_status"] == "RUNTIME_ARTIFACT_RECONCILIATION_REQUIRED"
def test_list_report_source_runtime_projection_good_is_fully_consistent(tmp_path: Path):
record = _record()
bare, branch = _init_bare_repo(tmp_path, record)
projection_root, baseline = _attested_projection(tmp_path, record)
ws = tmp_path / "runtime"
versions = ws / "versions"
versions.mkdir(parents=True)
_write_json(versions / f"{record['version']}.json", record)
report = list_registry_versions_report(
ws,
projection_root=projection_root,
baseline_file=baseline,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
assert report["source_registry_converged"] is True
assert report["runtime_registry_converged"] is True
assert report["runtime_bundle_contains_current_version"] is True
assert report["registry_consistency_status"] == "FULLY_CONSISTENT"
def test_list_report_source_missing_is_not_converged(tmp_path: Path):
record = _record()
work = tmp_path / "no-source-work"
bare = tmp_path / "no-source.git"
subprocess.run(["git", "init", "-q", str(work)], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.name", "test"], check=True)
subprocess.run(
["git", "-C", str(work), "config", "user.email", "test@example.invalid"],
check=True,
)
(work / "README").write_text("no source\n", encoding="utf-8")
subprocess.run(["git", "-C", str(work), "add", "README"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "no source"], check=True)
branch = subprocess.run(
["git", "-C", str(work), "symbolic-ref", "--short", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
projection_root, baseline = _attested_projection(tmp_path, record)
ws = tmp_path / "runtime"
(ws / "versions").mkdir(parents=True)
_write_json(ws / "versions" / f"{record['version']}.json", record)
report = list_registry_versions_report(
ws,
projection_root=projection_root,
baseline_file=baseline,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
assert report["source_registry_converged"] is False
assert report["runtime_registry_converged"] is True
assert report["registry_consistency_status"] == "SOURCE_REGISTRY_NOT_CONVERGED"
def test_list_registry_versions_excludes_non_version_entries(tmp_path: Path):
ws = tmp_path / "ws"
versions = ws / "versions"
versions.mkdir(parents=True)
(versions / "README.txt").write_text("do not expose\n", encoding="utf-8")
(versions / "scratch.json").write_text("{}\n", encoding="utf-8")
(versions / "proposals").mkdir()
(versions / "V2R1C7.json").write_text("{}\n", encoding="utf-8")
assert list_registry_versions(
ws,
projection_root=tmp_path / "projection",
baseline_file=tmp_path / "baseline.json",
) == ["V2R1C7.json"]
def _init_bare_repo(tmp_path: Path, record: dict) -> tuple[Path, str]:
work = tmp_path / "work"
bare = tmp_path / "repo.git"
subprocess.run(["git", "init", "-q", str(work)], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.name", "test"], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.email", "test@example.invalid"], check=True)
_write_json(work / "versions" / f"{record['version']}.json", record)
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "registry"], check=True)
branch = subprocess.run(
["git", "-C", str(work), "symbolic-ref", "--short", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
return bare, branch
def _init_bare_repo_with_work(tmp_path: Path, record: dict) -> tuple[Path, Path, str]:
work = tmp_path / "history-work"
bare = tmp_path / "history.git"
subprocess.run(["git", "init", "-q", str(work)], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.name", "test"], check=True)
subprocess.run(
["git", "-C", str(work), "config", "user.email", "test@example.invalid"],
check=True,
)
_write_json(work / "versions" / f"{record['version']}.json", record)
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "registry"], check=True)
branch = subprocess.run(
["git", "-C", str(work), "symbolic-ref", "--short", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
subprocess.run(["git", "-C", str(work), "remote", "add", "origin", str(bare)], check=True)
return work, bare, branch
def test_assert_source_registry_convergence_passes_matching_identity(tmp_path: Path):
record = _record()
bare, branch = _init_bare_repo(tmp_path, record)
projection = tmp_path / "projection.json"
_write_json(projection, record)
assert_source_registry_convergence(
projection,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
def test_assert_source_registry_convergence_fails_missing_entry(tmp_path: Path):
record = _record()
work = tmp_path / "work"
bare = tmp_path / "repo.git"
subprocess.run(["git", "init", "-q", str(work)], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.name", "test"], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.email", "test@example.invalid"], check=True)
(work / "README").write_text("x\n")
subprocess.run(["git", "-C", str(work), "add", "README"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "init"], check=True)
branch = subprocess.run(
["git", "-C", str(work), "symbolic-ref", "--short", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
projection = tmp_path / "projection.json"
_write_json(projection, record)
with pytest.raises(RegistryConvergenceError, match="source_registry_entry_missing"):
assert_source_registry_convergence(
projection,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
def test_assert_source_registry_convergence_fails_identity_mismatch(tmp_path: Path):
source = _record(tag_oid="5" * 40)
projection_record = _record(tag_oid="1" * 40)
bare, branch = _init_bare_repo(tmp_path, source)
projection = tmp_path / "projection.json"
_write_json(projection, projection_record)
with pytest.raises(RegistryConvergenceError, match="source_registry_mismatch:tag_oid"):
assert_source_registry_convergence(
projection,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
def test_assert_source_registry_convergence_requires_all_identity_fields(tmp_path: Path):
source = _record()
source.pop("peeled_target_oid")
bare, branch = _init_bare_repo(tmp_path, source)
projection = tmp_path / "projection.json"
_write_json(projection, _record())
with pytest.raises(RegistryConvergenceError, match="peeled_target_oid"):
assert_source_registry_convergence(
projection,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
def test_assert_source_registry_convergence_rejects_wrong_repository(tmp_path: Path):
source = _record()
source["repository"] = "other/repository"
bare, branch = _init_bare_repo(tmp_path, source)
projection = tmp_path / "projection.json"
_write_json(projection, _record())
with pytest.raises(RegistryConvergenceError, match="repository"):
assert_source_registry_convergence(
projection,
repo_git_dir=bare,
main_ref=f"refs/heads/{branch}",
)
def test_source_registry_main_convergence_accepts_unrelated_later_main_commit(tmp_path: Path):
record = _record()
work, bare, branch = _init_bare_repo_with_work(tmp_path, record)
source_main = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
(work / "README").write_text("unrelated later main commit\n", encoding="utf-8")
subprocess.run(["git", "-C", str(work), "add", "README"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "unrelated"], check=True)
subprocess.run(["git", "-C", str(work), "push", "-q", "origin", branch], check=True)
result = verify_source_registry_main_convergence(
bare,
record["version"],
main_ref=f"refs/heads/{branch}",
source_registry_main_oid=source_main,
expected_tag_oid=record["tag_oid"],
expected_target_oid=record["target_oid"],
expected_evidence_closure_oid=record["evidence_closure_oid"],
expected_approval_plan_id=record["approval_plan_id"],
)
assert result["source_registry_main_is_ancestor_of_current_main"] is True
def test_source_registry_main_convergence_rejects_side_branch_only_commit(tmp_path: Path):
record = _record()
work, bare, branch = _init_bare_repo_with_work(tmp_path, record)
base = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "-C", str(work), "checkout", "-qb", "side"], check=True)
(work / "side.txt").write_text("side\n", encoding="utf-8")
subprocess.run(["git", "-C", str(work), "add", "side.txt"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "side"], check=True)
side = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "-C", str(work), "checkout", "-q", branch], check=True)
(work / "main.txt").write_text("main\n", encoding="utf-8")
subprocess.run(["git", "-C", str(work), "add", "main.txt"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "main"], check=True)
subprocess.run(["git", "-C", str(work), "push", "-q", "origin", branch], check=True)
assert base != side
with pytest.raises(RegistryConvergenceError, match="not_ancestor"):
verify_source_registry_main_convergence(
bare,
record["version"],
main_ref=f"refs/heads/{branch}",
source_registry_main_oid=side,
)
def test_source_registry_main_convergence_rejects_removed_entry(tmp_path: Path):
record = _record()
work, bare, branch = _init_bare_repo_with_work(tmp_path, record)
source_main = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
(work / "versions" / f"{record['version']}.json").unlink()
subprocess.run(["git", "-C", str(work), "add", "-u", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "remove"], check=True)
subprocess.run(["git", "-C", str(work), "push", "-q", "origin", branch], check=True)
with pytest.raises(RegistryConvergenceError, match="entry_missing"):
verify_source_registry_main_convergence(
bare,
record["version"],
main_ref=f"refs/heads/{branch}",
source_registry_main_oid=source_main,
)
def test_source_registry_main_convergence_rejects_altered_entry(tmp_path: Path):
record = _record()
work, bare, branch = _init_bare_repo_with_work(tmp_path, record)
source_main = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
altered = dict(record)
altered["approval_plan_id"] = "sha256:" + "9" * 64
_write_json(work / "versions" / f"{record['version']}.json", altered)
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "alter"], check=True)
subprocess.run(["git", "-C", str(work), "push", "-q", "origin", branch], check=True)
with pytest.raises(RegistryConvergenceError, match="entry_changed"):
verify_source_registry_main_convergence(
bare,
record["version"],
main_ref=f"refs/heads/{branch}",
source_registry_main_oid=source_main,
)
def test_source_registry_generator_predeclares_release_identity():
value = build_source_registry_entry(
version="V2R2C1",
tag_oid="1" * 40,
target_oid="2" * 40,
peeled_target_oid="2" * 40,
evidence_closure_oid="3" * 40,
approval_plan_id="sha256:" + "4" * 64,
)
assert set(
(
"version",
"tag_oid",
"target_oid",
"peeled_target_oid",
"evidence_closure_oid",
"approval_plan_id",
)
).issubset(value)
def test_authoritative_repository_rejects_arbitrary_repository(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
record = _record()
bare, _ = _init_bare_repo(tmp_path, record)
monkeypatch.delenv("AIOS_TEST_MODE")
with pytest.raises(RegistryConvergenceError, match="path_mismatch"):
verify_authoritative_repository(bare)
def test_production_authority_defaults_are_fixed_and_env_overrides_fail(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
monkeypatch.delenv("AIOS_TEST_MODE")
monkeypatch.delenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", raising=False)
monkeypatch.delenv("AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT", raising=False)
assert configured_authoritative_git_dir() == Path("/data/aios-gitops/repo.git")
assert configured_approval_authority_root() == Path(
"/var/lib/aios-gitops/approval-authority"
)
monkeypatch.setenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", str(tmp_path / "fake.git"))
with pytest.raises(RegistryConvergenceError, match="override_forbidden"):
configured_authoritative_git_dir()
monkeypatch.setenv("AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT", str(tmp_path / "fake-root"))
with pytest.raises(RegistryConvergenceError, match="override_forbidden"):
configured_approval_authority_root()
def test_production_canonical_repository_and_test_injected_authority(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
record = _record()
bare, _ = _init_bare_repo(tmp_path, record)
subprocess.run(
[
"git",
f"--git-dir={bare}",
"config",
"remote.gitea.url",
"https://g.cnnetmkt.top/aios/aios.git",
],
check=True,
)
monkeypatch.delenv("AIOS_TEST_MODE")
monkeypatch.setattr(registry_module, "DEFAULT_AUTHORITATIVE_GIT_DIR", bare)
assert verify_authoritative_repository(bare)["repository_identity"] == "aios/aios"
monkeypatch.setenv("AIOS_TEST_MODE", "1")
monkeypatch.setenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", str(bare))
assert verify_authoritative_repository(bare)["configured_path"] == str(bare.resolve())
def test_authoritative_git_unavailable_stays_unknown_and_closure_fails(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
record = _record()
projection_root, baseline = _attested_projection(tmp_path, record)
ws = tmp_path / "runtime"
(ws / "versions").mkdir(parents=True)
_write_json(ws / "versions" / f"{record['version']}.json", record)
missing = tmp_path / "missing-authority.git"
report = list_registry_versions_report(
ws,
projection_root=projection_root,
baseline_file=baseline,
repo_git_dir=missing,
)
assert report["source_registry_converged"] == "unknown"
assert report["source_registry_convergence_error"] == "authoritative_repository_unavailable"
monkeypatch.delenv("AIOS_TEST_MODE")
with pytest.raises(RegistryConvergenceError, match="path_unavailable"):
assert_source_registry_convergence(
projection_root / f"{record['version']}.json",
repo_git_dir=missing,
)
def test_source_registry_snapshot_stays_pinned_after_main_advances(tmp_path: Path):
work = tmp_path / "work"
bare = tmp_path / "repo.git"
subprocess.run(["git", "init", "-q", str(work)], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.name", "test"], check=True)
subprocess.run(
["git", "-C", str(work), "config", "user.email", "test@example.invalid"],
check=True,
)
record = _record()
_write_json(work / "versions" / "V2R2C1.json", record)
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "registry-1"], check=True)
branch = subprocess.run(
["git", "-C", str(work), "branch", "--show-current"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
pinned = source_registry_snapshot(
bare,
"V2R2C1",
main_ref=f"refs/heads/{branch}",
)
updated = dict(record)
updated["review_marker"] = "main-advanced"
_write_json(work / "versions" / "V2R2C1.json", updated)
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "registry-2"], check=True)
subprocess.run(["git", "-C", str(work), "remote", "add", "authority", str(bare)], check=True)
subprocess.run(["git", "-C", str(work), "push", "-q", "authority", branch], check=True)
repinned = source_registry_snapshot(
bare,
"V2R2C1",
main_ref=f"refs/heads/{branch}",
source_registry_main_oid=pinned["source_registry_main_oid"],
)
assert repinned["source_registry_main_oid"] == pinned["source_registry_main_oid"]
assert repinned["bytes"] == pinned["bytes"]
assert repinned["source_registry_sha256"] == pinned["source_registry_sha256"]
assert repinned["record"].get("review_marker") is None
def _git_commit_with_release_tag(tmp_path: Path, *, annotated: bool = True):
work = tmp_path / "authority-work"
bare = tmp_path / "authority.git"
subprocess.run(["git", "init", "-q", str(work)], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.name", "test"], check=True)
subprocess.run(["git", "-C", str(work), "config", "user.email", "test@example.invalid"], check=True)
(work / "README").write_text("release\n", encoding="utf-8")
subprocess.run(["git", "-C", str(work), "add", "README"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "target"], check=True)
target = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"], capture_output=True, text=True, check=True
).stdout.strip()
tag_args = ["git", "-C", str(work), "tag"]
if annotated:
tag_args += ["-a", "V2R2C1", target, "-m", "release"]
else:
tag_args += ["V2R2C1", target]
subprocess.run(tag_args, check=True)
tag_oid = subprocess.run(
["git", "-C", str(work), "rev-parse", "refs/tags/V2R2C1"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "-C", str(work), "commit", "--allow-empty", "-qm", "evidence"], check=True)
evidence = subprocess.run(
["git", "-C", str(work), "rev-parse", "HEAD"], capture_output=True, text=True, check=True
).stdout.strip()
record = _record(tag_oid=tag_oid, target_oid=target)
record["evidence_closure_oid"] = evidence
_write_json(work / "versions" / "V2R2C1.json", record)
subprocess.run(["git", "-C", str(work), "add", "versions"], check=True)
subprocess.run(["git", "-C", str(work), "commit", "-qm", "source registry"], check=True)
branch = subprocess.run(
["git", "-C", str(work), "branch", "--show-current"], capture_output=True, text=True, check=True
).stdout.strip()
subprocess.run(["git", "clone", "-q", "--bare", str(work), str(bare)], check=True)
return work, bare, branch, record, target, evidence, tag_oid
def test_source_registry_git_identity_rejects_lightweight_tag(tmp_path: Path):
_, bare, _, record, _, _, _ = _git_commit_with_release_tag(tmp_path, annotated=False)
with pytest.raises(RegistryConvergenceError, match="tag_not_annotated"):
verify_source_registry_git_identity(bare, record)
def test_source_registry_git_identity_proves_annotated_tag_and_lineage(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
_, bare, _, record, target, evidence, tag_oid = _git_commit_with_release_tag(tmp_path)
result = verify_source_registry_git_identity(bare, record)
assert result["tag_oid"] == tag_oid
assert result["target_oid"] == target
assert result["evidence_closure_oid"] == evidence
authority_root = tmp_path / "authority-root"
authority_root.mkdir()
monkeypatch.setenv("AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT", str(authority_root))
authority = _canonical_approval(authority_root, record)
assert verify_approval_authority(authority, record)["release_binding_verified"] is True
def test_source_registry_generator_requires_authoritative_git_and_approval(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
):
_, bare, _, record, _, _, _ = _git_commit_with_release_tag(tmp_path)
subprocess.run(
["git", f"--git-dir={bare}", "config", "remote.gitea.url", "https://g.cnnetmkt.top/aios/aios.git"],
check=True,
)
authority_root = tmp_path / "authority-root"
authority_root.mkdir()
monkeypatch.setenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", str(bare))
monkeypatch.setenv("AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT", str(authority_root))
authority = _canonical_approval(authority_root, record)
output = tmp_path / "generated" / "versions" / "V2R2C1.json"
script = Path(__file__).resolve().parents[1] / "scripts" / "generate_source_registry.py"
command = [
sys.executable,
str(script),
"--output",
str(output),
"--version",
record["version"],
"--tag-oid",
record["tag_oid"],
"--target-oid",
record["target_oid"],
"--peeled-target-oid",
record["peeled_target_oid"],
"--evidence-closure-oid",
record["evidence_closure_oid"],
"--approval-plan-id",
record["approval_plan_id"],
"--repository",
str(bare),
"--approval-authority",
str(authority),
]
completed = subprocess.run(command, capture_output=True, text=True, check=False)
assert completed.returncode == 0, completed.stderr
assert json.loads(output.read_text(encoding="utf-8"))["tag_oid"] == record["tag_oid"]
wrong = list(command)
wrong[wrong.index("--approval-plan-id") + 1] = "sha256:" + "f" * 64
failed = subprocess.run(wrong, capture_output=True, text=True, check=False)
assert failed.returncode != 0
other = tmp_path / "other.git"
subprocess.run(["git", "clone", "-q", "--bare", str(bare), str(other)], check=True)
arbitrary = list(command)
arbitrary[arbitrary.index("--repository") + 1] = str(other)
failed_authority = subprocess.run(arbitrary, capture_output=True, text=True, check=False)
assert failed_authority.returncode != 0
def test_approval_authority_rejects_unassociated_plan(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
record = _record()
authority_root = tmp_path / "authority-root"
authority_root.mkdir()
monkeypatch.setenv("AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT", str(authority_root))
authority = authority_root / f"{record['version']}.json"
_write_json(
authority,
{
"schema": "aios.v2r2c1.tag-approval-request.v1",
"state": "APPROVED",
"approval_plan_id": record["approval_plan_id"],
},
)
authority.chmod(0o640)
with pytest.raises(RegistryConvergenceError, match="binding_missing"):
verify_approval_authority(authority, record)
def test_approval_authority_rejects_untrusted_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
authority_root = tmp_path / "authority-root"
authority_root.mkdir()
monkeypatch.setenv("AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT", str(authority_root))
authority = tmp_path / "approval.json"
_write_json(authority, {"schema": "aios.v2r2c1.tag-approval-request.v1"})
with pytest.raises(RegistryConvergenceError, match="untrusted_path"):
verify_approval_authority(authority, _record())
def test_approval_authority_rejects_revoked_receipt(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
authority_root = tmp_path / "authority-root"
authority_root.mkdir()
monkeypatch.setenv("AIOS_GITOPS_APPROVAL_AUTHORITY_ROOT", str(authority_root))
record = _record()
authority = _canonical_approval(authority_root, record)
value = json.loads(authority.read_text(encoding="utf-8"))
value["revoked"] = True
authority.write_text(json.dumps(value) + "\n", encoding="utf-8")
authority.chmod(0o640)
with pytest.raises(RegistryConvergenceError, match="revoked"):
verify_approval_authority(authority, record)
def test_baseline_apply_wires_convergence_to_caller_repository():
source = inspect.getsource(adapter.baseline_apply)
assert "assert_source_registry_convergence(" in source
assert "repo_git_dir=args.repository" in source
# The gate must execute before the dict that writes final production status.
assert source.index("assert_source_registry_convergence(") < source.index(
'"status": "PRODUCTION_DEPLOYED"'
)
+21
View File
@@ -0,0 +1,21 @@
{
"approval_plan_id": "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e",
"artifact_sha256": "ab4ae257346c82863f05abf5e309381a1c86612957608c3a4f4ab5cc9233f052",
"deployment_state": "ACTIVE",
"enabled": true,
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"peeled_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"production_deployed": true,
"production_port": 18927,
"production_service": "aios-v2r1c7-production.service",
"registered": true,
"repository": "aios/aios",
"schema": "aios.production-registry.v2",
"status": "PRODUCTION_DEPLOYED",
"tag": "V2R2C1",
"tag_object": "07fc2746b688f5f243f29b39de39f32edc3dd9d2",
"tag_oid": "07fc2746b688f5f243f29b39de39f32edc3dd9d2",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"updated_at": "2026-08-09T09:13:13Z",
"version": "V2R2C1"
}