257 lines
10 KiB
Python
257 lines
10 KiB
Python
"""Exact-base governed CREATE_NEW task bootstrap using the existing JobStore."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timezone, timedelta
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any, Mapping
|
|
|
|
from aios_authority_git_executor import AuthorityGitExecutor
|
|
from aios_continuation_executor import (
|
|
AuthorityGitAdapter,
|
|
ContinuationExecutor,
|
|
ContinuationPermissions,
|
|
ContinuationRuntimeBinding,
|
|
DetachedGitWorkspace,
|
|
GiteaContinuationRemote,
|
|
ServerSideCredentialProjection,
|
|
)
|
|
from aios_continuation_lease import WriterLeaseRequestV2
|
|
from aios_gitops_job_controller import GitOpsJobController, JobStore
|
|
|
|
OID_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
|
|
|
|
|
|
class BootstrapError(RuntimeError):
|
|
pass
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class GovernedTaskBinding:
|
|
task_id: str
|
|
repository: str
|
|
base_oid: str
|
|
target_branch: str
|
|
risk: str
|
|
description: str
|
|
idempotency_key: str
|
|
job_id: str | None = None
|
|
|
|
def response(self, *, idempotent: bool = False) -> dict[str, Any]:
|
|
return {
|
|
"task_id": self.task_id,
|
|
"repository": self.repository,
|
|
"base_oid": self.base_oid,
|
|
"target_branch": self.target_branch,
|
|
"expected_branch_oid": self.base_oid,
|
|
"generation": 1,
|
|
"branch_mode": "CREATE_NEW",
|
|
"create_target_branch": True,
|
|
"worktree_mode": "detached_at_base_oid",
|
|
"cas_ref_update_required": True,
|
|
"idempotent": idempotent,
|
|
"state": "BOUND",
|
|
"job_id": self.job_id,
|
|
}
|
|
|
|
|
|
def _safe_task(value: str) -> str:
|
|
return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-")[:120] or "task"
|
|
|
|
|
|
class GovernedTaskBootstrap:
|
|
def __init__(
|
|
self,
|
|
authority: AuthorityGitExecutor,
|
|
root: Path,
|
|
*,
|
|
remote_name: str,
|
|
token_path: Path,
|
|
ssh_key_path: Path,
|
|
api_base: str = "https://g.cnnetmkt.top/git/api/v1",
|
|
) -> None:
|
|
self.authority = authority
|
|
self.root = root
|
|
self.remote_name = remote_name
|
|
self.token_path = token_path
|
|
self.ssh_key_path = ssh_key_path
|
|
self.api_base = api_base
|
|
self.task_meta_root = root / "task-bindings"
|
|
self.job_root = root / "jobs"
|
|
self.worktree_root = root / "worktrees"
|
|
self.task_meta_root.mkdir(parents=True, exist_ok=True)
|
|
self.job_root.mkdir(parents=True, exist_ok=True)
|
|
self.worktree_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
def _store(self, task_id: str) -> tuple[JobStore, GitOpsJobController]:
|
|
task_root = self.job_root / _safe_task(task_id)
|
|
store = JobStore(task_root, payload_root=task_root / "payloads")
|
|
return store, GitOpsJobController(store)
|
|
|
|
def _metadata(self, task_id: str) -> Path:
|
|
return self.task_meta_root / (_safe_task(task_id) + ".json")
|
|
|
|
def _read_existing(self, task_id: str) -> GovernedTaskBinding | None:
|
|
path = self._metadata(task_id)
|
|
if not path.is_file() or path.is_symlink():
|
|
return None
|
|
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
return GovernedTaskBinding(
|
|
task_id=raw["task_id"],
|
|
repository=raw["repository"],
|
|
base_oid=raw["base_oid"],
|
|
target_branch=raw["target_branch"],
|
|
risk=raw["risk"],
|
|
description=raw["description"],
|
|
idempotency_key=raw["idempotency_key"],
|
|
job_id=raw.get("job_id"),
|
|
)
|
|
|
|
def _write_metadata(self, binding: GovernedTaskBinding) -> None:
|
|
path = self._metadata(binding.task_id)
|
|
temporary = path.with_suffix(".tmp")
|
|
temporary.write_text(
|
|
json.dumps(
|
|
{
|
|
"task_id": binding.task_id,
|
|
"repository": binding.repository,
|
|
"base_oid": binding.base_oid,
|
|
"target_branch": binding.target_branch,
|
|
"risk": binding.risk,
|
|
"description": binding.description,
|
|
"idempotency_key": binding.idempotency_key,
|
|
"job_id": binding.job_id,
|
|
},
|
|
sort_keys=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
temporary.replace(path)
|
|
|
|
def _pending_create_new_oid(self, task_id: str, branch: str) -> str | None:
|
|
"""Return the exact local OID only for an unfinished CREATE_NEW job."""
|
|
store, _ = self._store(task_id)
|
|
with store._connect() as connection:
|
|
rows = connection.execute(
|
|
"SELECT operation,state,receipt_json FROM jobs WHERE branch=? ORDER BY updated_at DESC",
|
|
(branch,),
|
|
).fetchall()
|
|
for row in rows:
|
|
if row["operation"] != "CONTINUATION_COMMIT_FILES_CREATE_NEW" or row["state"] != "RECONCILIATION_REQUIRED":
|
|
continue
|
|
receipt = json.loads(row["receipt_json"] or "{}")
|
|
desired = receipt.get("desired_remote_oid")
|
|
if isinstance(desired, str) and OID_RE.fullmatch(desired):
|
|
return desired
|
|
return None
|
|
|
|
def create(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
values = {key: str(request[key]) for key in (
|
|
"task_id",
|
|
"repository",
|
|
"base_oid",
|
|
"target_branch",
|
|
"risk",
|
|
"description",
|
|
"idempotency_key",
|
|
)}
|
|
if values["repository"] != "aios/aios" or not OID_RE.fullmatch(values["base_oid"]):
|
|
raise BootstrapError("TASK_BINDING_INVALID")
|
|
if not BRANCH_RE.fullmatch(values["target_branch"]) or values["target_branch"] in {"main", "master"}:
|
|
raise BootstrapError("TARGET_BRANCH_INVALID")
|
|
existing = self._read_existing(values["task_id"])
|
|
if existing is not None:
|
|
for key in ("repository", "base_oid", "target_branch", "risk", "description"):
|
|
if getattr(existing, key) != values[key]:
|
|
raise BootstrapError("TASK_BINDING_CONFLICT")
|
|
return existing.response(idempotent=True)
|
|
if not self.authority.commit_exists(values["base_oid"]):
|
|
raise BootstrapError("BASE_COMMIT_NOT_FOUND")
|
|
if self.authority.read_ref("refs/heads/" + values["target_branch"]) is not None:
|
|
raise BootstrapError("CONFLICT")
|
|
payload = json.dumps(values, sort_keys=True, separators=(",", ":")).encode()
|
|
_, controller = self._store(values["task_id"])
|
|
receipt = controller.submit(
|
|
{
|
|
"idempotency_key": values["idempotency_key"],
|
|
"campaign_id": values["task_id"],
|
|
"task_id": values["task_id"],
|
|
"repository": values["repository"],
|
|
"operation": "GOVERNED_TASK_CREATE",
|
|
"branch": values["target_branch"],
|
|
"expected_branch_oid": values["base_oid"],
|
|
"payload_digest": hashlib.sha256(payload).hexdigest(),
|
|
"lineage_anchor_oid": values["base_oid"],
|
|
},
|
|
payload=payload,
|
|
)
|
|
binding = GovernedTaskBinding(**values, job_id=receipt.job_id)
|
|
self._write_metadata(binding)
|
|
return binding.response()
|
|
|
|
def prepare_runtime(self, request: Mapping[str, Any], *, lease_seconds: int):
|
|
task = self._read_existing(str(request["task_id"]))
|
|
if task is None:
|
|
raise BootstrapError("TASK_NOT_FOUND")
|
|
if str(request["repository"]) != task.repository or str(request["base_oid"]) != task.base_oid:
|
|
raise BootstrapError("TASK_BINDING_CONFLICT")
|
|
if str(request["target_branch"]) != task.target_branch:
|
|
raise BootstrapError("TASK_BINDING_CONFLICT")
|
|
if request.get("branch_mode") != "CREATE_NEW" or int(request.get("generation", 0)) != 1:
|
|
raise BootstrapError("CREATE_NEW_LEASE_INVALID")
|
|
if not self.authority.commit_exists(task.base_oid):
|
|
raise BootstrapError("BASE_COMMIT_NOT_FOUND")
|
|
existing_target = self.authority.read_ref("refs/heads/" + task.target_branch)
|
|
recovery_oid = None
|
|
if existing_target is not None:
|
|
recovery_oid = self._pending_create_new_oid(task.task_id, task.target_branch)
|
|
if recovery_oid != existing_target:
|
|
raise BootstrapError("CONFLICT")
|
|
workspace_path = self.worktree_root / _safe_task(task.task_id)
|
|
if workspace_path.exists():
|
|
if workspace_path.is_symlink():
|
|
raise BootstrapError("WORKTREE_INVALID")
|
|
workspace = DetachedGitWorkspace(workspace_path)
|
|
allowed_heads = {task.base_oid}
|
|
if recovery_oid is not None:
|
|
allowed_heads.add(recovery_oid)
|
|
if not workspace.is_detached() or workspace.head_oid() not in allowed_heads:
|
|
raise BootstrapError("WORKTREE_HEAD_MISMATCH")
|
|
else:
|
|
self.authority.create_detached_worktree(task.base_oid, _safe_task(task.task_id), self.worktree_root)
|
|
workspace = DetachedGitWorkspace(workspace_path)
|
|
request_v2 = WriterLeaseRequestV2(
|
|
task_id=task.task_id,
|
|
repository=task.repository,
|
|
branch_mode="CREATE_NEW",
|
|
base_oid=task.base_oid,
|
|
expected_branch_oid=task.base_oid,
|
|
target_branch=task.target_branch,
|
|
generation=1,
|
|
lease_seconds=lease_seconds,
|
|
)
|
|
_, controller = self._store(task.task_id)
|
|
credential = ServerSideCredentialProjection(self.ssh_key_path, self.token_path)
|
|
remote = GiteaContinuationRemote(
|
|
workspace=workspace_path,
|
|
credential=credential,
|
|
remote_name=self.remote_name,
|
|
repository=task.repository,
|
|
api_base=self.api_base,
|
|
)
|
|
executor = ContinuationExecutor(controller, AuthorityGitAdapter(self.authority), workspace, remote, credential)
|
|
binding = ContinuationRuntimeBinding(
|
|
request=request_v2,
|
|
workspace_path=workspace_path,
|
|
lease_not_before=datetime.now(timezone.utc) - timedelta(seconds=1),
|
|
lease_expires_at=datetime.now(timezone.utc) + timedelta(seconds=lease_seconds),
|
|
original_receipt_valid=True,
|
|
original_grant_consumed=True,
|
|
permissions=ContinuationPermissions(create_target_branch=True),
|
|
)
|
|
return binding, controller, executor
|