Files
aios/tests/test_task_admission_bootstrap.py
T

583 lines
21 KiB
Python

from __future__ import annotations
import base64
from concurrent.futures import ThreadPoolExecutor
import hashlib
from pathlib import Path
import sys
import threading
import pytest
ROOT = Path(__file__).resolve().parents[1]
APP = ROOT / "active-runtime" / "app"
if str(APP) not in sys.path:
sys.path.insert(0, str(APP))
from aios_task_admission_bootstrap import ( # noqa: E402
OwnerDelegationEpoch,
TaskAdmissionBootstrapService,
TaskAdmissionError,
TaskAdmissionGrant,
TaskAdmissionPolicy,
TaskAdmissionStore,
canonical_json_bytes,
)
from aios_task_admission_live_adapters import ( # noqa: E402
AgentTaskAdmissionPort,
BoundOwnerAuthorizationBackend,
)
BASE_OID = "9" * 40
EXECUTION_DIGEST = "a" * 64
TASK_ID = "repository-discovery-p0-20260903"
BRANCH = "agent/repository-discovery-p0-20260902"
IDEMPOTENCY = "repository-discovery-p0-admission-20260903"
FILES = [
"active-runtime/app/aios_repository_discovery.py",
"active-runtime/app/aios_chatgpt_bridge_v2.py",
"docs/gitops/openapi-chatgpt-actions.yaml",
"tests/test_repository_discovery.py",
]
CAPS = [
"governed.task.create",
"agent-task:control",
"task.principal.issue",
"writer.lease",
"governed.write.files",
"git.read",
"testing",
"verification",
]
class FakeAuthorization:
def __init__(self, *, policy_digest: str, now: int = 1_800_000_000):
self.now = now
self.signature_calls = 0
self.epoch_calls = 0
self.signature_ok = True
self.epoch = OwnerDelegationEpoch(
epoch_id="epoch-repository-discovery-20260903",
owner_identity="owner",
trust_domain="aios.owner",
repository="aios/aios",
allowed_branch_prefixes=("agent/repository-discovery-",),
allowed_capabilities=frozenset(CAPS),
allowed_file_prefixes=("active-runtime", "docs", "tests"),
risk_ceiling="medium",
issued_at=now - 60,
not_before=now - 60,
expires_at=now + 3600,
revocation_epoch=0,
policy_digest=policy_digest,
)
def verify_delegation_epoch(self, epoch_id: str) -> OwnerDelegationEpoch:
self.epoch_calls += 1
if epoch_id != self.epoch.epoch_id:
raise TaskAdmissionError("UNKNOWN_EPOCH", 403)
return self.epoch
def verify_owner_signature(
self,
*,
key_id: str,
canonical_payload: bytes,
signature_b64: str,
) -> str:
self.signature_calls += 1
if key_id != "owner-key-1" or not self.signature_ok:
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403)
expected = base64.b64encode(
hashlib.sha256(canonical_payload + b"\0owner-key-1").digest()
).decode("ascii")
if signature_b64 != expected:
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403)
return "owner"
class FakeTaskPort:
def __init__(self):
self.calls = 0
self.grants = []
self._lock = threading.Lock()
def admit(self, grant):
with self._lock:
self.calls += 1
self.grants.append(grant)
return {"task_id": grant.task_id, "state": "QUEUED"}
@pytest.fixture
def policy():
return TaskAdmissionPolicy(
allowed_capabilities=frozenset(CAPS),
max_file_count=16,
max_task_ttl_seconds=3600,
max_envelope_ttl_seconds=900,
max_risk="medium",
)
@pytest.fixture
def service(tmp_path: Path, policy: TaskAdmissionPolicy):
now = 1_800_000_000
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest(), now=now)
port = FakeTaskPort()
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=auth,
task_port=port,
policy=policy,
clock=lambda: now,
)
return svc, auth, port
def request(**overrides):
value = {
"idempotency_key": IDEMPOTENCY,
"task_id": TASK_ID,
"repository": "aios/aios",
"authority_base_oid": BASE_OID,
"target_branch": BRANCH,
"generation": 1,
"execution_digest": EXECUTION_DIGEST,
"requested_capabilities": list(CAPS),
"file_allowlist": list(FILES),
"risk": "medium",
"expires_in_seconds": 600,
}
value.update(overrides)
return value
def test_envelope_exact_replay_returns_same_nonce_and_payload(service):
svc, _, _ = service
first = svc.create_envelope(request())
second = svc.create_envelope(request())
assert first["idempotent"] is False
assert second["idempotent"] is True
assert first["envelope"] == second["envelope"]
assert first["canonical_payload_base64"] == second["canonical_payload_base64"]
def test_envelope_idempotency_conflict_fails_closed(service):
svc, _, _ = service
svc.create_envelope(request())
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_IDEMPOTENCY_CONFLICT"):
svc.create_envelope(request(risk="low"))
@pytest.mark.parametrize(
"field,value,code",
[
("repository", "other/repo", "TASK_ADMISSION_REPOSITORY_REJECTED"),
("target_branch", "main", "TASK_ADMISSION_BRANCH_REJECTED"),
("authority_base_oid", "HEAD", "TASK_ADMISSION_BASE_OID_INVALID"),
("requested_capabilities", ["production.write"], "TASK_ADMISSION_PROHIBITED_CAPABILITY"),
("requested_capabilities", ["git:*"], "TASK_ADMISSION_CAPABILITY_INVALID"),
("file_allowlist", ["../secrets"], "TASK_ADMISSION_FILE_PATH_INVALID"),
("file_allowlist", ["/etc/passwd"], "TASK_ADMISSION_FILE_PATH_INVALID"),
("risk", "high", "TASK_ADMISSION_RISK_OUT_OF_POLICY"),
],
)
def test_envelope_policy_rejects_scope_widening(service, field, value, code):
svc, _, _ = service
with pytest.raises(TaskAdmissionError, match=code):
svc.create_envelope(request(**{field: value}))
def test_valid_delegation_epoch_admits_exact_task_once(service):
svc, auth, port = service
envelope = svc.create_envelope(request())["envelope"]
result = svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert result["schema"] == "aios.task-admission-receipt.v1"
assert result["task_id"] == TASK_ID
assert result["task_state"] == "QUEUED"
assert result["parent_epoch_id"] == auth.epoch.epoch_id
assert port.calls == 1
replay = svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert replay["task_id"] == TASK_ID
assert replay["idempotent"] is True
assert port.calls == 1
def test_post_admission_projection_runs_after_exact_task_readback_once(
tmp_path: Path, policy: TaskAdmissionPolicy
):
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest())
port = FakeTaskPort()
observations = []
def project(grant, task):
observations.append((grant.task_id, task["task_id"], task["state"]))
return {"job_id": "projected-job"}
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=auth,
task_port=port,
policy=policy,
clock=lambda: 1_800_000_000,
post_admission=project,
)
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
proof = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
first = svc.authorize_and_admit(proof)
replay = svc.authorize_and_admit(proof)
assert first["task_id"] == TASK_ID
assert replay["idempotent"] is True
assert observations == [(TASK_ID, TASK_ID, "QUEUED")]
def test_epoch_capability_widening_rejected_before_task_port(service):
svc, auth, port = service
# Envelope policy allows the capability, parent epoch deliberately does not.
auth.epoch = OwnerDelegationEpoch(
**{
**auth.epoch.as_public_dict(),
"allowed_capabilities": frozenset(
item for item in CAPS if item != "writer.lease"
),
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": auth.epoch.allowed_file_prefixes,
}
)
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(
TaskAdmissionError, match="TASK_ADMISSION_EPOCH_CAPABILITY_WIDENING"
):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_epoch_file_scope_widening_rejected(service):
svc, auth, port = service
auth.epoch = OwnerDelegationEpoch(
**{
**auth.epoch.as_public_dict(),
"allowed_capabilities": auth.epoch.allowed_capabilities,
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": ("docs",),
}
)
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_FILE_WIDENING"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_revoked_or_expired_epoch_rejected(service):
svc, auth, port = service
auth.epoch = OwnerDelegationEpoch(
**{
**auth.epoch.as_public_dict(),
"allowed_capabilities": auth.epoch.allowed_capabilities,
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": auth.epoch.allowed_file_prefixes,
"revocation_epoch": 1,
}
)
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_REVOKED"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_owner_signature_path_verifies_canonical_payload(service):
svc, auth, port = service
created = svc.create_envelope(request())
payload = canonical_json_bytes(created["envelope"])
signature = base64.b64encode(
hashlib.sha256(payload + b"\0owner-key-1").digest()
).decode("ascii")
result = svc.authorize_and_admit(
{
"envelope_id": created["envelope"]["envelope_id"],
"authorization_kind": "owner_signature",
"owner_key_id": "owner-key-1",
"signature_base64": signature,
}
)
assert result["authorization_kind"] == "owner_signature"
assert result["owner_identity"] == "owner"
assert auth.signature_calls == 1
assert port.calls == 1
def test_bad_owner_signature_is_rejected(service):
svc, _, port = service
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_OWNER_SIGNATURE_INVALID"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "owner_signature",
"owner_key_id": "owner-key-1",
"signature_base64": base64.b64encode(b"bad").decode("ascii"),
}
)
assert port.calls == 0
def test_expired_envelope_cannot_admit(tmp_path: Path, policy: TaskAdmissionPolicy):
now = [1_800_000_000]
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest(), now=now[0])
port = FakeTaskPort()
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=auth,
task_port=port,
policy=policy,
clock=lambda: now[0],
)
envelope = svc.create_envelope(request(expires_in_seconds=60))["envelope"]
now[0] += 61
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_ENVELOPE_EXPIRED"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_concurrent_exact_authorization_calls_admit_once(service):
svc, auth, port = service
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
body = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
with ThreadPoolExecutor(max_workers=16) as pool:
results = list(pool.map(lambda _: svc.authorize_and_admit(body), range(16)))
assert {item["task_id"] for item in results} == {TASK_ID}
assert port.calls == 1
def test_cross_instance_envelope_winner_is_the_durable_value(tmp_path: Path, policy: TaskAdmissionPolicy):
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest())
port = FakeTaskPort()
stores = [TaskAdmissionStore(tmp_path / "shared.sqlite3") for _ in range(16)]
services = [
TaskAdmissionBootstrapService(
store=store, authorization=auth, task_port=port, policy=policy,
clock=lambda: 1_800_000_000,
)
for store in stores
]
with ThreadPoolExecutor(max_workers=16) as pool:
values = list(pool.map(lambda svc: svc.create_envelope(request()), services))
assert len({item["envelope"]["envelope_id"] for item in values}) == 1
assert sum(not item["idempotent"] for item in values) == 1
def test_cross_instance_authorization_calls_create_one_task(tmp_path: Path, policy: TaskAdmissionPolicy):
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest())
port = FakeTaskPort()
database = tmp_path / "shared.sqlite3"
first = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(database), authorization=auth, task_port=port,
policy=policy, clock=lambda: 1_800_000_000,
)
envelope_id = first.create_envelope(request())["envelope"]["envelope_id"]
services = [
TaskAdmissionBootstrapService(
store=TaskAdmissionStore(database), authorization=auth, task_port=port,
policy=policy, clock=lambda: 1_800_000_000,
)
for _ in range(16)
]
body = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
with ThreadPoolExecutor(max_workers=16) as pool:
values = list(pool.map(lambda svc: svc.authorize_and_admit(body), services))
assert {item["task_id"] for item in values} == {TASK_ID}
assert port.calls == 1
@pytest.mark.parametrize("file_name", ["docs//gitops/a.yaml", "docs/./gitops/a.yaml", "docs/a\x00.yaml"])
def test_file_paths_must_already_be_canonical(service, file_name):
svc, _, _ = service
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_FILE_PATH_INVALID"):
svc.create_envelope(request(file_allowlist=[file_name]))
@pytest.mark.parametrize("generation", [True, "1", 1.0, 0])
def test_generation_is_a_strict_positive_integer(service, generation):
svc, _, _ = service
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_GENERATION_INVALID"):
svc.create_envelope(request(generation=generation))
def test_policy_digest_drift_and_changed_proof_cannot_replay(service):
svc, auth, _ = service
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
body = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
svc.authorize_and_admit(body)
svc.policy = TaskAdmissionPolicy(
allowed_capabilities=frozenset(CAPS), policy_digest="policy-has-changed"
)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_POLICY_DIGEST_DRIFT"):
svc.authorize_and_admit(body)
def test_grant_expiry_is_clipped_to_parent_epoch(tmp_path: Path, policy: TaskAdmissionPolicy):
now = 1_800_000_000
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest(), now=now)
auth.epoch = OwnerDelegationEpoch(
**{**auth.epoch.as_public_dict(), "allowed_capabilities": auth.epoch.allowed_capabilities,
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": auth.epoch.allowed_file_prefixes, "expires_at": now + 120}
)
port = FakeTaskPort()
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"), authorization=auth,
task_port=port, policy=policy, clock=lambda: now,
)
result = svc.authorize_and_admit({
"envelope_id": svc.create_envelope(request())["envelope"]["envelope_id"],
"authorization_kind": "delegation_epoch", "delegation_epoch_id": auth.epoch.epoch_id,
})
assert port.grants[0].expires_at == now + 120
assert result["task_id"] == TASK_ID
def test_owner_backend_uses_bound_caller_and_decoded_armored_signature(policy):
captured = {}
class Verifier:
def verify(self, **kwargs):
captured.update(kwargs)
return True
epoch = FakeAuthorization(policy_digest=policy.normalized_policy_digest()).epoch
backend = BoundOwnerAuthorizationBackend(
authenticated_caller={"uid": "bridge-service"},
verified_epoch_provider=lambda **kwargs: captured.update(epoch_lookup=kwargs) or epoch,
signature_verifier=Verifier(),
)
assert backend.verify_delegation_epoch(epoch.epoch_id) == epoch
assert captured["epoch_lookup"]["caller"] == {"uid": "bridge-service"}
assert backend.verify_owner_signature(
key_id="owner-key-1", canonical_payload=b"exact", signature_b64=base64.b64encode(b"-----BEGIN SSH SIGNATURE-----").decode(),
) == "aios-task-grant-signer"
assert captured["namespace"] == "aios-owner-approval-v1"
assert captured["payload"] == b"exact"
def test_official_task_port_reconciles_ambiguous_create(tmp_path: Path, policy):
grant = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=FakeAuthorization(policy_digest=policy.normalized_policy_digest()),
task_port=FakeTaskPort(), policy=policy, clock=lambda: 1_800_000_000,
)
# Construct via the public flow so the port sees a production-shaped grant.
created = grant.create_envelope(request())["envelope"]
envelope = grant._load_envelope(created["envelope_id"])
task_grant = TaskAdmissionGrant(
grant_id="task-grant-adapter-test", envelope_id=envelope.envelope_id,
task_id=envelope.task_id, repository=envelope.repository,
authority_base_oid=envelope.authority_base_oid, target_branch=envelope.target_branch,
generation=envelope.generation, execution_digest=envelope.execution_digest,
capabilities=envelope.requested_capabilities, file_allowlist=envelope.file_allowlist,
risk=envelope.risk, trust_domain=envelope.trust_domain,
policy_digest=envelope.policy_digest, authorization_kind="owner_signature",
owner_identity="aios-task-grant-signer", parent_epoch_id=None,
issued_at=1_800_000_000, expires_at=1_800_000_600,
)
class NotFound(Exception):
status = 404
class Transport:
def __init__(self): self.calls = []; self.task = None
def request(self, method, path, body=None):
self.calls.append((method, path, body))
if method == "GET" and self.task is None: raise NotFound()
if method == "POST":
self.task = {key: value for key, value in body.items() if key != "idempotency_key"}
self.task["status"] = "QUEUED"
raise TimeoutError("response lost after side effect")
return self.task
transport = Transport()
plan = {
"external_job_id": "fixture-job-adapter-01", "repository": task_grant.repository,
"target_branch": task_grant.target_branch, "generation": task_grant.generation,
"execution_digest": task_grant.execution_digest,
}
assert AgentTaskAdmissionPort(transport, execution_plan_provider=lambda _: plan).admit(task_grant) == {"task_id": TASK_ID, "state": "QUEUED"}
assert [call[:2] for call in transport.calls] == [
("GET", "/v1/tasks/" + TASK_ID), ("POST", "/v1/tasks"), ("GET", "/v1/tasks/" + TASK_ID),
]
def test_status_is_read_only_and_reports_admitted_task(service):
svc, auth, _ = service
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
before = svc.status(envelope_id)
assert before["status"] == "PENDING_AUTHORIZATION"
assert before["task"] is None
svc.authorize_and_admit(
{
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
after = svc.status(envelope_id)
assert after["status"] == "ADMITTED"
assert after["task"]["task_id"] == TASK_ID