Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9d3b1cf89 |
@@ -112,27 +112,6 @@ class AuthorityGitExecutor:
|
|||||||
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
|
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
|
||||||
return readback
|
return readback
|
||||||
|
|
||||||
def create_task_branch_CAS(self, branch: str, new_oid: str) -> str:
|
|
||||||
"""Create an agent branch only if the authoritative ref is absent."""
|
|
||||||
if not TASK_BRANCH_RE.fullmatch(branch):
|
|
||||||
raise AuthorityGitError("TASK_BRANCH_SCOPE_DENIED")
|
|
||||||
_oid(new_oid, "new_oid")
|
|
||||||
current = self.read_ref(branch)
|
|
||||||
if current is not None:
|
|
||||||
if current == new_oid:
|
|
||||||
return new_oid
|
|
||||||
raise AuthorityGitError("AUTHORITY_BRANCH_ALREADY_EXISTS")
|
|
||||||
try:
|
|
||||||
self._git(["update-ref", branch, new_oid, "0" * 40])
|
|
||||||
except AuthorityGitError as exc:
|
|
||||||
if self.read_ref(branch) == new_oid:
|
|
||||||
return new_oid
|
|
||||||
raise AuthorityGitError("AUTHORITY_BRANCH_CREATE_CONFLICT") from exc
|
|
||||||
readback = self.read_ref(branch)
|
|
||||||
if readback != new_oid:
|
|
||||||
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
|
|
||||||
return readback
|
|
||||||
|
|
||||||
def ff_CAS_main_after_verified_protected_merge(self, expected_oid: str, new_oid: str) -> str:
|
def ff_CAS_main_after_verified_protected_merge(self, expected_oid: str, new_oid: str) -> str:
|
||||||
_oid(expected_oid, "expected_oid")
|
_oid(expected_oid, "expected_oid")
|
||||||
_oid(new_oid, "new_oid")
|
_oid(new_oid, "new_oid")
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class GovernedRuntime:
|
|||||||
workspace = DetachedGitWorkspace(worktree_path)
|
workspace = DetachedGitWorkspace(worktree_path)
|
||||||
if workspace.head_oid() != lease.expected_branch_oid or not workspace.is_detached(): raise RuntimeError("WORKTREE_HEAD_MISMATCH")
|
if workspace.head_oid() != lease.expected_branch_oid or not workspace.is_detached(): raise RuntimeError("WORKTREE_HEAD_MISMATCH")
|
||||||
job_root = self._job_root(lease.task_id); controller = GitOpsJobController(JobStore(job_root, payload_root=job_root / "payloads")); credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path); remote = GiteaContinuationRemote(workspace=worktree_path, credential=credential, remote_name=self.config.remote_name, repository=lease.repository, api_base=self.config.api_base); executor = ContinuationExecutor(controller, AuthorityGitAdapter(self.authority), workspace, remote, credential)
|
job_root = self._job_root(lease.task_id); controller = GitOpsJobController(JobStore(job_root, payload_root=job_root / "payloads")); credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path); remote = GiteaContinuationRemote(workspace=worktree_path, credential=credential, remote_name=self.config.remote_name, repository=lease.repository, api_base=self.config.api_base); executor = ContinuationExecutor(controller, AuthorityGitAdapter(self.authority), workspace, remote, credential)
|
||||||
binding = ContinuationRuntimeBinding(request=lease, workspace_path=worktree_path, lease_not_before=datetime.now(timezone.utc) - timedelta(seconds=1), lease_expires_at=datetime.now(timezone.utc) + timedelta(seconds=lease.lease_seconds), original_receipt_valid=True, original_grant_consumed=True, permissions=ContinuationPermissions(create_target_branch=lease.branch_mode == "CREATE_NEW"))
|
binding = ContinuationRuntimeBinding(request=lease, workspace_path=worktree_path, lease_not_before=datetime.now(timezone.utc) - timedelta(seconds=1), lease_expires_at=datetime.now(timezone.utc) + timedelta(seconds=lease.lease_seconds), original_receipt_valid=True, original_grant_consumed=True, permissions=ContinuationPermissions())
|
||||||
result = (binding, controller, executor); self.runtimes[key] = result; self.controllers[lease.task_id] = controller; return result
|
result = (binding, controller, executor); self.runtimes[key] = result; self.controllers[lease.task_id] = controller; return result
|
||||||
|
|
||||||
def create_task(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
def create_task(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
||||||
@@ -148,16 +148,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")
|
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"]))
|
commit = ContinuationCommit(idempotency_key=str(request["idempotency_key"]), files=self._files(request), message=str(request["message"]))
|
||||||
executor.workspace.materialize_files(commit.files)
|
executor.workspace.materialize_files(commit.files)
|
||||||
result = executor.execute(binding, commit)
|
return 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
|
|
||||||
|
|
||||||
def _controller_for_job(self, job_id: str) -> GitOpsJobController:
|
def _controller_for_job(self, job_id: str) -> GitOpsJobController:
|
||||||
for controller in self.controllers.values():
|
for controller in self.controllers.values():
|
||||||
|
|||||||
@@ -52,12 +52,11 @@ class AuthorityPort(Protocol):
|
|||||||
def branch_head(self, branch: str) -> str: ...
|
def branch_head(self, branch: str) -> str: ...
|
||||||
def is_ancestor(self, older: str, newer: str) -> bool: ...
|
def is_ancestor(self, older: str, newer: str) -> bool: ...
|
||||||
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
|
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
|
||||||
def create_task_branch_CAS(self, branch: str, new: str) -> str: ...
|
|
||||||
|
|
||||||
|
|
||||||
class RemotePort(Protocol):
|
class RemotePort(Protocol):
|
||||||
def repository_identity(self) -> str: ...
|
def repository_identity(self) -> str: ...
|
||||||
def push_exact(self, branch: str, expected: str | None, new: str) -> None: ...
|
def push_exact(self, branch: str, expected: str, new: str) -> None: ...
|
||||||
def read_git_ref(self, branch: str) -> str | None: ...
|
def read_git_ref(self, branch: str) -> str | None: ...
|
||||||
def read_api_branch(self, branch: str) -> str | None: ...
|
def read_api_branch(self, branch: str) -> str | None: ...
|
||||||
def commit_visible(self, oid: str) -> bool: ...
|
def commit_visible(self, oid: str) -> bool: ...
|
||||||
@@ -107,9 +106,6 @@ class AuthorityGitAdapter:
|
|||||||
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str:
|
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str:
|
||||||
return self.executor.advance_task_branch_CAS(self._ref(branch), expected, new)
|
return self.executor.advance_task_branch_CAS(self._ref(branch), expected, new)
|
||||||
|
|
||||||
def create_task_branch_CAS(self, branch: str, new: str) -> str:
|
|
||||||
return self.executor.create_task_branch_CAS(self._ref(branch), new)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class DetachedGitWorkspace:
|
class DetachedGitWorkspace:
|
||||||
@@ -262,16 +258,10 @@ class GiteaContinuationRemote:
|
|||||||
raise ContinuationExecutionError("GITEA_GIT_READBACK_INVALID")
|
raise ContinuationExecutionError("GITEA_GIT_READBACK_INVALID")
|
||||||
return oid
|
return oid
|
||||||
|
|
||||||
def push_exact(self, branch: str, expected: str | None, new: str) -> None:
|
def push_exact(self, branch: str, expected: str, new: str) -> None:
|
||||||
self._require_projected()
|
self._require_projected()
|
||||||
self.repository_identity()
|
self.repository_identity()
|
||||||
observed = self.read_git_ref(branch)
|
if self.read_git_ref(branch) != expected:
|
||||||
if observed == new:
|
|
||||||
return
|
|
||||||
if expected is None:
|
|
||||||
if observed is not None:
|
|
||||||
raise ContinuationExecutionError("GITEA_BRANCH_EXPECTATION_MISMATCH")
|
|
||||||
elif observed != expected:
|
|
||||||
raise ContinuationExecutionError("GITEA_BRANCH_EXPECTATION_MISMATCH")
|
raise ContinuationExecutionError("GITEA_BRANCH_EXPECTATION_MISMATCH")
|
||||||
_run(["git", "push", self.remote_name, f"{new}:refs/heads/{branch}"], cwd=self.workspace)
|
_run(["git", "push", self.remote_name, f"{new}:refs/heads/{branch}"], cwd=self.workspace)
|
||||||
|
|
||||||
@@ -331,18 +321,8 @@ class ContinuationPermissions:
|
|||||||
production_write: bool = False
|
production_write: bool = False
|
||||||
main_write: bool = False
|
main_write: bool = False
|
||||||
|
|
||||||
def validate(self, *, branch_mode: str) -> None:
|
def validate(self) -> None:
|
||||||
if branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
|
if any(asdict(self).values()):
|
||||||
raise ContinuationExecutionError("BRANCH_MODE_INVALID")
|
|
||||||
expected_create = branch_mode == "CREATE_NEW"
|
|
||||||
forbidden = (
|
|
||||||
self.force_push,
|
|
||||||
self.tag_write,
|
|
||||||
self.baseline_write,
|
|
||||||
self.production_write,
|
|
||||||
self.main_write,
|
|
||||||
)
|
|
||||||
if self.create_target_branch != expected_create or any(forbidden):
|
|
||||||
raise ContinuationExecutionError("MUTATION_PERMISSION_DENIED")
|
raise ContinuationExecutionError("MUTATION_PERMISSION_DENIED")
|
||||||
|
|
||||||
|
|
||||||
@@ -435,14 +415,12 @@ class ContinuationExecutor:
|
|||||||
|
|
||||||
def _validate_preflight(self, binding: ContinuationRuntimeBinding) -> None:
|
def _validate_preflight(self, binding: ContinuationRuntimeBinding) -> None:
|
||||||
request = binding.request
|
request = binding.request
|
||||||
# WriterLeaseRequestV2 validation is the single normalization authority.
|
normalized = validate_writer_lease_request_v2(self._request_mapping(request), self.authority)
|
||||||
# CREATE_NEW legitimately normalizes to create_target_branch=True and
|
if normalized != {**self._request_mapping(request), "create_target_branch": False, "worktree_mode": "detached_at_expected_head", "cas_ref_update_required": True}:
|
||||||
# detached_at_base_oid; pinning the CONTINUE_EXISTING shape here rejects
|
raise ContinuationExecutionError("CONTINUATION_NORMALIZATION_INVALID")
|
||||||
# valid first commits before the governed CAS ref update can run.
|
|
||||||
validate_writer_lease_request_v2(self._request_mapping(request), self.authority)
|
|
||||||
if not binding.original_receipt_valid or not binding.original_grant_consumed:
|
if not binding.original_receipt_valid or not binding.original_grant_consumed:
|
||||||
raise ContinuationExecutionError("ORIGINAL_GRANT_LINEAGE_INVALID")
|
raise ContinuationExecutionError("ORIGINAL_GRANT_LINEAGE_INVALID")
|
||||||
binding.permissions.validate(branch_mode=request.branch_mode)
|
binding.permissions.validate()
|
||||||
now = self.now()
|
now = self.now()
|
||||||
if now < binding.lease_not_before:
|
if now < binding.lease_not_before:
|
||||||
raise ContinuationExecutionError("LEASE_NOT_ACTIVE")
|
raise ContinuationExecutionError("LEASE_NOT_ACTIVE")
|
||||||
@@ -464,7 +442,7 @@ class ContinuationExecutor:
|
|||||||
"campaign_id": request.task_id,
|
"campaign_id": request.task_id,
|
||||||
"task_id": request.task_id,
|
"task_id": request.task_id,
|
||||||
"repository": request.repository,
|
"repository": request.repository,
|
||||||
"operation": "CONTINUATION_COMMIT_FILES_CREATE_NEW" if request.branch_mode == "CREATE_NEW" else "CONTINUATION_COMMIT_FILES",
|
"operation": "CONTINUATION_COMMIT_FILES",
|
||||||
"branch": request.target_branch,
|
"branch": request.target_branch,
|
||||||
"expected_branch_oid": request.expected_branch_oid,
|
"expected_branch_oid": request.expected_branch_oid,
|
||||||
"base_oid": request.base_oid,
|
"base_oid": request.base_oid,
|
||||||
@@ -510,10 +488,7 @@ class ContinuationExecutor:
|
|||||||
new_oid = self.workspace.commit_files(commit.files, commit.message)
|
new_oid = self.workspace.commit_files(commit.files, commit.message)
|
||||||
if not OID_RE.fullmatch(new_oid) or not self.authority.is_ancestor(request.expected_branch_oid, new_oid):
|
if not OID_RE.fullmatch(new_oid) or not self.authority.is_ancestor(request.expected_branch_oid, new_oid):
|
||||||
raise ContinuationExecutionError("NON_FAST_FORWARD_COMMIT")
|
raise ContinuationExecutionError("NON_FAST_FORWARD_COMMIT")
|
||||||
if request.branch_mode == "CREATE_NEW":
|
authority_oid = self.authority.compare_and_swap_branch(request.target_branch, request.expected_branch_oid, new_oid)
|
||||||
authority_oid = self.authority.create_task_branch_CAS(request.target_branch, new_oid)
|
|
||||||
else:
|
|
||||||
authority_oid = self.authority.compare_and_swap_branch(request.target_branch, request.expected_branch_oid, new_oid)
|
|
||||||
if authority_oid != new_oid:
|
if authority_oid != new_oid:
|
||||||
raise ContinuationExecutionError("AUTHORITY_CAS_READBACK_MISMATCH")
|
raise ContinuationExecutionError("AUTHORITY_CAS_READBACK_MISMATCH")
|
||||||
self.controller.advance(
|
self.controller.advance(
|
||||||
@@ -524,7 +499,7 @@ class ContinuationExecutor:
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
self.controller.advance(submitted.job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS")
|
self.controller.advance(submitted.job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS")
|
||||||
self.remote.push_exact(request.target_branch, None if request.branch_mode == "CREATE_NEW" else request.expected_branch_oid, new_oid)
|
self.remote.push_exact(request.target_branch, request.expected_branch_oid, new_oid)
|
||||||
except Exception:
|
except Exception:
|
||||||
return self._remote_pending(submitted.job_id, authority_oid, new_oid)
|
return self._remote_pending(submitted.job_id, authority_oid, new_oid)
|
||||||
return self._finish_remote_verification(submitted.job_id, authority_oid, new_oid)
|
return self._finish_remote_verification(submitted.job_id, authority_oid, new_oid)
|
||||||
@@ -600,7 +575,7 @@ class ContinuationExecutor:
|
|||||||
if authority_oid == git_oid == api_oid == new_oid and visible:
|
if authority_oid == git_oid == api_oid == new_oid and visible:
|
||||||
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST")
|
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST")
|
||||||
return self._finish_remote_verification(job_id, new_oid, new_oid)
|
return self._finish_remote_verification(job_id, new_oid, new_oid)
|
||||||
expected_oid = None if request.get("branch_mode") == "CREATE_NEW" else request["expected_branch_oid"]
|
expected_oid = request["expected_branch_oid"]
|
||||||
if (
|
if (
|
||||||
self.authority.is_ancestor(new_oid, authority_oid)
|
self.authority.is_ancestor(new_oid, authority_oid)
|
||||||
and git_oid is not None
|
and git_oid is not None
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
from aios_authority_git_executor import AuthorityGitExecutor
|
|
||||||
from aios_continuation_executor import ContinuationExecutor, ContinuationPermissions
|
|
||||||
from aios_continuation_lease import WriterLeaseRequestV2
|
|
||||||
from authorized_task_writer_provisioning import validate_writer_lease_request_v2
|
|
||||||
|
|
||||||
|
|
||||||
BASE = "a" * 40
|
|
||||||
BRANCH = "agent/bridge-create-new-normalization-test"
|
|
||||||
|
|
||||||
|
|
||||||
class LocalAuthority:
|
|
||||||
def __init__(self, branch_head: str | None) -> None:
|
|
||||||
self._branch_head = branch_head
|
|
||||||
|
|
||||||
def branch_exists(self, branch: str) -> bool:
|
|
||||||
return self._branch_head is not None
|
|
||||||
|
|
||||||
def branch_head(self, branch: str) -> str:
|
|
||||||
assert self._branch_head is not None
|
|
||||||
return self._branch_head
|
|
||||||
|
|
||||||
def commit_exists(self, oid: str) -> bool:
|
|
||||||
return oid == BASE
|
|
||||||
|
|
||||||
def is_ancestor(self, older: str, newer: str) -> bool:
|
|
||||||
return older == BASE and newer == BASE
|
|
||||||
|
|
||||||
|
|
||||||
def request(branch_mode: str) -> dict[str, object]:
|
|
||||||
return {
|
|
||||||
"schema": WriterLeaseRequestV2.schema,
|
|
||||||
"task_id": "bridge-create-new-normalization-test",
|
|
||||||
"repository": "aios/aios",
|
|
||||||
"branch_mode": branch_mode,
|
|
||||||
"base_oid": BASE,
|
|
||||||
"expected_branch_oid": BASE,
|
|
||||||
"target_branch": BRANCH,
|
|
||||||
"generation": 1,
|
|
||||||
"lease_seconds": 120,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
create_new = validate_writer_lease_request_v2(request("CREATE_NEW"), LocalAuthority(None))
|
|
||||||
assert create_new["create_target_branch"] is True
|
|
||||||
assert create_new["worktree_mode"] == "detached_at_base_oid"
|
|
||||||
assert create_new["cas_ref_update_required"] is True
|
|
||||||
|
|
||||||
continue_existing = validate_writer_lease_request_v2(request("CONTINUE_EXISTING"), LocalAuthority(BASE))
|
|
||||||
assert continue_existing["create_target_branch"] is False
|
|
||||||
assert continue_existing["worktree_mode"] == "detached_at_expected_head"
|
|
||||||
assert continue_existing["cas_ref_update_required"] is True
|
|
||||||
|
|
||||||
preflight = inspect.getsource(ContinuationExecutor._validate_preflight)
|
|
||||||
assert "validate_writer_lease_request_v2" in preflight
|
|
||||||
assert '"create_target_branch": False' not in preflight
|
|
||||||
assert '"worktree_mode": "detached_at_expected_head"' not in preflight
|
|
||||||
|
|
||||||
ContinuationPermissions(create_target_branch=True).validate(branch_mode="CREATE_NEW")
|
|
||||||
ContinuationPermissions().validate(branch_mode="CONTINUE_EXISTING")
|
|
||||||
|
|
||||||
cas = inspect.getsource(AuthorityGitExecutor.create_task_branch_CAS)
|
|
||||||
assert "update-ref" in cas
|
|
||||||
assert '"0" * 40' in cas
|
|
||||||
print("CREATE_NEW_NORMALIZATION_REGRESSION_PASS")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
"""Generic worker sequential acceptance marker."""
|
||||||
|
|
||||||
|
def marker():
|
||||||
|
return 'SEQUENTIAL_ONE_OK'
|
||||||
Reference in New Issue
Block a user