Files
aios/tests/test_live_executor_contract.py
T
owner 9adec87c4b feat(governed): Release Source Admission (AIOS-BASELINE-1.1-RELEASE-SOURCE-ADMISSION)
Make governed release source closure explicit so a pushed governed candidate
can enter the authoritative Git/release closure without manual runtime
overwrite or fixed-recipe bypass.

- Gitea machine transport: add _safe_branch() and exact OID/branch verification.
- release_source_admission.py (new): admit a governed source by verifying remote
  branch -> exact OID, fetching only to refs/aios/release-sources/<OID>,
  validating authoritative main ancestry + tree OID, and writing a policy-bound,
  HMAC-authenticated immutable admission record (source_admission_hmac).
- trusted candidate builder: read-only admission verifier, remains
  PrivateNetwork / read-only; missing admission surfaces SOURCE_ADMISSION_REQUIRED.
- Model API / release saga: consume admission records and return machine-actionable
  SOURCE_ADMISSION_REQUIRED (not NOT_FOUND) when a non-fixed source is unadmitted.
- systemd: add aios-release-source-admission.service (oneshot, root/gitops,
  AIOS_GITOPS_WRITES_ENABLED=false, independent source_admission_hmac credential);
  wire source_admission_hmac into trusted builder; update staging unit.
- Fixed V2R2C2/V2R2C3 recipes are not overridden by admission.
- Tests: new release_source_admission + adjusted builder/model-api/live-executor/
  readiness tests.

Verified: 13-file after_sha256 byte-exact; py_compile PASS;
Linux 326 targeted passed / 0 failed; acceptance 165; regression 1401;
systemd-analyze verify 3/3 PASS.

Base: a320ea3 (agent/baseline-1.1-principal-recovery-20260908).
2026-09-08 11:44:09 +08:00

241 lines
9.0 KiB
Python

