Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d898f4b29c |
@@ -112,6 +112,27 @@ class AuthorityGitExecutor:
|
||||
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
|
||||
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:
|
||||
_oid(expected_oid, "expected_oid")
|
||||
_oid(new_oid, "new_oid")
|
||||
|
||||
@@ -124,7 +124,7 @@ class GovernedRuntime:
|
||||
workspace = DetachedGitWorkspace(worktree_path)
|
||||
if workspace.head_oid() != lease.expected_branch_oid or not workspace.is_detached(): raise RuntimeError("WORKTREE_HEAD_MISMATCH")
|
||||
job_root = self._job_root(lease.task_id); controller = GitOpsJobController(JobStore(job_root, payload_root=job_root / "payloads")); credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path); remote = GiteaContinuationRemote(workspace=worktree_path, credential=credential, remote_name=self.config.remote_name, repository=lease.repository, api_base=self.config.api_base); executor = ContinuationExecutor(controller, AuthorityGitAdapter(self.authority), workspace, remote, credential)
|
||||
binding = ContinuationRuntimeBinding(request=lease, workspace_path=worktree_path, lease_not_before=datetime.now(timezone.utc) - timedelta(seconds=1), lease_expires_at=datetime.now(timezone.utc) + timedelta(seconds=lease.lease_seconds), original_receipt_valid=True, original_grant_consumed=True, permissions=ContinuationPermissions())
|
||||
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"))
|
||||
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]:
|
||||
@@ -148,7 +148,16 @@ 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)
|
||||
return executor.execute(binding, commit)
|
||||
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
|
||||
|
||||
def _controller_for_job(self, job_id: str) -> GitOpsJobController:
|
||||
for controller in self.controllers.values():
|
||||
|
||||
@@ -52,11 +52,12 @@ class AuthorityPort(Protocol):
|
||||
def branch_head(self, branch: str) -> str: ...
|
||||
def is_ancestor(self, older: str, newer: str) -> bool: ...
|
||||
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
|
||||
def create_task_branch_CAS(self, branch: str, new: str) -> str: ...
|
||||
|
||||
|
||||
class RemotePort(Protocol):
|
||||
def repository_identity(self) -> str: ...
|
||||
def push_exact(self, branch: str, expected: str, new: str) -> None: ...
|
||||
def push_exact(self, branch: str, expected: str | None, new: str) -> None: ...
|
||||
def read_git_ref(self, branch: str) -> str | None: ...
|
||||
def read_api_branch(self, branch: str) -> str | None: ...
|
||||
def commit_visible(self, oid: str) -> bool: ...
|
||||
@@ -106,6 +107,9 @@ class AuthorityGitAdapter:
|
||||
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)
|
||||
|
||||
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)
|
||||
class DetachedGitWorkspace:
|
||||
@@ -258,10 +262,16 @@ class GiteaContinuationRemote:
|
||||
raise ContinuationExecutionError("GITEA_GIT_READBACK_INVALID")
|
||||
return oid
|
||||
|
||||
def push_exact(self, branch: str, expected: str, new: str) -> None:
|
||||
def push_exact(self, branch: str, expected: str | None, new: str) -> None:
|
||||
self._require_projected()
|
||||
self.repository_identity()
|
||||
if self.read_git_ref(branch) != expected:
|
||||
observed = self.read_git_ref(branch)
|
||||
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")
|
||||
_run(["git", "push", self.remote_name, f"{new}:refs/heads/{branch}"], cwd=self.workspace)
|
||||
|
||||
@@ -321,8 +331,18 @@ class ContinuationPermissions:
|
||||
production_write: bool = False
|
||||
main_write: bool = False
|
||||
|
||||
def validate(self) -> None:
|
||||
if any(asdict(self).values()):
|
||||
def validate(self, *, branch_mode: str) -> None:
|
||||
if branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
|
||||
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")
|
||||
|
||||
|
||||
@@ -415,12 +435,14 @@ class ContinuationExecutor:
|
||||
|
||||
def _validate_preflight(self, binding: ContinuationRuntimeBinding) -> None:
|
||||
request = binding.request
|
||||
normalized = validate_writer_lease_request_v2(self._request_mapping(request), self.authority)
|
||||
if normalized != {**self._request_mapping(request), "create_target_branch": False, "worktree_mode": "detached_at_expected_head", "cas_ref_update_required": True}:
|
||||
raise ContinuationExecutionError("CONTINUATION_NORMALIZATION_INVALID")
|
||||
# WriterLeaseRequestV2 validation is the single normalization authority.
|
||||
# CREATE_NEW legitimately normalizes to create_target_branch=True and
|
||||
# detached_at_base_oid; pinning the CONTINUE_EXISTING shape here rejects
|
||||
# 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:
|
||||
raise ContinuationExecutionError("ORIGINAL_GRANT_LINEAGE_INVALID")
|
||||
binding.permissions.validate()
|
||||
binding.permissions.validate(branch_mode=request.branch_mode)
|
||||
now = self.now()
|
||||
if now < binding.lease_not_before:
|
||||
raise ContinuationExecutionError("LEASE_NOT_ACTIVE")
|
||||
@@ -442,7 +464,7 @@ class ContinuationExecutor:
|
||||
"campaign_id": request.task_id,
|
||||
"task_id": request.task_id,
|
||||
"repository": request.repository,
|
||||
"operation": "CONTINUATION_COMMIT_FILES",
|
||||
"operation": "CONTINUATION_COMMIT_FILES_CREATE_NEW" if request.branch_mode == "CREATE_NEW" else "CONTINUATION_COMMIT_FILES",
|
||||
"branch": request.target_branch,
|
||||
"expected_branch_oid": request.expected_branch_oid,
|
||||
"base_oid": request.base_oid,
|
||||
@@ -488,7 +510,10 @@ class ContinuationExecutor:
|
||||
new_oid = self.workspace.commit_files(commit.files, commit.message)
|
||||
if not OID_RE.fullmatch(new_oid) or not self.authority.is_ancestor(request.expected_branch_oid, new_oid):
|
||||
raise ContinuationExecutionError("NON_FAST_FORWARD_COMMIT")
|
||||
authority_oid = self.authority.compare_and_swap_branch(request.target_branch, request.expected_branch_oid, new_oid)
|
||||
if request.branch_mode == "CREATE_NEW":
|
||||
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:
|
||||
raise ContinuationExecutionError("AUTHORITY_CAS_READBACK_MISMATCH")
|
||||
self.controller.advance(
|
||||
@@ -499,7 +524,7 @@ class ContinuationExecutor:
|
||||
)
|
||||
try:
|
||||
self.controller.advance(submitted.job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS")
|
||||
self.remote.push_exact(request.target_branch, request.expected_branch_oid, new_oid)
|
||||
self.remote.push_exact(request.target_branch, None if request.branch_mode == "CREATE_NEW" else request.expected_branch_oid, new_oid)
|
||||
except Exception:
|
||||
return self._remote_pending(submitted.job_id, authority_oid, new_oid)
|
||||
return self._finish_remote_verification(submitted.job_id, authority_oid, new_oid)
|
||||
@@ -575,7 +600,7 @@ class ContinuationExecutor:
|
||||
if authority_oid == git_oid == api_oid == new_oid and visible:
|
||||
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST")
|
||||
return self._finish_remote_verification(job_id, new_oid, new_oid)
|
||||
expected_oid = request["expected_branch_oid"]
|
||||
expected_oid = None if request.get("branch_mode") == "CREATE_NEW" else request["expected_branch_oid"]
|
||||
if (
|
||||
self.authority.is_ancestor(new_oid, authority_oid)
|
||||
and git_oid is not None
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user