[governance] bind cryptographic Owner approval to existing-branch publication

This commit is contained in:
AIOS Governed Continuation
2026-08-10 23:47:00 +08:00
parent 3244f0f17f
commit b2646286de
4 changed files with 1040 additions and 0 deletions
@@ -0,0 +1,352 @@
"""Cryptographic Owner-approval boundary for governed existing-branch publication.
Only a complete signed envelope from an authoritative trust-root adapter is
accepted. References, chat assertions, and locally supplied keys are not an
approval. Missing public-key or revocation evidence fails closed.
"""
from __future__ import annotations
import base64
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import re
from typing import Any, Mapping, Protocol
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
APPROVAL_SCHEMA = "aios.existing-branch-owner-approval.v1"
PUBLISH_OPERATION = "publishExistingBranchGoverned"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
DIGEST_RE = re.compile(r"^[0-9a-f]{64}$")
IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._@:+/-]{0,254}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class OwnerApprovalRejected(RuntimeError):
"""Stable, audit-safe error for a rejected Owner approval."""
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
def canonical_json(value: Any) -> bytes:
"""Encode a signed payload without implementation-dependent spacing."""
try:
encoded = json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
)
except (TypeError, ValueError) as exc:
raise OwnerApprovalRejected("OWNER_APPROVAL_CANONICALIZATION_REJECTED") from exc
return (encoded + "\n").encode("utf-8")
def digest_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value)).hexdigest()
def _require_identifier(field: str, value: object) -> str:
if not isinstance(value, str) or not IDENTIFIER_RE.fullmatch(value):
raise OwnerApprovalRejected(f"OWNER_APPROVAL_{field.upper()}_REJECTED")
return value
def _require_digest(field: str, value: object) -> str:
if not isinstance(value, str) or not DIGEST_RE.fullmatch(value):
raise OwnerApprovalRejected(f"OWNER_APPROVAL_{field.upper()}_REJECTED")
return value
def _require_oid(field: str, value: object) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise OwnerApprovalRejected(f"OWNER_APPROVAL_{field.upper()}_REJECTED")
return value
def _parse_utc(field: str, value: object) -> datetime:
if not isinstance(value, str):
raise OwnerApprovalRejected(f"OWNER_APPROVAL_{field.upper()}_REJECTED")
try:
instant = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise OwnerApprovalRejected(f"OWNER_APPROVAL_{field.upper()}_REJECTED") from exc
if instant.tzinfo is None:
raise OwnerApprovalRejected(f"OWNER_APPROVAL_{field.upper()}_REJECTED")
return instant.astimezone(timezone.utc)
@dataclass(frozen=True, slots=True)
class PublicationApprovalBinding:
repository: str
branch: str
expected_branch_oid: str
operation: str
request_digest: str
idempotency_digest: str
def validate(self) -> "PublicationApprovalBinding":
_require_identifier("repository", self.repository)
if not BRANCH_RE.fullmatch(self.branch):
raise OwnerApprovalRejected("OWNER_APPROVAL_BRANCH_REJECTED")
_require_oid("expected_branch_oid", self.expected_branch_oid)
if self.operation != PUBLISH_OPERATION:
raise OwnerApprovalRejected("OWNER_APPROVAL_OPERATION_REJECTED")
_require_digest("request_digest", self.request_digest)
_require_digest("idempotency_digest", self.idempotency_digest)
return self
@property
def as_dict(self) -> dict[str, str]:
return {
"repository": self.repository,
"branch": self.branch,
"expected_branch_oid": self.expected_branch_oid,
"operation": self.operation,
"request_digest": self.request_digest,
"idempotency_digest": self.idempotency_digest,
}
@dataclass(frozen=True, slots=True)
class OwnerApprovalEnvelope:
approval_id: str
repository: str
branch: str
expected_branch_oid: str
operation: str
issued_at: str
not_before: str
expires_at: str
nonce: str
owner_identity: str
trust_domain: str
key_id: str
request_digest: str
idempotency_digest: str
signature: str
@property
def canonical_payload(self) -> dict[str, str]:
return {
"schema": APPROVAL_SCHEMA,
"approval_id": self.approval_id,
"repository": self.repository,
"branch": self.branch,
"expected_branch_oid": self.expected_branch_oid,
"operation": self.operation,
"issued_at": self.issued_at,
"not_before": self.not_before,
"expires_at": self.expires_at,
"nonce": self.nonce,
"owner_identity": self.owner_identity,
"trust_domain": self.trust_domain,
"key_id": self.key_id,
"request_digest": self.request_digest,
"idempotency_digest": self.idempotency_digest,
}
def canonical_bytes(self) -> bytes:
return canonical_json(self.canonical_payload)
@property
def fingerprint(self) -> str:
return hashlib.sha256(self.canonical_bytes()).hexdigest()
@property
def binding(self) -> PublicationApprovalBinding:
return PublicationApprovalBinding(
repository=self.repository,
branch=self.branch,
expected_branch_oid=self.expected_branch_oid,
operation=self.operation,
request_digest=self.request_digest,
idempotency_digest=self.idempotency_digest,
)
@classmethod
def from_mapping(cls, value: object) -> "OwnerApprovalEnvelope":
if not isinstance(value, Mapping):
raise OwnerApprovalRejected("OWNER_APPROVAL_ENVELOPE_REQUIRED")
expected_fields = {
"schema", "approval_id", "repository", "branch", "expected_branch_oid",
"operation", "issued_at", "not_before", "expires_at", "nonce",
"owner_identity", "trust_domain", "key_id", "request_digest",
"idempotency_digest", "signature",
}
if set(value) != expected_fields or value.get("schema") != APPROVAL_SCHEMA:
raise OwnerApprovalRejected("OWNER_APPROVAL_ENVELOPE_REJECTED")
envelope = cls(
approval_id=_require_identifier("approval_id", value["approval_id"]),
repository=_require_identifier("repository", value["repository"]),
branch=str(value["branch"]),
expected_branch_oid=_require_oid("expected_branch_oid", value["expected_branch_oid"]),
operation=str(value["operation"]),
issued_at=str(value["issued_at"]),
not_before=str(value["not_before"]),
expires_at=str(value["expires_at"]),
nonce=_require_identifier("nonce", value["nonce"]),
owner_identity=_require_identifier("owner_identity", value["owner_identity"]),
trust_domain=_require_identifier("trust_domain", value["trust_domain"]),
key_id=_require_identifier("key_id", value["key_id"]),
request_digest=_require_digest("request_digest", value["request_digest"]),
idempotency_digest=_require_digest("idempotency_digest", value["idempotency_digest"]),
signature=str(value["signature"]),
)
envelope.binding.validate()
_parse_utc("issued_at", envelope.issued_at)
_parse_utc("not_before", envelope.not_before)
_parse_utc("expires_at", envelope.expires_at)
if not envelope.signature:
raise OwnerApprovalRejected("OWNER_APPROVAL_SIGNATURE_REJECTED")
return envelope
@dataclass(frozen=True, slots=True)
class TrustRootEvidence:
"""Public-key and revocation evidence from the authoritative trust root."""
key_id: str
owner_identity: str
trust_domain: str
public_key: bytes | None
authority_confirmed: bool
revocation_state: str
class AuthoritativeTrustRootAdapter(Protocol):
def resolve(
self, *, key_id: str, owner_identity: str, trust_domain: str
) -> TrustRootEvidence | None: ...
class ReplayLedger(Protocol):
def consume(
self, approval: OwnerApprovalEnvelope, binding: PublicationApprovalBinding
) -> None: ...
class DurableNonceReplayLedger:
"""Single-use nonce storage that remains conservative across restarts."""
def __init__(self, root: Path) -> None:
self.root = Path(root)
def consume(
self, approval: OwnerApprovalEnvelope, binding: PublicationApprovalBinding
) -> None:
binding.validate()
if self.root.is_symlink():
raise OwnerApprovalRejected("OWNER_APPROVAL_REPLAY_LEDGER_REJECTED")
try:
self.root.mkdir(parents=True, exist_ok=True)
if not self.root.is_dir() or self.root.is_symlink():
raise OwnerApprovalRejected("OWNER_APPROVAL_REPLAY_LEDGER_REJECTED")
name = hashlib.sha256(approval.nonce.encode("utf-8")).hexdigest()
record_path = self.root / name
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(record_path, flags, 0o600)
except FileExistsError as exc:
raise OwnerApprovalRejected("OWNER_APPROVAL_REPLAY_REJECTED") from exc
except OwnerApprovalRejected:
raise
except OSError as exc:
raise OwnerApprovalRejected("OWNER_APPROVAL_REPLAY_LEDGER_REJECTED") from exc
record = {
"schema": "aios.existing-branch-owner-approval-replay.v1",
"nonce_sha256": name,
"approval_fingerprint": approval.fingerprint,
"binding_digest": digest_json(binding.as_dict),
}
try:
with os.fdopen(descriptor, "wb", closefd=True) as stream:
stream.write(canonical_json(record))
stream.flush()
os.fsync(stream.fileno())
directory = os.open(self.root, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(directory)
finally:
os.close(directory)
except OSError as exc:
raise OwnerApprovalRejected("OWNER_APPROVAL_REPLAY_LEDGER_REJECTED") from exc
class OwnerApprovalVerifier:
def __init__(
self,
trust_root: AuthoritativeTrustRootAdapter,
*,
allowed_trust_domains: set[str] | frozenset[str],
maximum_lifetime_seconds: int = 900,
) -> None:
if not allowed_trust_domains or maximum_lifetime_seconds < 1:
raise ValueError("OWNER_APPROVAL_VERIFIER_CONFIGURATION_REJECTED")
self.trust_root = trust_root
self.allowed_trust_domains = frozenset(allowed_trust_domains)
self.maximum_lifetime_seconds = maximum_lifetime_seconds
def verify(
self,
value: object,
binding: PublicationApprovalBinding,
*,
now: datetime | None = None,
) -> OwnerApprovalEnvelope:
binding.validate()
approval = OwnerApprovalEnvelope.from_mapping(value)
if approval.binding != binding:
raise OwnerApprovalRejected("OWNER_APPROVAL_BINDING_REJECTED")
instant = datetime.now(timezone.utc) if now is None else now
if instant.tzinfo is None:
raise OwnerApprovalRejected("OWNER_APPROVAL_TIME_REJECTED")
instant = instant.astimezone(timezone.utc)
issued_at = _parse_utc("issued_at", approval.issued_at)
not_before = _parse_utc("not_before", approval.not_before)
expires_at = _parse_utc("expires_at", approval.expires_at)
if not issued_at <= not_before <= expires_at:
raise OwnerApprovalRejected("OWNER_APPROVAL_TIME_REJECTED")
if not not_before <= instant <= expires_at:
raise OwnerApprovalRejected("OWNER_APPROVAL_TIME_REJECTED")
if (expires_at - issued_at).total_seconds() > self.maximum_lifetime_seconds:
raise OwnerApprovalRejected("OWNER_APPROVAL_TIME_REJECTED")
if approval.trust_domain not in self.allowed_trust_domains:
raise OwnerApprovalRejected("OWNER_APPROVAL_TRUST_DOMAIN_REJECTED")
evidence = self.trust_root.resolve(
key_id=approval.key_id,
owner_identity=approval.owner_identity,
trust_domain=approval.trust_domain,
)
if evidence is None or not evidence.authority_confirmed:
raise OwnerApprovalRejected("OWNER_APPROVAL_TRUST_ROOT_EVIDENCE_MISSING")
if (
evidence.key_id != approval.key_id
or evidence.owner_identity != approval.owner_identity
or evidence.trust_domain != approval.trust_domain
):
raise OwnerApprovalRejected("OWNER_APPROVAL_TRUST_ROOT_BINDING_REJECTED")
if evidence.revocation_state == "REVOKED":
raise OwnerApprovalRejected("OWNER_APPROVAL_REVOKED")
if evidence.revocation_state != "ACTIVE":
raise OwnerApprovalRejected("OWNER_APPROVAL_REVOCATION_EVIDENCE_MISSING")
if evidence.public_key is None or len(evidence.public_key) != 32:
raise OwnerApprovalRejected("OWNER_APPROVAL_TRUST_ROOT_EVIDENCE_MISSING")
try:
signature = base64.b64decode(approval.signature, validate=True)
Ed25519PublicKey.from_public_bytes(evidence.public_key).verify(
signature, approval.canonical_bytes()
)
except (ValueError, TypeError, InvalidSignature) as exc:
raise OwnerApprovalRejected("OWNER_APPROVAL_SIGNATURE_REJECTED") from exc
return approval
@@ -0,0 +1,331 @@
"""Narrow governed publisher for an already-existing task branch.
Its only writing port is a compare-and-swap publication after a complete
signed Owner approval, a stable remote observation, and a single-use nonce
claim. It neither owns trust material nor accepts direct reference strings.
"""
from __future__ import annotations
from dataclasses import dataclass
import re
from typing import Any, Mapping, Protocol
from aios_existing_branch_approval import (
OwnerApprovalEnvelope,
OwnerApprovalRejected,
OwnerApprovalVerifier,
PublicationApprovalBinding,
ReplayLedger,
canonical_json,
digest_json,
)
REQUEST_SCHEMA = "aios.existing-branch-publication.v1"
PUBLISH_OPERATION = "publishExistingBranchGoverned"
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 ExistingBranchPublicationRejected(RuntimeError):
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
class RemoteOidConflict(RuntimeError):
pass
class RemoteObservationAmbiguous(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class ExistingBranchPublicationRequest:
repository: str
branch: str
expected_branch_oid: str
idempotency_key: str
payload: Mapping[str, Any]
@classmethod
def from_mapping(cls, value: object) -> "ExistingBranchPublicationRequest":
if not isinstance(value, Mapping):
raise ExistingBranchPublicationRejected("PUBLICATION_REQUEST_REJECTED")
expected_fields = {
"schema",
"repository",
"branch",
"expected_branch_oid",
"operation",
"idempotency_key",
"payload",
}
if set(value) != expected_fields or value.get("schema") != REQUEST_SCHEMA:
raise ExistingBranchPublicationRejected("PUBLICATION_REQUEST_REJECTED")
repository = value["repository"]
branch = value["branch"]
expected_oid = value["expected_branch_oid"]
key = value["idempotency_key"]
payload = value["payload"]
if (
not isinstance(repository, str)
or repository != "aios/aios"
or not BRANCH_RE.fullmatch(str(branch))
or not isinstance(expected_oid, str)
or not OID_RE.fullmatch(expected_oid)
or value.get("operation") != PUBLISH_OPERATION
or not isinstance(key, str)
or not key.strip()
or not isinstance(payload, Mapping)
):
raise ExistingBranchPublicationRejected("PUBLICATION_REQUEST_REJECTED")
try:
canonical_json(payload)
except OwnerApprovalRejected as exc:
raise ExistingBranchPublicationRejected("PUBLICATION_PAYLOAD_REJECTED") from exc
return cls(repository, str(branch), expected_oid, key, dict(payload))
@property
def request_digest(self) -> str:
return digest_json(
{
"schema": REQUEST_SCHEMA,
"repository": self.repository,
"branch": self.branch,
"expected_branch_oid": self.expected_branch_oid,
"operation": PUBLISH_OPERATION,
"payload": self.payload,
}
)
@property
def idempotency_digest(self) -> str:
return digest_json(
{
"idempotency_key": self.idempotency_key,
"request_digest": self.request_digest,
}
)
@property
def approval_binding(self) -> PublicationApprovalBinding:
return PublicationApprovalBinding(
repository=self.repository,
branch=self.branch,
expected_branch_oid=self.expected_branch_oid,
operation=PUBLISH_OPERATION,
request_digest=self.request_digest,
idempotency_digest=self.idempotency_digest,
)
@dataclass(frozen=True, slots=True)
class RemoteBranchObservation:
state: str
git_oid: str | None
api_oid: str | None
expected_commit_visible: bool | None
@dataclass(frozen=True, slots=True)
class RemotePublicationEvidence:
authority_oid: str | None
gitea_git_oid: str | None
gitea_api_oid: str | None
commit_visible: bool | None
class ExistingBranchRemotePort(Protocol):
def observe(self, branch: str) -> RemoteBranchObservation: ...
def publish_compare_and_swap(
self, branch: str, expected_branch_oid: str, commit_oid: str
) -> RemotePublicationEvidence: ...
class AuthorityBranchPort(Protocol):
def read_branch_oid(self, branch: str) -> str | None: ...
class CredentialProjection(Protocol):
def project(self) -> None: ...
def revoke(self) -> None: ...
class PublicationWorkspace(Protocol):
def head_oid(self) -> str: ...
def commit_payload(self, payload: Mapping[str, Any]) -> str: ...
class DetachedWorkspaceFactory(Protocol):
def open_detached(self, expected_branch_oid: str) -> PublicationWorkspace: ...
class AuditSink(Protocol):
def record(self, event: Mapping[str, Any]) -> None: ...
class ExistingBranchPublisher:
"""Validate once, observe once, then make one bounded publication attempt."""
def __init__(
self,
*,
verifier: OwnerApprovalVerifier,
replay_ledger: ReplayLedger,
authority: AuthorityBranchPort,
remote: ExistingBranchRemotePort,
credentials: CredentialProjection,
workspaces: DetachedWorkspaceFactory,
audit: AuditSink,
) -> None:
self.verifier = verifier
self.replay_ledger = replay_ledger
self.authority = authority
self.remote = remote
self.credentials = credentials
self.workspaces = workspaces
self.audit = audit
def _audit(
self,
*,
status: str,
request: ExistingBranchPublicationRequest,
approval: OwnerApprovalEnvelope | None = None,
commit_oid: str | None = None,
) -> None:
event: dict[str, Any] = {
"event": "governed_existing_branch_publication",
"status": status,
"repository": request.repository,
"branch": request.branch,
"expected_branch_oid": request.expected_branch_oid,
"operation": PUBLISH_OPERATION,
"request_digest": request.request_digest,
"idempotency_digest": request.idempotency_digest,
}
if approval is not None:
event.update(
{
"approval_id": approval.approval_id,
"owner_identity": approval.owner_identity,
"trust_domain": approval.trust_domain,
"approval_fingerprint": approval.fingerprint,
}
)
if commit_oid is not None:
event["commit_oid"] = commit_oid
self.audit.record(event)
def _preflight_remote(
self, request: ExistingBranchPublicationRequest
) -> RemoteBranchObservation | dict[str, str]:
try:
authority_oid = self.authority.read_branch_oid(request.branch)
except Exception:
return {"status": "RECONCILIATION_REQUIRED"}
if authority_oid != request.expected_branch_oid:
return {"status": "MANUAL_OWNER_REVIEW_REQUIRED"}
try:
observation = self.remote.observe(request.branch)
except RemoteObservationAmbiguous:
return {"status": "RECONCILIATION_REQUIRED"}
if observation.state == "AMBIGUOUS":
return {"status": "RECONCILIATION_REQUIRED"}
if observation.state != "PRESENT":
return {"status": "MANUAL_OWNER_REVIEW_REQUIRED"}
if (
observation.git_oid is not None
and observation.api_oid is not None
and observation.git_oid != observation.api_oid
):
return {"status": "RECONCILIATION_REQUIRED"}
if (
observation.git_oid != request.expected_branch_oid
or observation.api_oid != request.expected_branch_oid
):
return {"status": "MANUAL_OWNER_REVIEW_REQUIRED"}
if observation.expected_commit_visible is not True:
return {"status": "RECONCILIATION_REQUIRED"}
return observation
def publish(
self, request_value: object, approval_value: object
) -> dict[str, str | bool]:
request = ExistingBranchPublicationRequest.from_mapping(request_value)
try:
approval = self.verifier.verify(approval_value, request.approval_binding)
except OwnerApprovalRejected:
self._audit(status="REJECTED", request=request)
raise
preflight = self._preflight_remote(request)
if isinstance(preflight, dict):
self._audit(status=preflight["status"], request=request, approval=approval)
return preflight
self.replay_ledger.consume(approval, request.approval_binding)
workspace = self.workspaces.open_detached(request.expected_branch_oid)
if workspace.head_oid() != request.expected_branch_oid:
self._audit(status="REJECTED", request=request, approval=approval)
raise ExistingBranchPublicationRejected("DETACHED_WORKSPACE_HEAD_REJECTED")
projected = False
try:
self.credentials.project()
projected = True
commit_oid = workspace.commit_payload(request.payload)
if not isinstance(commit_oid, str) or not OID_RE.fullmatch(commit_oid):
raise ExistingBranchPublicationRejected("PUBLICATION_COMMIT_REJECTED")
try:
evidence = self.remote.publish_compare_and_swap(
request.branch, request.expected_branch_oid, commit_oid
)
except RemoteOidConflict:
self._audit(
status="MANUAL_OWNER_REVIEW_REQUIRED",
request=request,
approval=approval,
commit_oid=commit_oid,
)
return {"status": "MANUAL_OWNER_REVIEW_REQUIRED"}
except RemoteObservationAmbiguous:
self._audit(
status="RECONCILIATION_REQUIRED",
request=request,
approval=approval,
commit_oid=commit_oid,
)
return {"status": "RECONCILIATION_REQUIRED"}
if (
evidence.authority_oid != commit_oid
or evidence.gitea_git_oid != commit_oid
or evidence.gitea_api_oid != commit_oid
or evidence.commit_visible is not True
):
self._audit(
status="RECONCILIATION_REQUIRED",
request=request,
approval=approval,
commit_oid=commit_oid,
)
return {"status": "RECONCILIATION_REQUIRED"}
self._audit(
status="PUBLISHED",
request=request,
approval=approval,
commit_oid=commit_oid,
)
return {
"status": "PUBLISHED",
"commit_oid": commit_oid,
"authority_oid": evidence.authority_oid,
"gitea_git_oid": evidence.gitea_git_oid,
"gitea_api_oid": evidence.gitea_api_oid,
"commit_visible": evidence.commit_visible,
}
finally:
if projected:
self.credentials.revoke()
@@ -0,0 +1,285 @@
from __future__ import annotations
import base64
from datetime import datetime, timedelta, timezone
import json
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import pytest
from aios_existing_branch_approval import (
DurableNonceReplayLedger,
OwnerApprovalEnvelope,
OwnerApprovalRejected,
OwnerApprovalVerifier,
TrustRootEvidence,
)
from aios_existing_branch_publisher import (
ExistingBranchPublicationRejected,
ExistingBranchPublisher,
RemoteBranchObservation,
RemotePublicationEvidence,
)
BASE_OID = "a" * 40
COMMIT_OID = "b" * 40
BRANCH = "agent/owner-approval-contract-20260810"
class TrustRoot:
def __init__(self, public_key: bytes, *, revocation: str = "ACTIVE") -> None:
self.public_key = public_key
self.revocation = revocation
self.calls: list[str] = []
def resolve(self, *, key_id: str, owner_identity: str, trust_domain: str):
self.calls.append("trust-root")
return TrustRootEvidence(
key_id=key_id,
owner_identity=owner_identity,
trust_domain=trust_domain,
public_key=self.public_key,
authority_confirmed=True,
revocation_state=self.revocation,
)
class Credentials:
def __init__(self, events: list[str]) -> None:
self.events = events
def project(self) -> None:
self.events.append("credential-project")
def revoke(self) -> None:
self.events.append("credential-revoke")
class Workspace:
def __init__(self, events: list[str]) -> None:
self.events = events
def head_oid(self) -> str:
self.events.append("workspace-head")
return BASE_OID
def commit_payload(self, payload):
self.events.append("workspace-commit")
return COMMIT_OID
class Workspaces:
def __init__(self, events: list[str]) -> None:
self.events = events
def open_detached(self, expected_branch_oid: str) -> Workspace:
assert expected_branch_oid == BASE_OID
self.events.append("workspace-open")
return Workspace(self.events)
class Remote:
def __init__(self, events: list[str]) -> None:
self.events = events
self.observation = RemoteBranchObservation("PRESENT", BASE_OID, BASE_OID, True)
self.authority_oid = BASE_OID
self.pushes: list[tuple[str, str, str]] = []
def read_branch_oid(self, branch: str) -> str:
self.events.append("authority-observe")
assert branch == BRANCH
return self.authority_oid
def observe(self, branch: str) -> RemoteBranchObservation:
self.events.append("remote-observe")
assert branch == BRANCH
return self.observation
def publish_compare_and_swap(self, branch: str, expected: str, commit: str):
self.events.append("remote-publish")
self.pushes.append((branch, expected, commit))
return RemotePublicationEvidence(COMMIT_OID, COMMIT_OID, COMMIT_OID, True)
class Audit:
def __init__(self) -> None:
self.rows: list[dict] = []
def record(self, event) -> None:
self.rows.append(dict(event))
def request(*, payload=None, branch=BRANCH, expected_oid=BASE_OID):
return {
"schema": "aios.existing-branch-publication.v1",
"repository": "aios/aios",
"branch": branch,
"expected_branch_oid": expected_oid,
"operation": "publishExistingBranchGoverned",
"idempotency_key": "owner-approval-binding-20260810",
"payload": {"files": ["active-runtime/app/example.py"]} if payload is None else payload,
}
def signed_approval(private: Ed25519PrivateKey, request_value, **replacements):
from aios_existing_branch_publisher import ExistingBranchPublicationRequest
parsed = ExistingBranchPublicationRequest.from_mapping(request_value)
now = datetime.now(timezone.utc)
raw = {
"schema": "aios.existing-branch-owner-approval.v1",
"approval_id": "approval-owner-binding-20260810",
"repository": parsed.repository,
"branch": parsed.branch,
"expected_branch_oid": parsed.expected_branch_oid,
"operation": "publishExistingBranchGoverned",
"issued_at": (now - timedelta(seconds=30)).isoformat(),
"not_before": (now - timedelta(seconds=1)).isoformat(),
"expires_at": (now + timedelta(minutes=5)).isoformat(),
"nonce": "nonce-owner-binding-20260810",
"owner_identity": "owner.aios",
"trust_domain": "aios-owner-root",
"key_id": "owner-key-20260810",
"request_digest": parsed.request_digest,
"idempotency_digest": parsed.idempotency_digest,
"signature": "AA==",
}
raw.update(replacements)
envelope = OwnerApprovalEnvelope.from_mapping(raw)
raw["signature"] = base64.b64encode(private.sign(envelope.canonical_bytes())).decode("ascii")
return raw
def service(tmp_path, *, revocation="ACTIVE"):
private = Ed25519PrivateKey.generate()
public_key = private.public_key().public_bytes(
serialization.Encoding.Raw, serialization.PublicFormat.Raw
)
root = TrustRoot(public_key, revocation=revocation)
events: list[str] = []
remote = Remote(events)
audit = Audit()
publisher = ExistingBranchPublisher(
verifier=OwnerApprovalVerifier(root, allowed_trust_domains={"aios-owner-root"}),
replay_ledger=DurableNonceReplayLedger(tmp_path / "replay"),
authority=remote,
remote=remote,
credentials=Credentials(events),
workspaces=Workspaces(events),
audit=audit,
)
return private, root, events, remote, audit, publisher
def test_valid_signed_owner_approval_is_verified_before_capability_projection(tmp_path):
private, root, events, remote, audit, publisher = service(tmp_path)
value = request()
approval = signed_approval(private, value)
result = publisher.publish(value, approval)
assert result["status"] == "PUBLISHED"
assert result["commit_oid"] == COMMIT_OID
assert remote.pushes == [(BRANCH, BASE_OID, COMMIT_OID)]
assert events == [
"authority-observe",
"remote-observe",
"workspace-open",
"workspace-head",
"credential-project",
"workspace-commit",
"remote-publish",
"credential-revoke",
]
assert root.calls == ["trust-root"]
assert approval["signature"] not in json.dumps(audit.rows)
assert "signature" not in audit.rows[-1]
@pytest.mark.parametrize(
"case",
[
"invalid_signature",
"expired",
"revoked",
"missing_key_evidence",
"missing_revocation_evidence",
"wrong_repository",
"wrong_branch",
"wrong_oid",
"wrong_operation",
"arbitrary_reference",
],
)
def test_invalid_owner_approval_inputs_are_rejected_without_publication(tmp_path, case):
private, root, events, remote, _, publisher = service(
tmp_path, revocation="REVOKED" if case == "revoked" else "ACTIVE"
)
value = request()
approval = signed_approval(private, value)
if case == "invalid_signature":
approval["signature"] = "AAAA"
elif case == "expired":
approval = signed_approval(
private,
value,
issued_at="2000-01-01T00:00:00+00:00",
not_before="2000-01-01T00:00:01+00:00",
expires_at="2000-01-01T00:01:00+00:00",
)
elif case == "wrong_repository":
approval = signed_approval(private, value, repository="other/repository")
elif case == "wrong_branch":
approval = signed_approval(private, value, branch="agent/another-approved-branch")
elif case == "wrong_oid":
approval = signed_approval(private, value, expected_branch_oid="c" * 40)
elif case == "wrong_operation":
approval["operation"] = "differentOperation"
elif case == "arbitrary_reference":
approval = "owner-approval-reference-123"
elif case == "missing_key_evidence":
root.public_key = None
elif case == "missing_revocation_evidence":
root.revocation = "UNKNOWN"
with pytest.raises((OwnerApprovalRejected, ExistingBranchPublicationRejected)):
publisher.publish(value, approval)
assert remote.pushes == []
assert "credential-project" not in events
assert "workspace-open" not in events
@pytest.mark.parametrize(
"changed",
[
lambda value: {**value, "branch": "agent/approval-replay-other-branch"},
lambda value: {**value, "expected_branch_oid": "c" * 40},
lambda value: {**value, "payload": {"files": ["other.py"]}},
],
)
def test_signed_approval_cannot_be_replayed_for_another_branch_oid_or_payload(tmp_path, changed):
private, _, events, remote, _, publisher = service(tmp_path)
original = request()
approval = signed_approval(private, original)
with pytest.raises(OwnerApprovalRejected, match="OWNER_APPROVAL_BINDING_REJECTED"):
publisher.publish(changed(original), approval)
assert remote.pushes == []
assert events == []
def test_signed_approval_nonce_is_single_use(tmp_path):
private, _, events, remote, _, publisher = service(tmp_path)
value = request()
approval = signed_approval(private, value)
assert publisher.publish(value, approval)["status"] == "PUBLISHED"
with pytest.raises(OwnerApprovalRejected, match="OWNER_APPROVAL_REPLAY_REJECTED"):
publisher.publish(value, approval)
assert len(remote.pushes) == 1
assert events.count("credential-project") == 1
@@ -0,0 +1,72 @@
from __future__ import annotations
import inspect
from aios_existing_branch_publisher import (
ExistingBranchPublisher,
RemoteBranchObservation,
)
from test_existing_branch_owner_approval import (
BASE_OID,
COMMIT_OID,
request,
service,
signed_approval,
)
def test_remote_oid_conflict_requires_manual_owner_review_before_capability_projection(tmp_path):
private, _, events, remote, _, publisher = service(tmp_path)
remote.observation = RemoteBranchObservation("PRESENT", "c" * 40, "c" * 40, True)
value = request()
result = publisher.publish(value, signed_approval(private, value))
assert result == {"status": "MANUAL_OWNER_REVIEW_REQUIRED"}
assert events == ["authority-observe", "remote-observe"]
assert remote.pushes == []
def test_ambiguous_remote_requires_reconciliation_after_one_observation(tmp_path):
private, _, events, remote, _, publisher = service(tmp_path)
remote.observation = RemoteBranchObservation("AMBIGUOUS", None, None, None)
value = request()
result = publisher.publish(value, signed_approval(private, value))
assert result == {"status": "RECONCILIATION_REQUIRED"}
assert events == ["authority-observe", "remote-observe"]
assert remote.pushes == []
def test_authority_oid_conflict_requires_manual_owner_review(tmp_path):
private, _, events, remote, _, publisher = service(tmp_path)
remote.authority_oid = "c" * 40
value = request()
result = publisher.publish(value, signed_approval(private, value))
assert result == {"status": "MANUAL_OWNER_REVIEW_REQUIRED"}
assert events == ["authority-observe"]
assert remote.pushes == []
def test_success_reports_all_remote_agreement_fields(tmp_path):
private, _, _, _, _, publisher = service(tmp_path)
value = request()
result = publisher.publish(value, signed_approval(private, value))
assert result == {
"status": "PUBLISHED",
"commit_oid": COMMIT_OID,
"authority_oid": COMMIT_OID,
"gitea_git_oid": COMMIT_OID,
"gitea_api_oid": COMMIT_OID,
"commit_visible": True,
}
def test_publisher_exposes_only_compare_and_swap_publication_path():
source = inspect.getsource(ExistingBranchPublisher).lower()
assert "force" not in source