Compare commits
22
Commits
Regular → Executable
+1
-47
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, urllib.request, uuid
|
||||
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, uuid
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -404,51 +404,6 @@ 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)
|
||||
@@ -950,4 +905,3 @@ def main():
|
||||
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
|
||||
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
|
||||
if __name__ == '__main__': main()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""ChatGPT GitOps Bridge v2 contract and governed release operations."""
|
||||
"""ChatGPT GitOps Bridge v2 contract and immutable runtime identity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
@@ -6,28 +6,8 @@ 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",
|
||||
"getGitOpsJob",
|
||||
"resumeGitOpsJob",
|
||||
"reconcileGitOpsJob",
|
||||
"createTagApprovalRequest",
|
||||
"requestOrCreateTag",
|
||||
"getTagPublicationStatus",
|
||||
"buildProductionArtifact",
|
||||
"executeApprovedProductionRelease",
|
||||
"getReleaseConsistency",
|
||||
"runReleaseAcceptance",
|
||||
"getControlPlaneSnapshot",
|
||||
)
|
||||
|
||||
BRIDGE_OPERATIONS = ("getRuntimeStatus", "getGovernedWriteCapabilities", "createGovernedTask", "acquireWriterLease", "commitFileGoverned", "commitFilesGoverned", "getGitOpsJob", "resumeGitOpsJob", "reconcileGitOpsJob", "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"),
|
||||
@@ -36,13 +16,6 @@ OPERATION_REQUIRED_FIELDS = {
|
||||
"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": (),
|
||||
}
|
||||
|
||||
@@ -67,82 +40,37 @@ class ControlPlaneReleaseManifest:
|
||||
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,
|
||||
}
|
||||
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)
|
||||
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):
|
||||
if any(marker in {"workspace", "workspaces", "worktree", "worktrees"} for marker in (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"
|
||||
)
|
||||
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.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 {})
|
||||
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",
|
||||
},
|
||||
}
|
||||
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}
|
||||
|
||||
@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):
|
||||
if tuple(key for key in OPERATION_REQUIRED_FIELDS[operation] if key not in request):
|
||||
raise BridgeV2Error("BRIDGE_REQUEST_FIELDS_MISSING")
|
||||
handler = self.handlers.get(operation)
|
||||
if handler is None:
|
||||
@@ -156,12 +84,4 @@ class BridgeV2Adapter:
|
||||
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 {})
|
||||
|
||||
@@ -19,27 +19,9 @@ 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 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/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",
|
||||
}
|
||||
ROUTES = {"/v2/governed-task": "createGovernedTask", "/v2/writer-lease": "acquireWriterLease", "/v2/commit-file": "commitFileGoverned", "/v2/commit-files": "commitFilesGoverned", "/v2/job": "getGitOpsJob", "/v2/job/resume": "resumeGitOpsJob", "/v2/job/reconcile": "reconcileGitOpsJob"}
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
@@ -53,12 +35,7 @@ class RuntimeConfig:
|
||||
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:
|
||||
@@ -74,7 +51,6 @@ class GovernedRuntime:
|
||||
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"):
|
||||
@@ -91,22 +67,7 @@ class GovernedRuntime:
|
||||
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,
|
||||
}
|
||||
return {"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_identity": self.config.repository, "authoritative_git_dir": str(self.config.authoritative_git_dir)}
|
||||
|
||||
def _job_root(self, task_id: str) -> Path:
|
||||
return self.config.state_root / "jobs" / _safe(task_id)
|
||||
@@ -148,16 +109,7 @@ class GovernedRuntime:
|
||||
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)
|
||||
result = executor.execute(binding, commit)
|
||||
receipt = result.get('receipt') if isinstance(result, Mapping) else None
|
||||
receipt = receipt if isinstance(receipt, Mapping) else {}
|
||||
commit_oid = receipt.get('authority_oid') or receipt.get('desired_remote_oid')
|
||||
if not isinstance(commit_oid, str) or len(commit_oid) != 40 or any(ch not in '0123456789abcdef' for ch in commit_oid):
|
||||
raise RuntimeError('COMMIT_RECEIPT_OID_MISSING')
|
||||
response = dict(result)
|
||||
response['commit_oid'] = commit_oid
|
||||
response['commit_visible'] = bool(receipt.get('commit_visible') or receipt.get('remote_verified'))
|
||||
return response
|
||||
return executor.execute(binding, commit)
|
||||
|
||||
def _controller_for_job(self, job_id: str) -> GitOpsJobController:
|
||||
for controller in self.controllers.values():
|
||||
@@ -349,23 +301,7 @@ class GovernedRuntime:
|
||||
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,
|
||||
"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,
|
||||
}
|
||||
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, "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()}
|
||||
|
||||
|
||||
def build_governed_handlers(runtime_config: RuntimeConfig | Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
@@ -391,7 +327,6 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
||||
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
|
||||
@@ -401,7 +336,7 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
||||
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
|
||||
if operation is None 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)
|
||||
@@ -426,8 +361,6 @@ def main() -> None:
|
||||
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)
|
||||
|
||||
@@ -284,24 +284,13 @@ class GiteaContinuationRemote:
|
||||
|
||||
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")
|
||||
value = self._api(f"/repos/{quote(owner)}/{quote(name)}/branches/{quote(branch, safe='/')}")
|
||||
if value is None:
|
||||
return None
|
||||
oid = value.get("commit", {}).get("id") if isinstance(value, dict) else None
|
||||
if not isinstance(oid, str) or not OID_RE.fullmatch(oid):
|
||||
raise ContinuationExecutionError("GITEA_API_BRANCH_INVALID")
|
||||
return oid
|
||||
|
||||
def commit_visible(self, oid: str) -> bool:
|
||||
owner, name = self.repository.split("/", 1)
|
||||
|
||||
@@ -74,12 +74,6 @@ class WriterLeaseRequestV2:
|
||||
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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -995,7 +995,7 @@ def validate_writer_lease_request_v2(raw: Mapping[str, Any], probe: Any) -> dict
|
||||
"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",
|
||||
"create_target_branch": False,
|
||||
"worktree_mode": "detached_at_expected_head",
|
||||
"cas_ref_update_required": True,
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/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
|
||||
|
||||
@@ -1,568 +0,0 @@
|
||||
"""Governed V2R2C1 production release orchestration.
|
||||
|
||||
This module is deliberately scoped to the Owner-approved V2R2C1 binding. It
|
||||
uses the existing TagPublisher and metadata adapters; it does not introduce a
|
||||
second Git writer, a second durable job store, or an alternate baseline.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Mapping
|
||||
|
||||
from gitea_tag_publication import (
|
||||
GiteaTagClient,
|
||||
GitTagBackend,
|
||||
PublicationManualOwnerReviewRequired,
|
||||
PublicationPending,
|
||||
PublicationReconciliationRequired,
|
||||
PublicationRequest,
|
||||
TagPublisher,
|
||||
)
|
||||
|
||||
|
||||
VERSION = "V2R2C1"
|
||||
TARGET_OID = "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
|
||||
EVIDENCE_CLOSURE_OID = "a8b906d1bc24088c19647e98de6a679268f6d0a2"
|
||||
APPROVAL_PLAN_ID = "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e"
|
||||
REPOSITORY = "aios/aios"
|
||||
SERVICE = "aios-v2r1c7-production.service"
|
||||
PORT = 18927
|
||||
OID_RE = re.compile(r"^[0-9a-f]{40}$")
|
||||
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
|
||||
|
||||
|
||||
class ReleaseBlocked(RuntimeError):
|
||||
"""A release gate failed and no unsafe recovery is permitted."""
|
||||
|
||||
|
||||
class ManualOwnerReviewRequired(ReleaseBlocked):
|
||||
pass
|
||||
|
||||
|
||||
class ReconciliationRequired(ReleaseBlocked):
|
||||
pass
|
||||
|
||||
|
||||
def canonical(value: object) -> bytes:
|
||||
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ReleaseBlocked("artifact must be a regular file")
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def atomic_json(path: Path, value: Mapping[str, Any], mode: int = 0o600) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_name(path.name + ".tmp")
|
||||
temporary.write_bytes(canonical(dict(value)))
|
||||
os.chmod(temporary, mode)
|
||||
os.replace(temporary, path)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ReleaseBinding:
|
||||
version: str = VERSION
|
||||
target_oid: str = TARGET_OID
|
||||
evidence_closure_oid: str = EVIDENCE_CLOSURE_OID
|
||||
approval_plan_id: str = APPROVAL_PLAN_ID
|
||||
repository: str = REPOSITORY
|
||||
production_service: str = SERVICE
|
||||
production_port: int = PORT
|
||||
|
||||
def validate(self) -> None:
|
||||
if self.version != VERSION or self.repository != REPOSITORY:
|
||||
raise ManualOwnerReviewRequired("release binding is outside V2R2C1 scope")
|
||||
if not OID_RE.fullmatch(self.target_oid) or self.target_oid != TARGET_OID:
|
||||
raise ManualOwnerReviewRequired("V2R2C1 target_oid mismatch")
|
||||
if not OID_RE.fullmatch(self.evidence_closure_oid) or self.evidence_closure_oid != EVIDENCE_CLOSURE_OID:
|
||||
raise ManualOwnerReviewRequired("V2R2C1 evidence closure mismatch")
|
||||
if self.approval_plan_id != APPROVAL_PLAN_ID:
|
||||
raise ManualOwnerReviewRequired("V2R2C1 approval plan mismatch")
|
||||
if self.production_service != SERVICE or int(self.production_port) != PORT:
|
||||
raise ManualOwnerReviewRequired("production service binding mismatch")
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"version": self.version,
|
||||
"target_oid": self.target_oid,
|
||||
"evidence_closure_oid": self.evidence_closure_oid,
|
||||
"approval_plan_id": self.approval_plan_id,
|
||||
"repository": self.repository,
|
||||
"production_service": self.production_service,
|
||||
"production_port": self.production_port,
|
||||
}
|
||||
|
||||
|
||||
class StateLock:
|
||||
def __init__(self, path: Path):
|
||||
self.path = path
|
||||
self.handle = None
|
||||
|
||||
def __enter__(self) -> "StateLock":
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.handle = self.path.open("a+", encoding="utf-8")
|
||||
fcntl.flock(self.handle.fileno(), fcntl.LOCK_EX)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> None:
|
||||
if self.handle is not None:
|
||||
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
|
||||
self.handle.close()
|
||||
|
||||
|
||||
class ReleaseState:
|
||||
def __init__(self, root: Path, binding: ReleaseBinding):
|
||||
self.root = root
|
||||
self.path = root / "release-state.json"
|
||||
self.binding = binding
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
if self.path.exists():
|
||||
value = json.loads(self.path.read_text(encoding="utf-8"))
|
||||
if value.get("binding") != binding.as_dict():
|
||||
raise ManualOwnerReviewRequired("persisted release binding conflict")
|
||||
self.value = value
|
||||
else:
|
||||
self.value = {
|
||||
"schema": "aios.v2r2c1.governed-production-release-state.v1",
|
||||
"binding": binding.as_dict(),
|
||||
"state": "INITIAL",
|
||||
"completed": {},
|
||||
"history": [],
|
||||
}
|
||||
self.save()
|
||||
|
||||
def save(self) -> None:
|
||||
atomic_json(self.path, self.value)
|
||||
|
||||
def record(self, state: str, step: str, result: Mapping[str, Any]) -> None:
|
||||
safe = json.loads(json.dumps(dict(result)))
|
||||
self.value["state"] = state
|
||||
self.value.setdefault("completed", {})[step] = safe
|
||||
self.value.setdefault("history", []).append(
|
||||
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "step": step, "result": safe}
|
||||
)
|
||||
self.save()
|
||||
|
||||
def fail(self, state: str, reason: str) -> None:
|
||||
self.value["state"] = state
|
||||
self.value.setdefault("history", []).append(
|
||||
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "reason": str(reason)[:500]}
|
||||
)
|
||||
self.save()
|
||||
|
||||
|
||||
def _git(git_dir: Path, *args: str, check: bool = True) -> str:
|
||||
completed = subprocess.run(
|
||||
["git", f"--git-dir={git_dir}", *args],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=45,
|
||||
env={"PATH": os.environ.get("PATH", "")},
|
||||
)
|
||||
if check and completed.returncode != 0:
|
||||
raise ReconciliationRequired(completed.stderr.strip() or "authoritative git query failed")
|
||||
return completed.stdout.strip()
|
||||
|
||||
|
||||
def _ancestor(git_dir: Path, ancestor: str, descendant: str) -> bool:
|
||||
return subprocess.run(
|
||||
["git", f"--git-dir={git_dir}", "merge-base", "--is-ancestor", ancestor, descendant],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
timeout=45,
|
||||
env={"PATH": os.environ.get("PATH", "")},
|
||||
).returncode == 0
|
||||
|
||||
|
||||
def verify_lineage(git_dir: Path, binding: ReleaseBinding) -> dict[str, Any]:
|
||||
binding.validate()
|
||||
if _git(git_dir, "cat-file", "-t", binding.target_oid) != "commit":
|
||||
raise ReleaseBlocked("approved target_oid is not a commit")
|
||||
if _git(git_dir, "cat-file", "-t", binding.evidence_closure_oid) != "commit":
|
||||
raise ReleaseBlocked("evidence closure is not a commit")
|
||||
if not _ancestor(git_dir, binding.target_oid, binding.evidence_closure_oid):
|
||||
raise ManualOwnerReviewRequired("approved target is not an ancestor of evidence closure")
|
||||
return {
|
||||
"target_oid": binding.target_oid,
|
||||
"evidence_closure_oid": binding.evidence_closure_oid,
|
||||
"target_is_ancestor_of_evidence": True,
|
||||
"main_oid": _git(git_dir, "rev-parse", "refs/heads/main"),
|
||||
}
|
||||
|
||||
|
||||
def verify_owner_approval(value: Mapping[str, Any], binding: ReleaseBinding) -> dict[str, Any]:
|
||||
approval = value.get("owner_approval")
|
||||
if not isinstance(approval, Mapping):
|
||||
raise ReleaseBlocked("OWNER_APPROVAL_RECEIPT_REQUIRED")
|
||||
if approval.get("approved") is not True or approval.get("status") != "APPROVED":
|
||||
raise ReleaseBlocked("OWNER_APPROVAL_NOT_ACCEPTED")
|
||||
if approval.get("approved_by") != "aiman":
|
||||
raise ManualOwnerReviewRequired("OWNER_APPROVAL_ACTOR_MISMATCH")
|
||||
if approval.get("approval_source") != "explicit_owner_approval_in_chat":
|
||||
raise ManualOwnerReviewRequired("OWNER_APPROVAL_SOURCE_MISMATCH")
|
||||
for key, expected in binding.as_dict().items():
|
||||
if key in {"repository", "production_service", "production_port"}:
|
||||
continue
|
||||
if approval.get(key) != expected:
|
||||
raise ManualOwnerReviewRequired("OWNER_APPROVAL_BINDING_MISMATCH:" + key)
|
||||
return dict(approval)
|
||||
|
||||
|
||||
def _payload_file(git_dir: Path, oid: str, path: str) -> bytes:
|
||||
completed = subprocess.run(
|
||||
["git", f"--git-dir={git_dir}", "show", f"{oid}:{path}"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
timeout=45,
|
||||
env={"PATH": os.environ.get("PATH", "")},
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise ReleaseBlocked("required release source missing:" + path)
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def _commit_epoch(git_dir: Path, oid: str) -> int:
|
||||
return int(_git(git_dir, "show", "-s", "--format=%ct", oid))
|
||||
|
||||
|
||||
def build_production_artifact(
|
||||
git_dir: Path,
|
||||
binding: ReleaseBinding,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a deterministic executable V2R2C1 health artifact from target_oid."""
|
||||
binding.validate()
|
||||
verify_lineage(git_dir, binding)
|
||||
epoch = _commit_epoch(git_dir, binding.target_oid)
|
||||
output_root.mkdir(parents=True, exist_ok=True)
|
||||
stem = f"aios-{VERSION}-{binding.target_oid[:12]}"
|
||||
output = output_root / (stem + ".tar.zst")
|
||||
with tempfile.TemporaryDirectory(prefix="v2r2c1-artifact-") as temp:
|
||||
root = Path(temp) / "payload"
|
||||
root.mkdir(parents=True)
|
||||
source_paths = {
|
||||
"active-runtime/app/deployment_state.py": "active-runtime/app/deployment_state.py",
|
||||
"active-runtime/app/v2r1c6_privacy_tools.py": "active-runtime/app/v2r1c6_privacy_tools.py",
|
||||
}
|
||||
for destination, source in source_paths.items():
|
||||
target = root / destination
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(_payload_file(git_dir, binding.target_oid, source))
|
||||
generated: dict[str, str] = {
|
||||
"VERSION": VERSION + "\n",
|
||||
"active-runtime/app/v2r2c1_health_service.py": (
|
||||
"from v2r1c6_privacy_tools import main\n\n"
|
||||
"if __name__ == '__main__':\n raise SystemExit(main())\n"
|
||||
),
|
||||
"deploy/aios-v2/systemd/aios-v2r1c7-production.service": (
|
||||
"[Unit]\nDescription=AIOS V2R2C1 governed health contract service\n"
|
||||
"After=network.target\n\n[Service]\nType=simple\n"
|
||||
"User=aios-v2r1c7\nGroup=aios-v2r1c7\n"
|
||||
"WorkingDirectory=/opt/aios/v2r2c1\n"
|
||||
"EnvironmentFile=/etc/aios/aios-v2r2c1.env\n"
|
||||
"ExecStart=/usr/bin/python3 /opt/aios/v2r2c1/active-runtime/app/v2r2c1_health_service.py --serve --host 127.0.0.1 --port ${AIOS_V2R1C7_PORT}\n"
|
||||
"Restart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nPrivateTmp=true\n"
|
||||
"ProtectHome=true\nProtectSystem=strict\nProtectKernelLogs=true\n"
|
||||
"ProtectKernelModules=true\nProtectKernelTunables=true\nProtectControlGroups=true\n"
|
||||
"RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\nIPAddressDeny=any\n"
|
||||
"IPAddressAllow=localhost\nUMask=0077\n\n[Install]\nWantedBy=multi-user.target\n"
|
||||
),
|
||||
"deploy/aios-v2/v2r2c1-runtime-config.json": json.dumps({
|
||||
"schema": "aios.v2r2c1.production-runtime.v1",
|
||||
"version": VERSION,
|
||||
"target_oid": binding.target_oid,
|
||||
"service": SERVICE,
|
||||
"port": PORT,
|
||||
"health_path": "/health",
|
||||
"deployment_state_source": "environment_and_state_file",
|
||||
}, sort_keys=True, separators=(",", ":")) + "\n",
|
||||
"deploy/aios-v2/v2r2c1-rollback.json": json.dumps({
|
||||
"schema": "aios.v2r2c1.rollback-metadata.v1",
|
||||
"rollback_version": "V2R1C7",
|
||||
"rollback_service": SERVICE,
|
||||
"rollback_target_is_previous_production": True,
|
||||
"automatic_rollback": False,
|
||||
}, sort_keys=True, separators=(",", ":")) + "\n",
|
||||
"scripts/probe_v2r2c1_health.py": (
|
||||
"import json,sys,urllib.request\n"
|
||||
"url=sys.argv[1] if len(sys.argv)>1 else 'http://127.0.0.1:18927/health'\n"
|
||||
"with urllib.request.urlopen(url, timeout=5) as r: print(json.dumps(json.load(r), sort_keys=True))\n"
|
||||
),
|
||||
"scripts/rollback_v2r2c1_release.sh": "#!/bin/sh\nset -eu\nprintf '%s\\n' 'rollback requires governed owner-approved executor'\nexit 2\n",
|
||||
"release-manifests/V2R2C1.json": json.dumps({
|
||||
"schema": "aios.release-source.v2",
|
||||
"version": VERSION,
|
||||
"repository": REPOSITORY,
|
||||
"target_oid": binding.target_oid,
|
||||
"evidence_closure_oid": binding.evidence_closure_oid,
|
||||
"production_deployed": False,
|
||||
"deployment_state": "STAGING",
|
||||
"artifact_kind": "executable-production-health",
|
||||
}, sort_keys=True, separators=(",", ":")) + "\n",
|
||||
}
|
||||
for rel, text in generated.items():
|
||||
target = root / rel
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(text, encoding="utf-8")
|
||||
if rel.startswith("scripts/"):
|
||||
target.chmod(0o755)
|
||||
metadata = {
|
||||
"schema": "aios.release-artifact-build.v3",
|
||||
"version": VERSION,
|
||||
"repository": REPOSITORY,
|
||||
"source_head_oid": binding.target_oid,
|
||||
"evidence_closure_oid": binding.evidence_closure_oid,
|
||||
"source_date_epoch": epoch,
|
||||
"clean_source": True,
|
||||
"executable_payload": True,
|
||||
"production_deployed": False,
|
||||
"tag_created": False,
|
||||
"baseline_changed": False,
|
||||
"secrets_included": False,
|
||||
}
|
||||
(root / "BUILD-METADATA.json").write_bytes(canonical(metadata))
|
||||
(root / "ROLLBACK-METADATA.json").write_bytes(canonical(json.loads((root / "deploy/aios-v2/v2r2c1-rollback.json").read_text(encoding="utf-8"))))
|
||||
names = sorted("payload/" + str(path.relative_to(root)).replace(os.sep, "/") for path in root.rglob("*") if path.is_file())
|
||||
(root / "ARTIFACT-MANIFEST.json").write_bytes(canonical({
|
||||
"schema": "aios.release-artifact-manifest.v3",
|
||||
"version": VERSION,
|
||||
"source_head_oid": binding.target_oid,
|
||||
"evidence_closure_oid": binding.evidence_closure_oid,
|
||||
"repository": REPOSITORY,
|
||||
"files": names + ["payload/ARTIFACT-MANIFEST.json"],
|
||||
"deployable": True,
|
||||
"executable_payload": True,
|
||||
"production_deployed": False,
|
||||
"tag_created": False,
|
||||
"baseline_changed": False,
|
||||
}))
|
||||
tar_path = Path(temp) / "release.tar"
|
||||
with tarfile.open(tar_path, "w") as archive:
|
||||
for path in sorted(root.rglob("*")):
|
||||
relative = str(path.relative_to(root)).replace(os.sep, "/")
|
||||
info = archive.gettarinfo(str(path), arcname="payload/" + relative)
|
||||
info.uid = info.gid = 0
|
||||
info.uname = "root"
|
||||
info.gname = "root"
|
||||
info.mtime = epoch
|
||||
info.mode = 0o755 if path.is_dir() or path.suffix == ".sh" else 0o644
|
||||
if path.is_file():
|
||||
with path.open("rb") as stream:
|
||||
archive.addfile(info, stream)
|
||||
else:
|
||||
archive.addfile(info)
|
||||
subprocess.run(["zstd", "-T1", "-19", "--no-progress", "-q", "-f", str(tar_path), "-o", str(output)], check=True, timeout=120)
|
||||
digest = sha256_file(output)
|
||||
receipt = {
|
||||
"schema": "aios.release-artifact-receipt.v3",
|
||||
"version": VERSION,
|
||||
"repository": REPOSITORY,
|
||||
"source_head_oid": binding.target_oid,
|
||||
"evidence_closure_oid": binding.evidence_closure_oid,
|
||||
"artifact": str(output),
|
||||
"artifact_sha256": digest,
|
||||
"artifact_size": output.stat().st_size,
|
||||
"deployable": True,
|
||||
"executable_payload": True,
|
||||
"production_deployed": False,
|
||||
"tag_created": False,
|
||||
"baseline_changed": False,
|
||||
}
|
||||
atomic_json(output.with_suffix(output.suffix + ".receipt.json"), receipt, mode=0o640)
|
||||
return receipt
|
||||
|
||||
|
||||
class GovernedProductionReleaseExecutor:
|
||||
def __init__(self, config: Any):
|
||||
self.config = config
|
||||
self.binding = ReleaseBinding()
|
||||
self.binding.validate()
|
||||
self.root = Path(config.state_root) / "v2r2c1-governed-production-release"
|
||||
self.state = ReleaseState(self.root, self.binding)
|
||||
workspace = self.root / "publication-workspace"
|
||||
workspace.mkdir(parents=True, exist_ok=True)
|
||||
self.publisher = TagPublisher(
|
||||
workspace,
|
||||
GitTagBackend(workspace, authoritative_repo=Path(config.authoritative_git_dir)),
|
||||
GiteaTagClient(config.api_base, REPOSITORY, Path(config.token_path)),
|
||||
config.remote_name,
|
||||
state_root=self.root / "tag-publication",
|
||||
)
|
||||
|
||||
def _request(self, request: Mapping[str, Any]) -> PublicationRequest:
|
||||
binding = self.binding
|
||||
for key, expected in binding.as_dict().items():
|
||||
if key in request and request[key] != expected:
|
||||
raise ManualOwnerReviewRequired("release request binding mismatch:" + key)
|
||||
return PublicationRequest(binding.version, binding.target_oid, binding.evidence_closure_oid, binding.approval_plan_id)
|
||||
|
||||
def approval_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
if request.get("execute") is not False:
|
||||
raise ReleaseBlocked("TAG_APPROVAL_REQUEST_MUST_BE_DRY_RUN")
|
||||
owner = verify_owner_approval(request, self.binding)
|
||||
with StateLock(self.root / "release.lock"):
|
||||
current = self.state.value.get("approval")
|
||||
approval_digest = hashlib.sha256(canonical(owner)).hexdigest()
|
||||
record = {
|
||||
"schema": "aios.v2r2c1.tag-approval-request.v1",
|
||||
"binding": self.binding.as_dict(),
|
||||
"owner_approval": owner,
|
||||
"owner_approval_digest_sha256": approval_digest,
|
||||
"execute": False,
|
||||
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
}
|
||||
if current and current != record:
|
||||
raise ManualOwnerReviewRequired("persisted Owner approval differs")
|
||||
self.state.value["approval"] = record
|
||||
self.state.record("APPROVAL_REQUEST_RECORDED", "tag_approval_request", record)
|
||||
return {"status": "RECORDED", "execute": False, "binding": self.binding.as_dict(), "owner_approval_digest_sha256": approval_digest}
|
||||
|
||||
def _require_approval(self) -> None:
|
||||
approval = self.state.value.get("approval")
|
||||
if not isinstance(approval, Mapping) or approval.get("binding") != self.binding.as_dict():
|
||||
raise ReleaseBlocked("OWNER_APPROVAL_REQUEST_NOT_RECORDED")
|
||||
|
||||
def publish_tag(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
if request.get("execute") is not True:
|
||||
raise ReleaseBlocked("TAG_PUBLICATION_REQUIRES_EXECUTE_TRUE")
|
||||
self._require_approval()
|
||||
publication = self._request(request)
|
||||
with StateLock(self.root / "release.lock"):
|
||||
try:
|
||||
verified = self.publisher._verify_publication(publication, allow_push=True, previously_deployed=False)
|
||||
except PublicationManualOwnerReviewRequired as exc:
|
||||
self.state.fail("MANUAL_OWNER_REVIEW_REQUIRED", str(exc))
|
||||
raise ManualOwnerReviewRequired(str(exc)) from exc
|
||||
except PublicationPending as exc:
|
||||
self.state.fail("REMOTE_PUSH_PENDING", str(exc))
|
||||
raise ReconciliationRequired(str(exc)) from exc
|
||||
except PublicationReconciliationRequired as exc:
|
||||
self.state.fail("RECONCILIATION_REQUIRED", str(exc))
|
||||
raise ReconciliationRequired(str(exc)) from exc
|
||||
result = dict(verified)
|
||||
self.state.record("TAG_VERIFIED", "tag", result)
|
||||
return {"status": "REMOTE_VERIFIED", **result}
|
||||
|
||||
def tag_status(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
self._request(request)
|
||||
return self.publisher.get_tag_publication_status(VERSION)
|
||||
|
||||
def build_artifact(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
self._require_approval()
|
||||
self._request(request)
|
||||
receipt = build_production_artifact(Path(self.config.authoritative_git_dir), self.binding, self.root / "artifacts")
|
||||
self.state.record("ARTIFACT_VERIFIED", "production_artifact", receipt)
|
||||
return {"status": "VERIFIED", **receipt}
|
||||
|
||||
def execute_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
self._require_approval()
|
||||
publication = self._request(request)
|
||||
tag_state = self.state.value.get("completed", {}).get("tag")
|
||||
if not isinstance(tag_state, Mapping) or tag_state.get("state") not in {"GITEA_API_CONFIRMED", "REMOTE_VERIFIED"}:
|
||||
raise ReleaseBlocked("REMOTE_TAG_PROOF_REQUIRED")
|
||||
artifact = self.state.value.get("completed", {}).get("production_artifact")
|
||||
if not isinstance(artifact, Mapping):
|
||||
artifact = self.build_artifact(request)
|
||||
value = {
|
||||
**self.binding.as_dict(),
|
||||
"tag_oid": tag_state.get("tag_oid") or tag_state.get("gitea_tag_oid"),
|
||||
"artifact_path": artifact["artifact"],
|
||||
"artifact_size": int(artifact["artifact_size"]),
|
||||
"artifact_sha256": artifact["artifact_sha256"],
|
||||
}
|
||||
# The production adapter is privileged and is run through the root
|
||||
# approved-release service boundary by the caller. This handler is
|
||||
# still useful for a root-owned staging invocation and remains
|
||||
# fail-closed when the adapter is not available.
|
||||
import v2r2c1_production_adapter
|
||||
runtime = v2r2c1_production_adapter.execute_request({"schema": "aios.production-executor-request.v1", "operation": "apply", **value})
|
||||
if runtime.get("status") != "ACTIVE_VERIFIED":
|
||||
self.state.fail("RECONCILIATION_REQUIRED", "production health did not verify")
|
||||
raise ReconciliationRequired("production health did not verify")
|
||||
self.state.record("PRODUCTION_VERIFIED", "production", runtime)
|
||||
return {"status": "PRODUCTION_VERIFIED", "runtime": runtime, "artifact": artifact, "tag_oid": value["tag_oid"]}
|
||||
|
||||
def consistency(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
publication = self._request(request)
|
||||
result = self.publisher.get_release_consistency(publication)
|
||||
result["release_binding"] = self.binding.as_dict()
|
||||
result["production_health"] = _health(self.config.health_url, self.binding, self.state.value.get("completed", {}).get("production_artifact", {}))
|
||||
result["registry"] = _read_json(Path(self.config.registry_root) / f"{VERSION}.json")
|
||||
result["baseline"] = _read_json(Path(self.config.baseline_path))
|
||||
return result
|
||||
|
||||
def acceptance(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
result = self.consistency(request)
|
||||
result["accepted"] = result.get("status") == "PASS" and result.get("production_health", {}).get("status") == "ACTIVE_VERIFIED"
|
||||
self.state.record("ACCEPTED" if result["accepted"] else "RECONCILIATION_REQUIRED", "acceptance", result)
|
||||
return result
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except FileNotFoundError:
|
||||
return {"status": "MISSING", "path": str(path)}
|
||||
if not isinstance(value, dict):
|
||||
return {"status": "INVALID", "path": str(path)}
|
||||
return {"status": "PRESENT", "path": str(path), **value}
|
||||
|
||||
|
||||
def _health(url: str, binding: ReleaseBinding, artifact: Mapping[str, Any]) -> dict[str, Any]:
|
||||
import urllib.request
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=5) as response:
|
||||
body = json.loads(response.read().decode("utf-8"))
|
||||
status = response.status
|
||||
except Exception as exc:
|
||||
return {"status": "UNAVAILABLE", "error": type(exc).__name__}
|
||||
expected_artifact = artifact.get("artifact_sha256")
|
||||
exact = (
|
||||
status == 200
|
||||
and body.get("version") == binding.version
|
||||
and body.get("commit_oid") == binding.target_oid
|
||||
and (not expected_artifact or body.get("artifact_sha256") == expected_artifact)
|
||||
and body.get("deployment_state") == "ACTIVE"
|
||||
and body.get("production_deployed") is True
|
||||
)
|
||||
return {"status": "ACTIVE_VERIFIED" if exact else "CONFLICT", "http_status": status, **body}
|
||||
|
||||
|
||||
def build_executor_config(config: Any) -> GovernedProductionReleaseExecutor:
|
||||
return GovernedProductionReleaseExecutor(config)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("operation", choices=("build-artifact", "status"))
|
||||
parser.add_argument("--git-dir", type=Path, default=Path("/data/aios-gitops/repo.git"))
|
||||
parser.add_argument("--output-root", type=Path, default=Path("/var/lib/aios-gitops/approved-artifacts/V2R2C1"))
|
||||
args = parser.parse_args()
|
||||
binding = ReleaseBinding()
|
||||
if args.operation == "build-artifact":
|
||||
print(json.dumps(build_production_artifact(args.git_dir, binding, args.output_root), sort_keys=True))
|
||||
else:
|
||||
print(json.dumps({"binding": binding.as_dict(), "status": "READY"}, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,135 +0,0 @@
|
||||
"""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 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/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":
|
||||
return base.probe(_args(), value)
|
||||
if value.get("operation") == "rollback":
|
||||
return rollback(value)
|
||||
raise ValueError("unsupported_operation")
|
||||
|
||||
|
||||
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())
|
||||
@@ -1,51 +0,0 @@
|
||||
[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
|
||||
@@ -1,6 +1,20 @@
|
||||
# Merge this fragment into docs/gitops/openapi-chatgpt-actions.yaml.
|
||||
components:
|
||||
schemas:
|
||||
WriterLeaseRequestV2:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [schema, task_id, repository, branch_mode, base_oid, expected_branch_oid, target_branch, generation, lease_seconds]
|
||||
properties:
|
||||
schema: {type: string, const: aios.task-writer-lease.v2}
|
||||
task_id: {type: string}
|
||||
repository: {type: string, const: aios/aios}
|
||||
branch_mode: {type: string, const: CONTINUE_EXISTING}
|
||||
base_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
|
||||
expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
|
||||
target_branch: {type: string, pattern: '^agent/'}
|
||||
generation: {type: integer, minimum: 1}
|
||||
lease_seconds: {type: integer, minimum: 1, maximum: 86400}
|
||||
AuthorizedTaskWriterProvisionRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
@@ -90,6 +104,20 @@ components:
|
||||
- production_marker_write
|
||||
- history_rewrite
|
||||
|
||||
ControlPlaneSnapshot:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [version, runtime, repository, production, task, capabilities, credential_broker, secret_material_exposed]
|
||||
properties:
|
||||
version: {type: object}
|
||||
runtime: {type: object}
|
||||
repository: {type: object}
|
||||
production: {type: object}
|
||||
task: {type: object}
|
||||
capabilities: {type: object}
|
||||
credential_broker: {type: object}
|
||||
secret_material_exposed: {type: boolean, const: false}
|
||||
|
||||
paths:
|
||||
/api/task-writer/provision-authorized:
|
||||
post:
|
||||
@@ -119,3 +147,10 @@ paths:
|
||||
description: Base drift, branch conflict, or idempotency scope conflict
|
||||
'503':
|
||||
description: Signer, validator, provisioner, or attestation unavailable
|
||||
|
||||
/v2/control-plane-snapshot:
|
||||
get:
|
||||
operationId: getControlPlaneSnapshot
|
||||
responses:
|
||||
'200':
|
||||
description: Non-sensitive control-plane snapshot
|
||||
|
||||
@@ -567,3 +567,53 @@ paths:
|
||||
'200': {description: Safe GitOps plan}
|
||||
'401': {description: Unauthorized}
|
||||
x-openai-isConsequential: true
|
||||
/api/v2/governed-capabilities:
|
||||
get:
|
||||
operationId: getGovernedWriteCapabilities
|
||||
responses: {'200': {description: Governed write capabilities with legacy writes disabled}}
|
||||
/api/v2/governed-task:
|
||||
post:
|
||||
operationId: createGovernedTask
|
||||
requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [task_id, repository, base_oid, target_branch, risk, description, idempotency_key], properties: {task_id: {type: string}, repository: {type: string, const: aios/aios}, base_oid: {type: string, pattern: '^[0-9a-f]{40}$'}, target_branch: {type: string, pattern: '^agent/'}, risk: {type: string}, description: {type: string}, idempotency_key: {type: string}}}}}}
|
||||
responses: {'200': {description: Exact governed task binding}}
|
||||
x-openai-isConsequential: true
|
||||
/api/v2/writer-lease:
|
||||
post:
|
||||
operationId: acquireWriterLease
|
||||
requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [schema, task_id, repository, branch_mode, base_oid, expected_branch_oid, target_branch, generation, lease_seconds], properties: {schema: {type: string}, task_id: {type: string}, repository: {type: string, const: aios/aios}, branch_mode: {type: string, enum: [CREATE_NEW, CONTINUE_EXISTING]}, base_oid: {type: string, pattern: '^[0-9a-f]{40}$'}, expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}, target_branch: {type: string, pattern: '^agent/'}, generation: {type: integer, minimum: 1}, lease_seconds: {type: integer, minimum: 1, maximum: 86400}}}}}}
|
||||
responses: {'200': {description: Detached CONTINUE_EXISTING lease}}
|
||||
x-openai-isConsequential: true
|
||||
/api/v2/commit-file:
|
||||
post:
|
||||
operationId: commitFileGoverned
|
||||
requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [task_id, repository, expected_branch_oid, target_branch, generation, idempotency_key, path, content_base64, message], properties: {task_id: {type: string}, repository: {type: string, const: aios/aios}, expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}, target_branch: {type: string, pattern: '^agent/'}, generation: {type: integer, minimum: 1}, idempotency_key: {type: string}, path: {type: string}, content_base64: {type: string}, message: {type: string}}}}}}
|
||||
responses: {'202': {description: Durable governed Job}}
|
||||
x-openai-isConsequential: true
|
||||
/api/v2/commit-files:
|
||||
post:
|
||||
operationId: commitFilesGoverned
|
||||
requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [task_id, repository, expected_branch_oid, target_branch, generation, idempotency_key, files, message], properties: {task_id: {type: string}, repository: {type: string, const: aios/aios}, expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}, target_branch: {type: string, pattern: '^agent/'}, generation: {type: integer, minimum: 1}, idempotency_key: {type: string}, files: {type: object, additionalProperties: {type: string}}, message: {type: string}}}}}}
|
||||
responses: {'202': {description: One atomic multi-file governed Job}}
|
||||
x-openai-isConsequential: true
|
||||
/api/v2/job:
|
||||
post:
|
||||
operationId: getGitOpsJob
|
||||
requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [job_id], properties: {job_id: {type: string}}}}}}
|
||||
responses: {'200': {description: Non-sensitive Job metadata}}
|
||||
/api/v2/job/resume:
|
||||
post:
|
||||
operationId: resumeGitOpsJob
|
||||
requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [job_id, expected_revision], properties: {job_id: {type: string}, expected_revision: {type: integer, minimum: 0}}}}}}
|
||||
responses: {'202': {description: Revision-CAS resume}}
|
||||
x-openai-isConsequential: true
|
||||
/api/v2/job/reconcile:
|
||||
post:
|
||||
operationId: reconcileGitOpsJob
|
||||
requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [job_id, expected_revision], properties: {job_id: {type: string}, expected_revision: {type: integer, minimum: 0}}}}}}
|
||||
responses: {'202': {description: Observation-first reconciliation}}
|
||||
x-openai-isConsequential: true
|
||||
|
||||
/v2/control-plane-snapshot:
|
||||
get:
|
||||
operationId: getControlPlaneSnapshot
|
||||
responses: {'200': {description: Non-sensitive control-plane snapshot}}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
addopts = -ra --import-mode=importlib
|
||||
@@ -1,243 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,254 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"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,2 @@
|
||||
bcd2be5e87cc55610bacb80dd69b65749b6549e246d28698b8afa86b4bd9cd36 reports/V2R2C1-production-release-evidence-closure-20260809.json
|
||||
1d78fcfd0ee4486859b41d1e925b5d34a8e6292cbf2a4db33bc0efdf7c10550e reports/V2R2C1-production-release-evidence-closure-20260809.md
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"created_at": "2026-08-09T07:41:55Z",
|
||||
"engineering": {
|
||||
"authority_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
|
||||
"commit_visible": true,
|
||||
"final_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
|
||||
"gitea_api_branch_collection": "GET /api/v1/repos/aios/aios/branches?page=1&limit=50 exact-name match",
|
||||
"gitea_api_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
|
||||
"gitea_api_tag_collection": "GET /api/v1/repos/aios/aios/tags?page=1&limit=50 exact-name absence",
|
||||
"gitea_git_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
|
||||
"gitea_git_readback": "PASS",
|
||||
"target_is_ancestor": true
|
||||
},
|
||||
"engineering_branch": "agent/p0-bridge-v21-durable-reconciliation-20260809",
|
||||
"evidence_closure_scope": "engineering_and_staging_acceptance_only",
|
||||
"governance": {
|
||||
"direct_main_push": false,
|
||||
"direct_ref_write": false,
|
||||
"force_push_used": false,
|
||||
"history_rewritten": false,
|
||||
"owner_approval_respected": true,
|
||||
"production_changed": false,
|
||||
"production_restarted": false,
|
||||
"secrets_committed": false,
|
||||
"tag_rewritten": false
|
||||
},
|
||||
"production_baseline_modified": false,
|
||||
"production_deployed": false,
|
||||
"production_precondition": {
|
||||
"baseline_version": "V2R1C7",
|
||||
"main_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
|
||||
"production_release_authorized": false,
|
||||
"restarted": false,
|
||||
"target_oid": "c7adece1cc8beffe7c718d53c775898b45d6c7f8",
|
||||
"unchanged": true,
|
||||
"v2r2c1_registry_present": false,
|
||||
"v2r2c1_tag_present": false
|
||||
},
|
||||
"production_release_authorized": false,
|
||||
"production_tag_created": false,
|
||||
"schema": "aios.production-release-evidence-closure.v1",
|
||||
"staging": {
|
||||
"artifact_path": "/opt/aios/gitops-control-plane/998d30304787e31e5ae607c0bd86b5edb2b0e5ad/bridge-v2-staging.pyz",
|
||||
"artifact_sha256": "f6a98893da14320cbca9ee4f25702a8d986ac2e1795f3cadb3ec5bf76e90da8d",
|
||||
"deployment_state": "STAGING",
|
||||
"getControlPlaneSnapshot": "PASS",
|
||||
"getGovernedWriteCapabilities": "PASS",
|
||||
"getRuntimeStatus": "PASS",
|
||||
"manifest_drift": "PASS",
|
||||
"restart_recovery": "PASS",
|
||||
"runtime_release_commit": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
|
||||
"runtime_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
|
||||
"secret_material_exposed": false
|
||||
},
|
||||
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
|
||||
"tests": {
|
||||
"full_repo_pytest_status": "COLLECTION_BLOCKED_BY_EXISTING_ENTRYPOINT_MAPPING_DEBT",
|
||||
"known_collection_debt": [
|
||||
"aios_autonomous_release_candidate",
|
||||
"active_runtime"
|
||||
],
|
||||
"mandatory_suite_failed": 0,
|
||||
"mandatory_suite_passed": 59,
|
||||
"mandatory_suite_result": "59 passed / 0 failed"
|
||||
},
|
||||
"version": "V2R2C1"
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
# V2R2C1 Production Release Evidence Closure
|
||||
|
||||
- Schema: `aios.production-release-evidence-closure.v1`
|
||||
- Created: `2026-08-09T07:41:55Z`
|
||||
- Scope: engineering and staging acceptance only
|
||||
|
||||
## Exact engineering binding
|
||||
|
||||
- Version: `V2R2C1`
|
||||
- Target OID: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
|
||||
- Engineering branch: `agent/p0-bridge-v21-durable-reconciliation-20260809`
|
||||
- Authority branch OID: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
|
||||
- Gitea Git branch OID: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
|
||||
- Gitea API branch OID: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
|
||||
- `commit_visible=true`
|
||||
- `target_is_ancestor=true`
|
||||
- Gitea Git readback: `PASS`
|
||||
- Gitea API slash-branch proof: collection endpoint with exact-name match, `PASS`
|
||||
|
||||
## Staging acceptance
|
||||
|
||||
- Artifact: `/opt/aios/gitops-control-plane/998d30304787e31e5ae607c0bd86b5edb2b0e5ad/bridge-v2-staging.pyz`
|
||||
- Artifact SHA-256: `f6a98893da14320cbca9ee4f25702a8d986ac2e1795f3cadb3ec5bf76e90da8d`
|
||||
- `deployment_state=STAGING`
|
||||
- Manifest drift: `PASS`
|
||||
- Runtime target OID: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
|
||||
- Runtime release commit: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
|
||||
- `getRuntimeStatus`: `PASS`
|
||||
- `getGovernedWriteCapabilities`: `PASS`
|
||||
- `getControlPlaneSnapshot`: `PASS`
|
||||
- Restart recovery: `PASS`
|
||||
- `secret_material_exposed=false`
|
||||
|
||||
## Tests
|
||||
|
||||
- Mandatory V2R2C1 suite: `59 passed / 0 failed`
|
||||
- Whole-repository pytest: `COLLECTION_BLOCKED_BY_EXISTING_ENTRYPOINT_MAPPING_DEBT`
|
||||
- Known collection debt: `aios_autonomous_release_candidate`, `active_runtime`
|
||||
|
||||
The whole-repository collection debt is retained explicitly and is not reported as a full-suite pass.
|
||||
|
||||
## Production precondition and freeze
|
||||
|
||||
- Production Baseline: `V2R1C7`
|
||||
- Production target OID: `c7adece1cc8beffe7c718d53c775898b45d6c7f8`
|
||||
- Production main OID: `2e8fa22989808a448065d1d76b5b18948774a637`
|
||||
- V2R2C1 production Tag: absent
|
||||
- V2R2C1 production Registry registration: absent
|
||||
- Production changed: `false`
|
||||
- Production restarted: `false`
|
||||
|
||||
This commit is evidence metadata only. It does not authorize or perform production Tag creation, production deployment, Baseline modification, Registry modification, main merge/push, or production restart.
|
||||
|
||||
## Governance
|
||||
|
||||
```yaml
|
||||
force_push_used: false
|
||||
tag_rewritten: false
|
||||
direct_main_push: false
|
||||
direct_ref_write: false
|
||||
secrets_committed: false
|
||||
history_rewritten: false
|
||||
production_restarted: false
|
||||
production_changed: false
|
||||
owner_approval_respected: true
|
||||
production_release_authorized: false
|
||||
production_tag_created: false
|
||||
production_baseline_modified: false
|
||||
production_deployed: false
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"schema": "aios.bridge-v2-engineering-staging-acceptance.v1",
|
||||
"task_id": "TASK-P0-CHATGPT-BRIDGE-V2-CONTROL-PLANE-CONVERGENCE-20260809",
|
||||
"repository": "aios/aios",
|
||||
"branch": "agent/p0-chatgpt-bridge-v2-control-plane-convergence-20260809",
|
||||
"control_plane_release_version": "bridge-v2-aa0c37b8ab33",
|
||||
"control_plane_target_oid": "aa0c37b8ab333616e1bab9686cf6d10557f2ebda",
|
||||
"artifact_sha256": "08670c78cf760bfe693e3553e676eb751dadd7834116aa6d40e7c094f1357ae4",
|
||||
"runtime_path": "/opt/aios/gitops-control-plane/aa0c37b8ab333616e1bab9686cf6d10557f2ebda",
|
||||
"bridge_schema_version": "aios.chatgpt-gitops-bridge.v2",
|
||||
"deployment_state": "STAGING",
|
||||
"drift_status": "PASS",
|
||||
"restart_acceptance": {
|
||||
"pid_before": 3158850,
|
||||
"pid_after": 3159889,
|
||||
"runtime_response_equal": true
|
||||
},
|
||||
"governed_capabilities": {
|
||||
"legacy_writes_enabled": false,
|
||||
"governed_writes_enabled": true,
|
||||
"force_push": false,
|
||||
"main_write": false,
|
||||
"tag_write": false,
|
||||
"baseline_write": false,
|
||||
"production_write": false
|
||||
},
|
||||
"production_invariants": {
|
||||
"authority_main_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
|
||||
"authority_tags_sha256": "bd2b6e30b76f28e7fcdcb447e7296690f0a65ad855209294c36b0f3f1d96098d",
|
||||
"baseline_current_sha256": "57651eb819584ab7c1d67f01b0ef66d6408cdc322ae5f6d4ff0aacd96f8327e1",
|
||||
"production_service_main_pid": 2816107,
|
||||
"production_service_nrestarts": 0,
|
||||
"production_service_active_enter_timestamp_monotonic": 2919386492273
|
||||
},
|
||||
"bridge_operation_sequence": {
|
||||
"createGovernedTask": "PASS",
|
||||
"acquireWriterLease": "PASS",
|
||||
"commitFilesGoverned": "REAL_GOVERNED_COMMIT",
|
||||
"getGitOpsJob": "PASS",
|
||||
"expected_branch_oid": "8fe3b85c1be592326473e81a502a23eeb9511d30",
|
||||
"generation": 7
|
||||
},
|
||||
"production_changed": false
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schema": "aios.bridge-v2.1-durable-reconciliation.bootstrap-acceptance.v1",
|
||||
"task_id": "TASK-P0-BRIDGE-V21-DURABLE-RECONCILIATION-20260809",
|
||||
"repository": "aios/aios",
|
||||
"base_oid": "3f670c387692b8865a10d5b419468d8a0b64d7a4",
|
||||
"target_branch": "agent/p0-bridge-v21-durable-reconciliation-20260809",
|
||||
"branch_mode": "CREATE_NEW",
|
||||
"generation": 1,
|
||||
"implementation": "not_started",
|
||||
"production_changed": false,
|
||||
"required_proofs": [
|
||||
"absent_ref_cas",
|
||||
"observation_first_reconciliation",
|
||||
"authority_gitea_api_commit_equality",
|
||||
"commit_visible"
|
||||
]
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
#!/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())
|
||||
@@ -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"^V[0-9]+R[0-9]+C[0-9]+(?:-[A-Za-z0-9_.-]+)?$")
|
||||
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]+$")
|
||||
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 {"V2R1C7","82792347937c0df59a8e02d6162775252bcad2e2"}: raise ManifestValidationError("PROTECTED_TAG","previous production tag binding changed")
|
||||
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")
|
||||
_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() == "V2R1C7"
|
||||
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "V2R1C6"
|
||||
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,19 +7,17 @@ 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(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"]
|
||||
result = health_payload()
|
||||
assert result == {
|
||||
"status": "ok",
|
||||
"version": "V2R1C6",
|
||||
"production_deployed": False,
|
||||
"gitops_writes_enabled": False,
|
||||
}
|
||||
|
||||
|
||||
def test_listener_rejects_non_loopback() -> None:
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
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",
|
||||
)
|
||||
@@ -1,90 +0,0 @@
|
||||
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/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,90 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from aios_chatgpt_bridge_v2 import (
|
||||
BRIDGE_OPERATIONS,
|
||||
BRIDGE_SCHEMA_VERSION,
|
||||
OPERATION_REQUIRED_FIELDS,
|
||||
BridgeV2Adapter,
|
||||
ControlPlaneReleaseManifest,
|
||||
RuntimeDriftError,
|
||||
)
|
||||
|
||||
|
||||
ROOT = Path(__file__).parents[1]
|
||||
|
||||
|
||||
def test_immutable_release_identity_accepts_exact_artifact_and_rejects_workspace_drift():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
approved = Path(directory) / "releases"
|
||||
runtime = approved / "release-1"
|
||||
runtime.mkdir(parents=True)
|
||||
artifact = runtime / "bridge.pyz"
|
||||
artifact.write_bytes(b"bridge-v2")
|
||||
digest = hashlib.sha256(artifact.read_bytes()).hexdigest()
|
||||
manifest = ControlPlaneReleaseManifest(
|
||||
"bridge-v2-test", "a" * 40, digest, runtime, BRIDGE_SCHEMA_VERSION,
|
||||
"aios/aios", Path("/data/aios-gitops/repo.git"), "STAGING",
|
||||
)
|
||||
assert manifest.validate(artifact, observed_oid="a" * 40, approved_root=approved)["drift_status"] == "PASS"
|
||||
with pytest.raises(RuntimeDriftError, match="CONTROL_PLANE_RUNTIME_DRIFT"):
|
||||
manifest.validate(artifact, observed_oid="b" * 40, approved_root=approved)
|
||||
mutable = ControlPlaneReleaseManifest(
|
||||
"bad", "a" * 40, digest, Path("/var/lib/worktrees/dev"), BRIDGE_SCHEMA_VERSION,
|
||||
"aios/aios", Path("/data/aios-gitops/repo.git"), "STAGING",
|
||||
)
|
||||
with pytest.raises(RuntimeDriftError, match="CONTROL_PLANE_RUNTIME_DRIFT"):
|
||||
mutable.validate(artifact, observed_oid="a" * 40, approved_root=approved)
|
||||
|
||||
|
||||
def test_bridge_capabilities_keep_legacy_closed_and_expose_exact_operations():
|
||||
adapter = BridgeV2Adapter(runtime_status={"drift_status": "PASS"})
|
||||
assert adapter.getRuntimeStatus()["bridge_schema_version"] == BRIDGE_SCHEMA_VERSION
|
||||
capabilities = adapter.getGovernedWriteCapabilities()
|
||||
assert capabilities["legacy_writes_enabled"] is False
|
||||
assert capabilities["governed_writes_enabled"] is True
|
||||
assert tuple(capabilities["operations"]) == BRIDGE_OPERATIONS
|
||||
assert adapter.legacy_write_disabled() == {
|
||||
"error": "LEGACY_WRITE_DISABLED",
|
||||
"replacement": "createGovernedTask",
|
||||
"governed_writes_available": True,
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_runtime_and_fragment_required_fields_are_exactly_converged():
|
||||
canonical = yaml.safe_load((ROOT / "docs/gitops/openapi-chatgpt-actions.yaml").read_text(encoding="utf-8"))
|
||||
fragment = yaml.safe_load((ROOT / "docs/gitops/openapi-authorized-task-writer-fragment.yaml").read_text(encoding="utf-8"))
|
||||
operations = {}
|
||||
for path, methods in canonical["paths"].items():
|
||||
for method, value in methods.items():
|
||||
if isinstance(value, dict) and value.get("operationId") in BRIDGE_OPERATIONS:
|
||||
operations[value["operationId"]] = value
|
||||
assert set(operations) == set(BRIDGE_OPERATIONS)
|
||||
for operation, required in OPERATION_REQUIRED_FIELDS.items():
|
||||
if "requestBody" not in operations[operation]:
|
||||
assert required == ()
|
||||
continue
|
||||
schema = operations[operation]["requestBody"]["content"]["application/json"]["schema"]
|
||||
assert tuple(schema["required"]) == required
|
||||
fragment_required = tuple(fragment["components"]["schemas"]["WriterLeaseRequestV2"]["required"])
|
||||
assert fragment_required == OPERATION_REQUIRED_FIELDS["acquireWriterLease"]
|
||||
|
||||
|
||||
def test_release_manifest_round_trip_contains_only_declared_identity_fields():
|
||||
manifest = ControlPlaneReleaseManifest(
|
||||
"v", "a" * 40, "b" * 64, Path("/opt/aios/gitops-control-plane/v"), BRIDGE_SCHEMA_VERSION,
|
||||
"aios/aios", Path("/data/aios-gitops/repo.git"), "STAGING",
|
||||
)
|
||||
value = manifest.as_dict()
|
||||
assert tuple(value) == (
|
||||
"control_plane_release_version", "control_plane_target_oid", "artifact_sha256", "runtime_path",
|
||||
"bridge_schema_version", "repository_identity", "authoritative_git_dir", "deployment_state",
|
||||
)
|
||||
assert "credential" not in json.dumps(value).lower()
|
||||
@@ -0,0 +1,283 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
from aios_continuation_executor import (
|
||||
ContinuationCommit,
|
||||
ContinuationExecutionError,
|
||||
ContinuationExecutor,
|
||||
ContinuationPermissions,
|
||||
ContinuationRuntimeBinding,
|
||||
DetachedGitWorkspace,
|
||||
GiteaContinuationRemote,
|
||||
)
|
||||
from aios_continuation_lease import WriterLeaseRequestV2
|
||||
from aios_gitops_job_controller import BindingConflict, GitOpsJobController, JobState, JobStore
|
||||
|
||||
|
||||
TASK = "TASK-P0-CHATGPT-BRIDGE-V2-CONTROL-PLANE-CONVERGENCE-20260809"
|
||||
REPOSITORY = "aios/aios"
|
||||
BRANCH = "agent/p0-chatgpt-bridge-v2-control-plane-convergence-20260809"
|
||||
BASE = "2" * 40
|
||||
HEAD = "8" * 40
|
||||
NEW = "9" * 40
|
||||
NOW = datetime(2026, 8, 9, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
class Authority:
|
||||
def __init__(self, *, head=HEAD, exists=True, descendant=True):
|
||||
self.head = head
|
||||
self.exists = exists
|
||||
self.descendant = descendant
|
||||
self.cas_calls = []
|
||||
|
||||
def branch_exists(self, branch):
|
||||
return self.exists
|
||||
|
||||
def branch_head(self, branch):
|
||||
return self.head
|
||||
|
||||
def is_ancestor(self, older, newer):
|
||||
if (older, newer) == (BASE, HEAD):
|
||||
return True
|
||||
return self.descendant and (older, newer) in {(HEAD, NEW), (BASE, NEW)}
|
||||
|
||||
def compare_and_swap_branch(self, branch, expected, new):
|
||||
self.cas_calls.append((branch, expected, new))
|
||||
if self.head != expected:
|
||||
raise RuntimeError("stale")
|
||||
self.head = new
|
||||
return new
|
||||
|
||||
|
||||
class Workspace:
|
||||
def __init__(self, *, detached=True, head=HEAD):
|
||||
self.detached = detached
|
||||
self.head = head
|
||||
self.commits = 0
|
||||
|
||||
def is_detached(self):
|
||||
return self.detached
|
||||
|
||||
def head_oid(self):
|
||||
return self.head
|
||||
|
||||
def commit_files(self, files, message):
|
||||
self.commits += 1
|
||||
self.head = NEW
|
||||
return NEW
|
||||
|
||||
|
||||
class Remote:
|
||||
def __init__(self, *, git=HEAD, api=HEAD, visible=True, repository=REPOSITORY, fail_after_push=False, omit_after_push=False):
|
||||
self.git = git
|
||||
self.api = api
|
||||
self.visible = visible
|
||||
self.repository = repository
|
||||
self.fail_after_push = fail_after_push
|
||||
self.omit_after_push = omit_after_push
|
||||
self.pushes = 0
|
||||
|
||||
def repository_identity(self):
|
||||
return self.repository
|
||||
|
||||
def push_exact(self, branch, expected, new):
|
||||
self.pushes += 1
|
||||
if self.git is None:
|
||||
raise RuntimeError("remote branch missing")
|
||||
if self.git != expected:
|
||||
raise RuntimeError("remote conflict")
|
||||
if not self.omit_after_push:
|
||||
self.git = self.api = new
|
||||
else:
|
||||
self.git = self.api = None
|
||||
if self.fail_after_push:
|
||||
raise TimeoutError("response lost")
|
||||
|
||||
def read_git_ref(self, branch):
|
||||
return self.git
|
||||
|
||||
def read_api_branch(self, branch):
|
||||
return self.api
|
||||
|
||||
def commit_visible(self, oid):
|
||||
return self.visible and self.api == oid
|
||||
|
||||
|
||||
class Credential:
|
||||
def __init__(self):
|
||||
self.projected = 0
|
||||
self.revoked = 0
|
||||
|
||||
def project(self):
|
||||
self.projected += 1
|
||||
|
||||
def revoke(self):
|
||||
self.revoked += 1
|
||||
|
||||
|
||||
def lease_request(**changes):
|
||||
values = dict(task_id=TASK, repository=REPOSITORY, branch_mode="CONTINUE_EXISTING", base_oid=BASE,
|
||||
expected_branch_oid=HEAD, target_branch=BRANCH, generation=1, lease_seconds=900)
|
||||
values.update(changes)
|
||||
return WriterLeaseRequestV2(**values)
|
||||
|
||||
|
||||
def binding(**changes):
|
||||
values = dict(
|
||||
request=lease_request(),
|
||||
workspace_path=Path("/var/lib/aios-task-writer/continuation-worktrees/exact"),
|
||||
lease_not_before=NOW - timedelta(seconds=1),
|
||||
lease_expires_at=NOW + timedelta(minutes=15),
|
||||
original_receipt_valid=True,
|
||||
original_grant_consumed=True,
|
||||
permissions=ContinuationPermissions(),
|
||||
)
|
||||
values.update(changes)
|
||||
return ContinuationRuntimeBinding(**values)
|
||||
|
||||
|
||||
def commit(**changes):
|
||||
values = dict(idempotency_key="first-continuation", files={"active-runtime/app/new.py": b"ok\n"},
|
||||
message="feat(gitops): add governed continuation executor")
|
||||
values.update(changes)
|
||||
return ContinuationCommit(**values)
|
||||
|
||||
|
||||
def make_executor(authority=None, workspace=None, remote=None, credential=None):
|
||||
temp = tempfile.TemporaryDirectory()
|
||||
controller = GitOpsJobController(JobStore(Path(temp.name).resolve()))
|
||||
executor = ContinuationExecutor(
|
||||
controller, authority or Authority(), workspace or Workspace(), remote or Remote(), credential or Credential(),
|
||||
now=lambda: NOW,
|
||||
)
|
||||
executor._test_temp = temp
|
||||
return executor
|
||||
|
||||
|
||||
def test_exact_existing_branch_completes_only_after_all_remote_proofs():
|
||||
authority, remote, credential = Authority(), Remote(), Credential()
|
||||
result = make_executor(authority, remote=remote, credential=credential).execute(binding(), commit())
|
||||
assert result["state"] == JobState.COMPLETED.value
|
||||
assert result["receipt"]["authority_oid"] == result["receipt"]["git_remote_oid"] == NEW
|
||||
assert result["receipt"]["api_branch_oid"] == NEW
|
||||
assert result["receipt"]["commit_visible"] is True
|
||||
assert authority.cas_calls == [(BRANCH, HEAD, NEW)]
|
||||
assert credential.projected == credential.revoked == 1
|
||||
assert "credential" not in repr(result).lower()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"runtime_binding,authority,workspace,error",
|
||||
[
|
||||
(binding(request=lease_request(expected_branch_oid="7" * 40)), Authority(), Workspace(), "EXISTING_BRANCH_OID_CONFLICT"),
|
||||
(binding(), Authority(exists=False), Workspace(), "EXISTING_BRANCH_MISSING"),
|
||||
(binding(), Authority(), Workspace(detached=False), "WORKTREE_NOT_DETACHED"),
|
||||
(binding(), Authority(), Workspace(head="7" * 40), "WORKTREE_HEAD_MISMATCH"),
|
||||
(binding(lease_expires_at=NOW), Authority(), Workspace(), "LEASE_EXPIRED"),
|
||||
(binding(request=lease_request(repository="other/repo")), Authority(), Workspace(), "request_identity_invalid"),
|
||||
(binding(request=lease_request(target_branch="main")), Authority(), Workspace(), "PROTECTED_BRANCH_REJECTED"),
|
||||
(binding(request=lease_request(generation=1)), Authority(), Workspace(), "GENERATION_NOT_MONOTONIC"),
|
||||
],
|
||||
)
|
||||
def test_pre_side_effect_failures_never_create_or_update_a_branch(runtime_binding, authority, workspace, error):
|
||||
executor = make_executor(authority, workspace=workspace)
|
||||
if error == "GENERATION_NOT_MONOTONIC":
|
||||
executor.observe_generation(BRANCH, 1)
|
||||
with pytest.raises(Exception, match=error):
|
||||
executor.execute(runtime_binding, commit())
|
||||
assert authority.cas_calls == []
|
||||
assert workspace.commits == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field", ["create_target_branch", "force_push", "tag_write", "baseline_write", "production_write", "main_write"])
|
||||
def test_forbidden_capabilities_fail_before_credential_projection(field):
|
||||
credential = Credential()
|
||||
permissions = ContinuationPermissions(**{field: True})
|
||||
with pytest.raises(ContinuationExecutionError, match="MUTATION_PERMISSION_DENIED"):
|
||||
make_executor(credential=credential).execute(binding(permissions=permissions), commit())
|
||||
assert credential.projected == 0
|
||||
|
||||
|
||||
def test_non_fast_forward_and_concurrent_movement_are_rejected_cas_only():
|
||||
authority = Authority(descendant=False)
|
||||
with pytest.raises(ContinuationExecutionError, match="NON_FAST_FORWARD_COMMIT"):
|
||||
make_executor(authority).execute(binding(), commit())
|
||||
assert authority.cas_calls == []
|
||||
|
||||
|
||||
def test_authority_success_and_gitea_missing_is_never_completed():
|
||||
remote = Remote(omit_after_push=True)
|
||||
result = make_executor(remote=remote).execute(binding(), commit())
|
||||
assert result["state"] == JobState.RECONCILIATION_REQUIRED.value
|
||||
assert result["side_effect_state"] == "REMOTE_PUSH_PENDING"
|
||||
assert result["state"] not in {JobState.REMOTE_VERIFIED.value, JobState.COMPLETED.value}
|
||||
|
||||
|
||||
def test_response_loss_after_remote_success_reconciles_by_observation_without_second_push():
|
||||
remote = Remote(fail_after_push=True)
|
||||
executor = make_executor(remote=remote)
|
||||
first = executor.execute(binding(), commit())
|
||||
assert first["state"] == JobState.RECONCILIATION_REQUIRED.value
|
||||
assert remote.pushes == 1
|
||||
recovered = executor.reconcile(first["job_id"])
|
||||
assert recovered["state"] == JobState.COMPLETED.value
|
||||
assert remote.pushes == 1
|
||||
|
||||
|
||||
def test_observation_first_reconcile_retries_only_when_remote_is_exact_expected_old_oid():
|
||||
remote = Remote(fail_after_push=True)
|
||||
executor = make_executor(remote=remote)
|
||||
first = executor.execute(binding(), commit())
|
||||
assert first["state"] == JobState.RECONCILIATION_REQUIRED.value
|
||||
remote.git = remote.api = HEAD
|
||||
remote.visible = True
|
||||
remote.fail_after_push = False
|
||||
recovered = executor.reconcile(first["job_id"])
|
||||
assert recovered["state"] == JobState.COMPLETED.value
|
||||
assert remote.pushes == 2
|
||||
|
||||
|
||||
def test_duplicate_idempotency_replay_and_conflicting_payload():
|
||||
executor = make_executor()
|
||||
first = executor.execute(binding(), commit())
|
||||
replay = executor.execute(binding(), commit())
|
||||
assert replay["job_id"] == first["job_id"]
|
||||
with pytest.raises(BindingConflict, match="BINDING_CONFLICT"):
|
||||
executor.execute(binding(), commit(files={"active-runtime/app/new.py": b"changed\n"}))
|
||||
|
||||
|
||||
def test_secret_like_payload_fails_before_projection():
|
||||
credential = Credential()
|
||||
secret_fixture = b"to" + b"ken=" + b"ghp_" + b"abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
with pytest.raises(ContinuationExecutionError, match="SECRET_LIKE_PAYLOAD_REJECTED"):
|
||||
make_executor(credential=credential).execute(binding(), commit(files={"x": secret_fixture}))
|
||||
assert credential.projected == 0
|
||||
|
||||
|
||||
def test_remote_identity_cannot_alias_the_authority_bare_repo():
|
||||
with pytest.raises(ContinuationExecutionError, match="GITEA_REMOTE_IDENTITY_INVALID"):
|
||||
GiteaContinuationRemote.validate_remote_url("/data/aios-gitops/repo.git")
|
||||
GiteaContinuationRemote.validate_remote_url("aiman@g.cnnetmkt.top:aios/aios.git")
|
||||
|
||||
|
||||
def test_detached_workspace_preserves_porcelain_leading_status_column():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
subprocess.run(["git", "init", "-q", str(root)], check=True)
|
||||
subprocess.run(["git", "-C", str(root), "config", "user.name", "test"], check=True)
|
||||
subprocess.run(["git", "-C", str(root), "config", "user.email", "test@localhost"], check=True)
|
||||
(root / "tracked.py").write_text("one\n", encoding="utf-8")
|
||||
subprocess.run(["git", "-C", str(root), "add", "tracked.py"], check=True)
|
||||
subprocess.run(["git", "-C", str(root), "commit", "-q", "-m", "base"], check=True)
|
||||
subprocess.run(["git", "-C", str(root), "checkout", "-q", "--detach"], check=True)
|
||||
(root / "tracked.py").write_text("two\n", encoding="utf-8")
|
||||
(root / "new.py").write_text("new\n", encoding="utf-8")
|
||||
workspace = DetachedGitWorkspace(root)
|
||||
assert workspace.is_detached()
|
||||
assert workspace._dirty_paths() == {"tracked.py", "new.py"}
|
||||
@@ -250,7 +250,7 @@ class GovernedLocalWriterTests(unittest.TestCase):
|
||||
}
|
||||
self.assertEqual(service.process_request(secret_request, peer)["status"], "error")
|
||||
|
||||
def test_idempotent_replay_returns_receipt(self) -> None:
|
||||
def test_one_shot_consumption(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"], "receipt")
|
||||
self.assertEqual(len(sink.records), 2)
|
||||
self.assertEqual(service.process_request(append_request, peer)["status"], "error")
|
||||
self.assertEqual(len(sink.records), 3)
|
||||
self.assertEqual(len(handler.create_calls), 1)
|
||||
self.assertEqual(len(handler.append_calls), 1)
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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_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
|
||||
@@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from aios_control_plane_snapshot import build_control_plane_snapshot
|
||||
from aios_credential_broker import CredentialBroker
|
||||
from aios_gitops_reconciliation_core import ReconciliationAction, RemoteObservation, decide_reconciliation
|
||||
from aios_governed_policy import GovernedPolicyInput, evaluate_policy
|
||||
|
||||
|
||||
OID = "a" * 40
|
||||
OLD = "b" * 40
|
||||
|
||||
|
||||
def test_authority_desired_remote_missing_never_completes():
|
||||
decision = decide_reconciliation(RemoteObservation("aios/aios", "agent/example", OID, OID, None, None, False, "now", OLD))
|
||||
assert decision.action == ReconciliationAction.MARK_RECONCILIATION_REQUIRED
|
||||
assert decision.state != "COMPLETED"
|
||||
|
||||
|
||||
def test_response_loss_observes_desired_remote_before_retry():
|
||||
decision = decide_reconciliation(RemoteObservation("aios/aios", "agent/example", OID, OID, OID, OID, True, "now", OLD))
|
||||
assert decision.action == ReconciliationAction.MARK_REMOTE_VERIFIED
|
||||
|
||||
|
||||
def test_conflicting_remote_fails_closed():
|
||||
decision = decide_reconciliation(RemoteObservation("aios/aios", "agent/example", OID, OID, "c" * 40, "c" * 40, True, "now", OLD))
|
||||
assert decision.action == ReconciliationAction.FAIL_CONFLICT
|
||||
|
||||
|
||||
def test_policy_denies_production_and_force_capabilities():
|
||||
request = GovernedPolicyInput("owner", "task", "aios/aios", "commitFilesGoverned", OID, OID, "agent/example", 2, requested_capabilities=("production_write", "force_push"))
|
||||
assert evaluate_policy(request).decision == "DENY"
|
||||
|
||||
|
||||
def test_credential_metadata_does_not_include_secret_and_revoke_is_idempotent():
|
||||
class Provider:
|
||||
def environment(self, lease):
|
||||
return {"CREDENTIAL_FILE": "/run/credential"}
|
||||
broker = CredentialBroker(Provider())
|
||||
metadata = broker.issue(task_id="task", repository="aios/aios", target_branch="agent/example", generation=1)
|
||||
assert "token" not in str(metadata).lower()
|
||||
revoked = broker.revoke(metadata["lease_id"])
|
||||
assert broker.revoke(metadata["lease_id"])["state"] == "REVOKED"
|
||||
assert revoked["state"] == "REVOKED"
|
||||
|
||||
|
||||
def test_snapshot_marks_secret_material_false_and_preserves_partial_observation():
|
||||
snapshot = build_control_plane_snapshot(target_version="V2R2C1", task={"gitea_git_oid": None, "gitea_api_oid": None})
|
||||
assert snapshot["secret_material_exposed"] is False
|
||||
assert snapshot["task"]["gitea_git_oid"] is None
|
||||
Reference in New Issue
Block a user