from __future__ import annotations
import tempfile
from types import SimpleNamespace
from pathlib import Path
import pytest
import aios_gitops_job_executor as executor_module
from aios_authority_git_executor import AuthorityGitError, AuthorityGitExecutor
import aios_gitea_machine_transport as gitea_transport_module
from aios_gitea_machine_transport import GiteaMachineTransport
from aios_gitea_machine_broker import CampaignCapabilityReceipt, GiteaMachineBroker, MachineCredentialProjection
from aios_gitops_job_controller import GitOpsJobController, JobError, JobStore, JobState
from aios_gitops_writer_worker import handle_request
OLD = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
NEW = "3fbbc18fb2131bbbd9e511004a59ee95cf3e86b7"
class FakeAuthority:
def __init__(self, descendant=True):
self.head = OLD
self.descendant = descendant
self.cas_calls = []
def read_ref(self, ref):
return self.head
def ancestry_check(self, older, newer):
return self.descendant and older == OLD and newer == NEW
def advance_task_branch_CAS(self, branch, expected, new):
if self.head != expected:
raise AuthorityGitError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
self.cas_calls.append((branch, expected, new))
self.head = new
return new
class FakeBroker:
def __init__(self):
self.remote = None
self.push_calls = []
def verify_identity(self):
return {"login": "aios-autonomy-agent", "repository": "aios/aios"}
def readback(self):
return self.remote
def push_branch(self, new):
self.push_calls.append(new)
self.remote = new
return new
def create_pr(self, *args):
return {"number": 1}
def status(self, *args):
return {"status": "success"}
def merge(self, *args):
return {"merged": True}
def _request(**extra):
value = {
"campaign_id": executor_module.CAMPAIGN_ID,
"repository": executor_module.REPOSITORY,
"operation": "ADVANCE_EXISTING_CAMPAIGN_BRANCH",
"branch": executor_module.BRANCH,
"expected_branch_oid": OLD,
"payload_digest": "0" * 64,
"task_id": executor_module.TASK_ID,
"new_branch_oid": NEW,
"authorization_recovery_digest": executor_module.AUTHORIZATION_DIGEST,
}
value.update(extra)
return value
def _executor(temp, authority=None, broker=None):
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request())
executor = executor_module.DurableJobExecutor(controller, authority or FakeAuthority(), broker or FakeBroker(), Path(temp) / "recovery.json")
return controller, job, executor
def _recovery():
return {
"digest": executor_module.AUTHORIZATION_DIGEST,
"campaign_id": executor_module.CAMPAIGN_ID,
"task_id": executor_module.TASK_ID,
"repository": executor_module.REPOSITORY,
"branch": executor_module.BRANCH,
"expected_branch_oid": OLD,
}
def test_advance_existing_branch_success_and_duplicate_execute(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
controller, job, executor = _executor(temp)
first = executor.execute(job.job_id)
second = executor.execute(job.job_id)
assert first["state"] == JobState.COMPLETED.value
assert second["state"] == JobState.COMPLETED.value
assert executor.authority.cas_calls == [(f"refs/heads/{executor_module.BRANCH}", OLD, NEW)]
assert executor.broker.push_calls == [NEW]
def test_authorization_digest_required(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request(authorization_recovery_digest="bad"))
executor = executor_module.DurableJobExecutor(controller, FakeAuthority(), FakeBroker(), Path(temp) / "recovery.json")
with pytest.raises(executor_module.JobExecutionError, match="AUTHORIZATION_RECOVERY_DIGEST_MISMATCH"):
executor.dry_run(job.job_id)
def test_wrong_branch_rejected(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request(branch="agent/not-the-campaign"))
executor = executor_module.DurableJobExecutor(controller, FakeAuthority(), FakeBroker(), Path(temp) / "recovery.json")
with pytest.raises(executor_module.JobExecutionError, match="BRANCH_MISMATCH"):
executor.dry_run(job.job_id)
def test_non_descendant_new_oid_rejected(monkeypatch):
monkeypatch.setattr(executor_module, "load_recovered_authorization", lambda _path: _recovery())
with tempfile.TemporaryDirectory() as temp:
authority = FakeAuthority(descendant=False)
controller, job, executor = _executor(temp, authority=authority)
with pytest.raises(executor_module.JobExecutionError, match="NEW_OID_NOT_DESCENDANT"):
executor.dry_run(job.job_id)
def test_worker_execute_action_consumes_job():
with tempfile.TemporaryDirectory() as temp:
controller = GitOpsJobController(JobStore(temp))
job = controller.submit(_request())
class Stub:
def execute(self, job_id, *, dry_run=False):
assert job_id == job.job_id
assert dry_run is True
return {"decision": "SAFE_TO_ADVANCE"}
result = handle_request(controller, {"action": "execute", "job_id": job.job_id, "dry_run": True}, Stub())
assert result["job"]["decision"] == "SAFE_TO_ADVANCE"
def test_machine_transport_branch_cas_is_not_force_push(monkeypatch):
transport = GiteaMachineTransport("http://127.0.0.1:3000/api/v1", "aios/aios", "https://g.cnnetmkt.top/git/aios/aios.git", Path("/tmp/repo.git"))
calls = []
def read_branch(_self, _branch):
return NEW if calls else OLD
monkeypatch.setattr(GiteaMachineTransport, "read_branch", read_branch)
monkeypatch.setattr(GiteaMachineTransport, "_push_branch_fast_forward", lambda _self, branch, new: calls.append((branch, new)))
assert transport.compare_and_swap_branch(executor_module.BRANCH, OLD, NEW) == NEW
assert calls == [(executor_module.BRANCH, NEW)]
def test_machine_transport_fetches_exact_branch_tip_into_quarantine_ref(monkeypatch, tmp_path):
transport = GiteaMachineTransport(
"http://127.0.0.1:3000/api/v1",
"aios/aios",
"https://g.cnnetmkt.top/git/aios/aios.git",
tmp_path / "repo.git",
)
reads = iter([NEW, NEW])
monkeypatch.setattr(
GiteaMachineTransport,
"read_branch",
lambda _self, _branch: next(reads),
)
askpass = tmp_path / "askpass"
askpass.write_text("#!/bin/sh\n", encoding="utf-8")
monkeypatch.setattr(
GiteaMachineTransport,
"_askpass",
lambda _self: (askpass, {}),
)
calls = []
def run(argv, **kwargs):
calls.append(list(argv))
if "fetch" in argv:
return SimpleNamespace(returncode=0, stdout="", stderr="")
if "rev-parse" in argv:
# Before fetch the quarantine ref is absent; after fetch it resolves.
seen_fetch = any("fetch" in call for call in calls[:-1])
return SimpleNamespace(
returncode=0 if seen_fetch else 1,
stdout=(NEW + "\n") if seen_fetch else "",
stderr="",
)
if "update-ref" in argv:
return SimpleNamespace(returncode=0, stdout="", stderr="")
raise AssertionError(argv)
monkeypatch.setattr(gitea_transport_module.subprocess, "run", run)
assert transport.fetch_branch_exact(executor_module.BRANCH, NEW) == NEW
fetch = next(call for call in calls if "fetch" in call)
assert "--no-tags" in fetch
assert "--no-write-fetch-head" in fetch
assert f"refs/heads/{executor_module.BRANCH}:refs/aios/release-sources/{NEW}" in fetch
assert not any("--force" in item or item == "-f" for item in fetch)
def test_machine_transport_does_not_fetch_when_remote_tip_moved(monkeypatch, tmp_path):
transport = GiteaMachineTransport(
"http://127.0.0.1:3000/api/v1",
"aios/aios",
"https://g.cnnetmkt.top/git/aios/aios.git",
tmp_path / "repo.git",
)
monkeypatch.setattr(
GiteaMachineTransport,
"read_branch",
lambda _self, _branch: OLD,
)
monkeypatch.setattr(
gitea_transport_module.subprocess,
"run",
lambda *args, **kwargs: (_ for _ in ()).throw(
AssertionError("git must not run after remote mismatch")
),
)
with pytest.raises(
gitea_transport_module.GiteaTransportError,
match="GITEA_SOURCE_REF_MISMATCH",
):
transport.fetch_branch_exact(executor_module.BRANCH, NEW)