Compare commits

..
2 Commits
53 changed files with 10972 additions and 59 deletions
@@ -21,7 +21,7 @@ import sqlite3
from typing import Any, Callable, Iterable, Mapping
import uuid
from aios_capability_contract import has_capabilities
from aios_capability_contract import has_capabilities, normalize_required_capabilities
TERMINAL_STATES = frozenset({"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"})
@@ -399,6 +399,9 @@ class TaskStore:
raise TaskProtocolError("DEADLINE_INVALID", 400)
criteria = _criteria(success_criteria)
required = _string_list(required_capabilities, "required_capabilities")
normalized_required = normalize_required_capabilities(required)
if any(value.startswith("exact:") for value in normalized_required):
raise TaskProtocolError("TASK_CAPABILITY_UNKNOWN", 403)
constraints_value = _json_object(constraints, "constraints")
metadata_value = _json_object(metadata, "metadata")
payload = {
+36 -7
View File
@@ -6,7 +6,7 @@ The built-in host adapter is READ_ONLY. Mutating operations require separately
registered governed adapters with receiver fencing/readback, never arbitrary argv.
"""
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import dataclass, replace
from contextlib import contextmanager
from datetime import datetime,timezone
import base64
@@ -18,7 +18,7 @@ import time
from pathlib import Path
from typing import Mapping
from task_plane.models import TaskPlan,WorkerRegistration,ProbeEvidence,ProbeOutcome,Budget
from task_plane.models import TaskPlan,WorkerRegistration,ProbeEvidence,ProbeOutcome,Budget,EvidenceEnvelope
from task_plane.errors import TaskPlaneHTTPError
from aios_gitops_job_controller import GitOpsJobController,JobStore,JobState,JobError
from .evidence import ReceiptSigner,canonical,digest
@@ -69,6 +69,7 @@ class BaselineService:
self.store.conn.execute('''CREATE TABLE IF NOT EXISTS baseline_worker_policy(
worker_id TEXT PRIMARY KEY,policy_json TEXT NOT NULL)''')
self.store.probe_attestation_verifier=self._verify_probe
self.store.evidence_attestation_verifier=self._verify_task_evidence
def _user(self,identity):
if not isinstance(identity,Identity) or identity.role not in {'user','owner'}:
@@ -379,11 +380,39 @@ class BaselineService:
def _close_task(self,task,claim,envelope):
if not self._receipt_valid(task,envelope): raise ServiceError('RESULT_ATTESTATION_INVALID',403)
current=self.store.get_task(task.task_id)
if current.status=='CANCELLED': raise ServiceError('TASK_CANCELLED')
if current.status=='CLAIMED': self.store.update_task_status(task.task_id,current.revision,'RUNNING')
self.store.complete_claim(task.task_id,claim.claim_id,'COMPLETED',envelope,
evidence_entries=[('diagnostic',envelope,claim.worker_identity)])
with self.store._transaction():
current=self.store.get_task(task.task_id)
if current.status=='CANCELLED': raise ServiceError('TASK_CANCELLED')
if current.status=='CLAIMED': self.store.update_task_status(task.task_id,current.revision,'RUNNING')
evidence=EvidenceEnvelope.create(task_id=task.task_id,claim_id=claim.claim_id,
kind='diagnostic',worker_identity=claim.worker_identity,payload=envelope,
previous_evidence_hash=self.store._last_evidence_hash(task.task_id),
attestation_ref='baseline-machine:'+self.signer.key_id)
proof=canonical({'domain':'aios.baseline.task-evidence.v1',
'key_id':self.signer.key_id,'evidence':evidence.digest_payload()})
evidence=replace(evidence,signature=base64.b64encode(self.signer.private_key.sign(proof)).decode())
self.store.complete_claim_with_evidence_and_event(task.task_id,claim.claim_id,
'COMPLETED',envelope,[evidence])
def _verify_task_evidence(self,evidence):
"""Verify the TaskPlane wrapper and its separately signed operation receipt."""
try:
if not isinstance(evidence,EvidenceEnvelope) or evidence.kind!='diagnostic': return False
prefix='baseline-machine:'
if not evidence.attestation_ref or not evidence.attestation_ref.startswith(prefix): return False
key_id=evidence.attestation_ref[len(prefix):]
key=self.signer.trusted_public_keys.get(key_id)
if key is None or not evidence.signature: return False
proof=canonical({'domain':'aios.baseline.task-evidence.v1',
'key_id':key_id,'evidence':evidence.digest_payload()})
key.verify(base64.b64decode(evidence.signature,validate=True),proof)
claim=self.store.get_claim(evidence.claim_id)
task=self.store.get_task(evidence.task_id)
if claim.task_id!=task.task_id or claim.worker_identity!=evidence.worker_identity: return False
if claim.operation_digest!=task.task_plan.operation_digest(): return False
return self._receipt_valid(task,evidence.payload)
except Exception:
return False
def cancel_task(self,identity,task_id):
self._user(identity)
@@ -0,0 +1,133 @@
"""Canonical fail-closed capability semantics for formal AgentTask eligibility."""
from __future__ import annotations
from collections.abc import Iterable
# These aliases are the verified C4 contract captured in
# ``qa/capability-contract.txt``. Unknown task names become exact requirements;
# unknown worker names are never granted.
_TASK_ALIASES: dict[str, frozenset[str]] = {
"release:candidate": frozenset({"release:candidate"}),
"repository_read": frozenset({"repo.read"}),
"git.read": frozenset({"repo.read"}),
"repo.read": frozenset({"repo.read"}),
"test_execution": frozenset({"test.execute"}),
"git.test": frozenset({"test.execute"}),
"test.execute": frozenset({"test.execute"}),
"governed_git_write": frozenset({"governed.git.write.branch", "governed.git.write.file"}),
"governed.git.write": frozenset({"governed.git.write.branch", "governed.git.write.file"}),
"governed.write.branch": frozenset({"governed.git.write.branch"}),
"governed.write.file": frozenset({"governed.git.write.file"}),
"shell": frozenset({"runtime.shell"}),
"git": frozenset({"runtime.git"}),
"python": frozenset({"runtime.python"}),
"code.read": frozenset({"legacy.code.read"}),
"code.review": frozenset({"legacy.code.review"}),
"code.write": frozenset({"legacy.code.write"}),
"code.write.patch": frozenset({"legacy.code.write.patch"}),
"test.python": frozenset({"legacy.test.python"}),
"test.shell.allowlisted": frozenset({"legacy.test.shell.allowlisted"}),
"git.commit": frozenset({"legacy.git.commit"}),
"git.diff.read": frozenset({"legacy.git.diff.read"}),
"git.force_push": frozenset({"legacy.git.force_push"}),
"deploy.production": frozenset({"legacy.deploy.production"}),
"secret.transform": frozenset({"legacy.secret.transform"}),
"secure_file_transformer": frozenset({"legacy.secure_file_transformer"}),
"runtime.shadow": frozenset({"legacy.runtime.shadow"}),
"review.security": frozenset({"legacy.review.security"}),
"review.api-contract": frozenset({"legacy.review.api-contract"}),
"gpu.train": frozenset({"legacy.gpu.train"}),
}
_WORKER_ALIASES = {
key: value for key, value in _TASK_ALIASES.items()
if key not in {"release:candidate", "governed_git_write", "governed.git.write"}
}
_WORKER_ALIASES.update({
"release:candidate": frozenset({"release:candidate"}),
"governed.git.write.branch": frozenset({"governed.git.write.branch"}),
"governed.git.write.file": frozenset({"governed.git.write.file"}),
})
_EXACT_LIFECYCLE_CAPABILITIES = frozenset(
{
"agent-task:control",
"governed.task.create",
"governed.write.files",
"production.plan.apply",
"production.promote",
"production.rollback",
"task.principal.issue",
"testing",
"verification",
"writer.lease",
}
)
for _capability in _EXACT_LIFECYCLE_CAPABILITIES:
_TASK_ALIASES[_capability] = frozenset({_capability})
_WORKER_ALIASES[_capability] = frozenset({_capability})
_UNKNOWN_PREFIX = "exact:"
def _clean(value: object) -> str:
if not isinstance(value, str):
raise ValueError("capability must be a string")
text = value.strip().lower()
if not text:
raise ValueError("capability must not be empty")
return text
def normalize_required_capabilities(values: Iterable[object]) -> frozenset[str]:
result: set[str] = set()
for value in values:
text = _clean(value)
if "*" in text:
result.add(_UNKNOWN_PREFIX + text)
else:
result.update(_TASK_ALIASES.get(text, frozenset({_UNKNOWN_PREFIX + text})))
return frozenset(result)
def normalize_worker_capabilities(values: Iterable[object]) -> frozenset[str]:
result: set[str] = set()
for value in values:
text = _clean(value)
if "*" in text:
continue
mapped = _WORKER_ALIASES.get(text)
if mapped is not None:
result.update(mapped)
if {"governed.git.write.branch", "governed.git.write.file"}.issubset(result):
result.add("governed.git.write")
return frozenset(result)
def has_capabilities(worker_capabilities: Iterable[object], required_capabilities: Iterable[object]) -> bool:
required = normalize_required_capabilities(required_capabilities)
effective = normalize_worker_capabilities(worker_capabilities)
if any(value.startswith(_UNKNOWN_PREFIX) for value in required):
return False
return required.issubset(effective)
def required_capabilities_subset(granted_requirements: Iterable[object], required_requirements: Iterable[object]) -> bool:
granted = normalize_required_capabilities(granted_requirements)
required = normalize_required_capabilities(required_requirements)
if any(value.startswith(_UNKNOWN_PREFIX) for value in granted | required):
return False
return required.issubset(granted)
def normalized_capability_matrix(worker_capabilities: Iterable[object], required_capabilities: Iterable[object]) -> dict[str, object]:
worker_values = tuple(worker_capabilities)
required_values = tuple(required_capabilities)
worker = normalize_worker_capabilities(worker_values)
required = normalize_required_capabilities(required_values)
return {
"worker_effective": sorted(worker),
"task_required": sorted(required),
"eligible": has_capabilities(worker_values, required_values),
}
@@ -1342,21 +1342,39 @@ def main(
BridgeHandler.principal_verifier = task_principal_verifier
if task_admission_handlers is None:
admission_runtime = None
if os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_environment,
source_activation = os.environ.get("AIOS_SOURCE_PUBLICATION_ACTIVATION_FILE", "").strip()
try:
if os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_environment,
)
admission_runtime = build_task_admission_runtime_from_environment(
post_admission=runtime.project_admitted_task,
)
elif activation_raw and Path(activation_raw).is_file():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_activation_file(
Path(activation_raw),
post_admission=runtime.project_admitted_task,
)
except TaskAdmissionError as exc:
if not source_activation or str(exc) != "TASK_ADMISSION_EPOCH_EXPIRED":
raise
from source_publication_runtime import expired_admission_runtime
admission_runtime = expired_admission_runtime()
if source_activation:
from source_publication_runtime import (
AdmissionRuntimeRouter, build_source_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_environment(
post_admission=runtime.project_admitted_task,
)
elif activation_raw and Path(activation_raw).is_file():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_activation_file(
Path(activation_raw),
post_admission=runtime.project_admitted_task,
if admission_runtime is None or task_principal_verifier is not None:
raise RuntimeError("SOURCE_ACTIVATION_ORDINARY_RUNTIME_REQUIRED")
source_runtime = build_source_runtime_from_activation_file(
Path(source_activation), post_admission=runtime.project_admitted_task,
ordinary_activation_file=Path(activation_raw),
)
admission_runtime = AdmissionRuntimeRouter(admission_runtime, source_runtime).bundle()
if admission_runtime is not None:
task_admission_handlers = admission_runtime.handlers
if BridgeHandler.principal_verifier is None:
@@ -51,15 +51,30 @@ AUTHORIZATION_BUNDLE_SCHEMA = "aios.owner-authorization-bundle.v1"
MACHINE_CAPABILITY_SCHEMA = "aios.machine-derived-capability.v1"
BOOTSTRAP_STATE_SCHEMA = "aios.single-host-bootstrap-state.v1"
OWNER_EPOCH_SCHEMA = "aios.owner-delegation-epoch.v2"
SOURCE_PUBLICATION_POLICY_SCHEMA = "aios.source-publication-policy.v1"
SOURCE_PUBLICATION_POLICY_ID = "C5_SOURCE_PUBLICATION_ONLY"
SOURCE_PUBLICATION_CAPABILITIES = frozenset(
{
"agent-task:control",
"governed.task.create",
"governed.write.files",
"task.principal.issue",
"writer.lease",
}
)
DEFAULT_REGISTRY_PATH = Path("/etc/aios/single-host-signer-registry.json")
DEFAULT_OWNER_SIGNER_ID = "local-owner-authority"
DEFAULT_MACHINE_SIGNER_ID = "machine-operational"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_CAP = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$")
_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/-]{2,255}$")
_RISK = {"low": 0, "medium": 1, "high": 2}
# ``engineering`` is deliberately between medium and high. Adding this rank
# does not raise any configured ceiling; existing policy files keep their
# existing value (the normal local-owner policy is ``medium``).
_RISK = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
_MAX_JSON = 128 * 1024
_MAX_KEY = 64 * 1024
_MAGIC = b"SSHSIG"
@@ -691,6 +706,30 @@ class OwnerAuthorizationBundle:
return asdict(self)
@dataclass(frozen=True, slots=True)
class SourcePublicationPolicyRequest:
"""One exact, source-only unattended publication authorization request."""
policy_id: str
repository: str
authority_base_oid: str
source_oid: str
source_tree_oid: str
target_agent: str
target_branch: str
generation: int
execution_digest: str
exact_files: tuple[str, ...]
nonce: str
ttl_seconds: int
risk: str = "engineering"
production_impact: str = "none"
renewal: str = "none"
def __post_init__(self) -> None:
object.__setattr__(self, "exact_files", tuple(self.exact_files))
@dataclass(frozen=True, slots=True)
class MachineCapabilityRequest:
capability_id: str
@@ -755,20 +794,28 @@ class LocalOwnerAuthority(LocalFileSigner):
def __init__(
self,
key_path: Path,
key_path: Path | None,
policy: SingleHostOwnerPolicy,
*,
require_root_owner: bool = True,
clock: Any = time.time,
registered_signer: Any | None = None,
) -> None:
super().__init__(
key_path,
key_path or Path("/dev/null"),
signer_id=DEFAULT_OWNER_SIGNER_ID,
namespace=OWNER_NAMESPACE,
require_root_owner=require_root_owner,
)
self.policy = policy
self.clock = clock
self.registered_signer = registered_signer
@property
def fingerprint(self) -> str:
if self.registered_signer is not None:
return str(self.registered_signer.fingerprint)
return super().fingerprint
@staticmethod
def _prefix_narrows(requested: str, allowed: Sequence[str]) -> bool:
@@ -831,6 +878,111 @@ class LocalOwnerAuthority(LocalFileSigner):
value["canonical_payload_digest"] = sha256_json(value)
return self._sign_canonical(value)
def _sign_canonical(self, value: Mapping[str, Any]) -> SignedArtifact:
if self.registered_signer is None:
return super()._sign_canonical(value)
payload = canonical_json_bytes(dict(value))
signature = self.registered_signer._sign_canonical_payload(
self.namespace, payload
)
return SignedArtifact(
payload=dict(value),
canonical_payload=payload,
signature=signature,
key_id=self.fingerprint,
namespace=self.namespace,
)
def issue_source_publication_policy(
self, request: SourcePublicationPolicyRequest
) -> SignedArtifact:
"""Sign one exact C5 source-publication policy.
This is an issuance operation only. It cannot change the configured
owner policy, its ceiling, branch/file allowlists, or trust domain.
"""
now = int(self.clock())
files = tuple(request.exact_files)
def source_scope_narrows(value: str, allowed: Sequence[str]) -> bool:
base = value.rstrip("/")
return any(
base == prefix.rstrip("/")
or base.startswith(prefix.rstrip("/") + "/")
for prefix in allowed
)
if (
request.policy_id != SOURCE_PUBLICATION_POLICY_ID
or request.repository != self.policy.repository
or not _HEX40.fullmatch(request.authority_base_oid)
or not _HEX40.fullmatch(request.source_oid)
or not _HEX40.fullmatch(request.source_tree_oid)
or not _ID.fullmatch(request.target_agent)
or not request.target_agent.startswith("agent/")
or not source_scope_narrows(
request.target_branch, self.policy.allowed_branch_prefixes
)
or not request.target_branch.startswith("agent/")
or "\\" in request.target_branch
or ".." in PurePosixPath(request.target_branch).parts
or request.target_branch.endswith(("/", "."))
or type(request.generation) is not int
or request.generation < 1
or not _HEX64.fullmatch(request.execution_digest)
or not files
or any(
not item
or PurePosixPath(item).is_absolute()
or "\\" in item
or ".." in PurePosixPath(item).parts
or item.startswith(".git")
or not source_scope_narrows(item, self.policy.allowed_file_prefixes)
for item in files
)
or not SOURCE_PUBLICATION_CAPABILITIES.issubset(self.policy.allowed_capabilities)
or len(set(files)) != len(files)
or not _ID.fullmatch(request.nonce)
or request.risk != "engineering"
or request.risk not in _RISK
or _RISK[request.risk] > _RISK[self.policy.risk_ceiling]
or request.production_impact != "none"
or request.renewal != "none"
or type(request.ttl_seconds) is not int
or not 1 <= request.ttl_seconds <= 3600
or request.ttl_seconds > self.policy.maximum_epoch_ttl_seconds
):
_fail("SOURCE_PUBLICATION_REQUEST_WIDENS_POLICY")
value: dict[str, Any] = {
"schema": SOURCE_PUBLICATION_POLICY_SCHEMA,
"policy_id": SOURCE_PUBLICATION_POLICY_ID,
"owner_identity": OWNER_IDENTITY,
"repository": request.repository,
"trust_domain": self.policy.trust_domain,
"policy_digest": self.policy.digest,
"authority_base_oid": request.authority_base_oid,
"source_oid": request.source_oid,
"source_tree_oid": request.source_tree_oid,
"target_agent": request.target_agent,
"target_branch": request.target_branch,
"generation": request.generation,
"execution_digest": request.execution_digest,
"exact_files": list(files),
"capabilities": sorted(SOURCE_PUBLICATION_CAPABILITIES),
"risk": "engineering",
"production_impact": "none",
"renewal": "none",
"nonce": request.nonce,
"issued_at": now,
"not_before": max(0, now - 30),
"expires_at": now + request.ttl_seconds,
"status": "ACTIVE",
"signature_namespace": OWNER_NAMESPACE,
"signature_key_id": self.fingerprint,
}
value["canonical_payload_digest"] = sha256_json(value)
return self._sign_canonical(value)
issue_source_only_policy = issue_source_publication_policy
def issue_authorization_bundle(
self,
*,
@@ -35,6 +35,8 @@ from typing import Any, Callable, Mapping, Protocol, Sequence
SCHEMA_ENVELOPE = "aios.task-admission-envelope.v1"
SCHEMA_GRANT = "aios.task-admission-grant.v1"
SCHEMA_EPOCH = "aios.owner-delegation-epoch.v1"
SCHEMA_EPOCH_V2 = "aios.owner-delegation-epoch.v2"
SUPPORTED_EPOCH_SCHEMAS = frozenset({SCHEMA_EPOCH, SCHEMA_EPOCH_V2})
SCHEMA_RECEIPT = "aios.task-admission-receipt.v1"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
@@ -142,9 +144,30 @@ class OwnerDelegationEpoch:
revocation_epoch: int
policy_digest: str
schema: str = SCHEMA_EPOCH
production_scope: str | None = None
destructive_policy: str | None = None
# Provenance retained by the consumer. These fields are populated by the
# signed-directory provider for v2 (and left at their v1-compatible
# defaults when reading legacy epochs); they are never used to translate or
# re-sign the original artifact.
status: str = ""
signature_namespace: str = ""
signature_key_id: str = ""
canonical_payload_digest: str = ""
canonical_payload: bytes = b""
@property
def issuer(self) -> str:
"""The signed epoch issuer, retained under the legacy identity name."""
return self.owner_identity
@property
def canonical_payload_bytes(self) -> bytes:
return self.canonical_payload
def as_public_dict(self) -> dict[str, Any]:
value = asdict(self)
value.pop("canonical_payload", None)
value["allowed_capabilities"] = sorted(self.allowed_capabilities)
value["allowed_branch_prefixes"] = list(self.allowed_branch_prefixes)
value["allowed_file_prefixes"] = list(self.allowed_file_prefixes)
@@ -309,7 +332,7 @@ def _normalize_capabilities(
def _risk_rank(value: str) -> int:
ranks = {"low": 0, "medium": 1, "high": 2}
ranks = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
try:
return ranks[value]
except KeyError:
@@ -838,7 +861,7 @@ class TaskAdmissionBootstrapService:
now: int,
) -> OwnerDelegationEpoch:
epoch = self.authorization.verify_delegation_epoch(epoch_id)
if epoch.schema != SCHEMA_EPOCH:
if epoch.schema not in SUPPORTED_EPOCH_SCHEMAS:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_SCHEMA_INVALID", 403)
if epoch.epoch_id != epoch_id:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_ID_MISMATCH", 403)
@@ -26,6 +26,8 @@ import urllib.request
from aios_task_admission_bootstrap import (
OwnerDelegationEpoch,
SCHEMA_EPOCH,
SCHEMA_EPOCH_V2,
TaskAdmissionError,
TaskAdmissionGrant,
canonical_json_bytes,
@@ -44,6 +46,19 @@ from aios_gitops_job_identity import (
OWNER_NAMESPACE = "aios-owner-approval-v1"
OWNER_SIGNER_IDENTITY = "aios-task-grant-signer"
_EPOCH_V1_FIELDS = frozenset({
"epoch_id", "owner_identity", "trust_domain", "repository",
"allowed_branch_prefixes", "allowed_capabilities", "allowed_file_prefixes",
"risk_ceiling", "issued_at", "not_before", "expires_at",
"revocation_epoch", "policy_digest", "schema",
})
_EPOCH_V2_FIELDS = frozenset({
*_EPOCH_V1_FIELDS,
"business_goal", "production_scope", "destructive_policy",
"signature_namespace", "signature_key_id", "canonical_payload_digest",
"status",
})
_SSH_FINGERPRINT_RE = re.compile(r"^SHA256:[A-Za-z0-9+/]{43}$")
class OfficialTaskTransport(Protocol):
@@ -463,6 +478,7 @@ class TaskAdmissionPrincipalProvider:
class TaskAdmissionRuntimeBundle:
handlers: Mapping[str, Any]
principal_verifier: TaskAdmissionPrincipalProvider
service: Any | None = None
class TaskAdmissionHostError(RuntimeError):
@@ -622,11 +638,15 @@ class SignedEpochDirectoryProvider:
"TASK_ADMISSION_EPOCH_CANONICAL_MISMATCH",
403,
)
if value.get("schema") != "aios.owner-delegation-epoch.v1":
schema = value.get("schema")
if schema not in {SCHEMA_EPOCH, SCHEMA_EPOCH_V2}:
raise TaskAdmissionError(
"TASK_ADMISSION_EPOCH_SCHEMA_INVALID",
403,
)
expected_fields = _EPOCH_V1_FIELDS if schema == SCHEMA_EPOCH else _EPOCH_V2_FIELDS
if set(value) != expected_fields:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_FIELDS_INVALID", 403)
if value.get("epoch_id") != epoch_id:
raise TaskAdmissionError(
"TASK_ADMISSION_EPOCH_ID_MISMATCH",
@@ -642,7 +662,34 @@ class SignedEpochDirectoryProvider:
if epoch_id in self._revoked_ids():
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REVOKED", 403)
# v2 is a producer-authored envelope. Validate its self-digest and
# signature binding before the cryptographic verifier sees the exact
# bytes from disk. The digest is over the payload with only the digest
# member removed; no schema translation is permitted.
if schema == SCHEMA_EPOCH_V2:
if (
value.get("status") != "ACTIVE"
or not isinstance(value.get("business_goal"), str)
or not value["business_goal"]
or not isinstance(value.get("production_scope"), str)
or not value["production_scope"]
or not isinstance(value.get("destructive_policy"), str)
or value["destructive_policy"] not in {"forbidden", "rollback-only"}
or value.get("signature_namespace") != OWNER_NAMESPACE
or not isinstance(value.get("signature_key_id"), str)
or not _SSH_FINGERPRINT_RE.fullmatch(value["signature_key_id"])
or not isinstance(value.get("canonical_payload_digest"), str)
or not re.fullmatch(r"[0-9a-f]{64}", value["canonical_payload_digest"])
):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_V2_BINDING_INVALID", 403)
digest_value = dict(value)
supplied_digest = digest_value.pop("canonical_payload_digest", None)
if sha256_json(digest_value) != supplied_digest:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_PAYLOAD_DIGEST_INVALID", 403)
key_id = key_id_file.read_text(encoding="utf-8").strip()
if schema == SCHEMA_EPOCH_V2 and key_id != value["signature_key_id"]:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_KEY_BINDING_INVALID", 403)
armored = signature.read_text(encoding="utf-8")
if self.verifier.verify(
namespace=OWNER_NAMESPACE,
@@ -659,6 +706,23 @@ class SignedEpochDirectoryProvider:
not_before = value.get("not_before")
expires_at = value.get("expires_at")
if (
not isinstance(value.get("epoch_id"), str)
or not value["epoch_id"]
or not isinstance(value.get("owner_identity"), str)
or not value["owner_identity"]
or not isinstance(value.get("trust_domain"), str)
or not value["trust_domain"]
or not isinstance(value.get("repository"), str)
or not value["repository"]
or not isinstance(value.get("risk_ceiling"), str)
or value["risk_ceiling"] not in {"low", "medium", "engineering", "high"}
or not isinstance(value.get("issued_at"), int)
or isinstance(value.get("issued_at"), bool)
or not isinstance(value.get("revocation_epoch"), int)
or isinstance(value.get("revocation_epoch"), bool)
or not isinstance(value.get("policy_digest"), str)
or not value["policy_digest"]
or
not isinstance(not_before, int)
or isinstance(not_before, bool)
or not isinstance(expires_at, int)
@@ -672,13 +736,16 @@ class SignedEpochDirectoryProvider:
files = value.get("allowed_file_prefixes")
if (
not isinstance(branches, list)
or not branches
or not all(isinstance(item, str) and item for item in branches)
or not isinstance(capabilities, list)
or not capabilities
or not all(
isinstance(item, str) and item
for item in capabilities
)
or not isinstance(files, list)
or not files
or not all(isinstance(item, str) and item for item in files)
):
raise TaskAdmissionError(
@@ -701,6 +768,19 @@ class SignedEpochDirectoryProvider:
revocation_epoch=int(value["revocation_epoch"]),
policy_digest=str(value["policy_digest"]),
schema=str(value["schema"]),
production_scope=(
str(value["production_scope"])
if schema == SCHEMA_EPOCH_V2 else None
),
destructive_policy=(
str(value["destructive_policy"])
if schema == SCHEMA_EPOCH_V2 else None
),
status=str(value.get("status", "")),
signature_namespace=str(value.get("signature_namespace", "")),
signature_key_id=str(value.get("signature_key_id", "")),
canonical_payload_digest=str(value.get("canonical_payload_digest", "")),
canonical_payload=payload,
)
@@ -915,6 +995,7 @@ def _build_task_admission_runtime(
task_port=task_port,
epoch_id=epoch.epoch_id,
),
service=service,
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,340 @@
"""Concrete transport adapter for the governed source publication boundary.
The adapter composes the already existing live writer executor. It owns no
leases, TaskPrincipal, credentials, retry policy, or release state machine.
Those remain server side. ``authenticated_readback`` is deliberately an
injected port: a local commit response is not evidence of an authenticated
source/tree binding.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping
import hashlib
import json
import re
from typing import Any, Protocol
from governed_unattended_release import SourcePublicationIntent
from task_plane.governed_writer_automation import GovernedWriterRequest
class GovernedPublicationError(RuntimeError):
"""Stable fail-closed publication error."""
def __init__(self, code: str):
self.code = code
super().__init__(code)
class GovernedWriterExecutor(Protocol):
def acquire(self, *, request: GovernedWriterRequest, job_id: str) -> Any: ...
def commit_and_readback(
self, *, session: Any, request: GovernedWriterRequest,
files: Mapping[str, bytes], expected_sha256: Mapping[str, str],
message: str,
) -> Mapping[str, Any]: ...
def submit_task_result(
self, *, session: Any, result: Mapping[str, Any], idempotency_key: str,
) -> Mapping[str, Any]: ...
class SourceFileReader(Protocol):
def __call__(self, intent: SourcePublicationIntent) -> Mapping[str, bytes]: ...
class AuthenticatedReadback(Protocol):
def __call__(
self, *, intent: SourcePublicationIntent, request: GovernedWriterRequest,
session: Any, commit_result: Mapping[str, Any],
) -> Mapping[str, Any]: ...
class AdmissionBindingReader(Protocol):
"""Read the server-side task/governed binding after admission."""
def __call__(
self, *, intent: SourcePublicationIntent,
admission_receipt: Mapping[str, Any],
) -> Mapping[str, Any]: ...
class GitTreeFileReader(Protocol):
"""Read bytes from an immutable committed tree object, never a workspace."""
def __call__(self, *, tree_oid: str, path: str) -> bytes: ...
class CommittedTreeSourceReader:
"""Adapt an authoritative git-object reader to the publication port."""
def __init__(self, reader: GitTreeFileReader) -> None:
if not callable(reader):
raise ValueError("git_object_reader_required")
self.reader = reader
def __call__(self, intent: SourcePublicationIntent) -> Mapping[str, bytes]:
return {
path: self.reader(tree_oid=intent.source_tree_oid, path=path)
for path in intent.file_allowlist
}
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA = re.compile(r"^[0-9a-f]{64}$")
def _oid(value: Any) -> str:
result = str(value or "").lower()
if not _OID.fullmatch(result):
raise GovernedPublicationError("PUBLICATION_OID_INVALID")
return result
def _sha(value: Any) -> str:
result = str(value or "").lower()
if not _SHA.fullmatch(result):
raise GovernedPublicationError("PUBLICATION_SHA_INVALID")
return result
def _canonical(value: Mapping[str, Any]) -> bytes:
try:
return json.dumps(
dict(value), sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise GovernedPublicationError("PUBLICATION_EVIDENCE_INVALID") from exc
class GovernedPublicationClient:
"""Run TaskLease -> TaskPrincipal -> WriterLease -> commit through ports.
``writer_executor`` is normally ``LiveGovernedWriterExecutor`` from
``task_plane.live_governed_writer_adapters``. ``source_files`` must return
the exact immutable bytes for the intent's allowlist; it is never allowed
to return a path or a mutable object. The authenticated readback port
must be backed by the authoritative provider/bridge and prove the final
source OID, tree OID, task, branch, base, generation, and execution digest.
"""
def __init__(
self,
*,
writer_executor: GovernedWriterExecutor,
source_files: SourceFileReader | None = None,
git_tree_file: GitTreeFileReader | None = None,
admission_binding: AdmissionBindingReader,
authenticated_readback: AuthenticatedReadback,
subject: str,
manifest_id: str,
runtime_fingerprint: str,
process_start_identity: str,
commit_message: str = "governed source publication",
) -> None:
if (
writer_executor is None
or any(
not callable(getattr(writer_executor, name, None))
for name in (
"acquire", "commit_and_readback", "submit_task_result",
)
)
):
raise ValueError("writer_executor_required")
if source_files is not None:
raise ValueError("workspace_source_reader_forbidden")
if git_tree_file is None:
raise ValueError("git_object_source_reader_required")
if not callable(admission_binding) or not callable(authenticated_readback):
raise ValueError("authenticated_ports_required")
for name, value in (("subject", subject), ("manifest_id", manifest_id),
("runtime_fingerprint", runtime_fingerprint),
("process_start_identity", process_start_identity),
("commit_message", commit_message)):
if not isinstance(value, str) or not value:
raise ValueError(name + "_required")
self.writer_executor = writer_executor
self.source_files = None
self.git_tree_file = git_tree_file
self.admission_binding = admission_binding
self.authenticated_readback = authenticated_readback
self.subject = subject
self.manifest_id = manifest_id
self.runtime_fingerprint = runtime_fingerprint
self.process_start_identity = process_start_identity
self.commit_message = commit_message
def _validate_admission(self, intent: SourcePublicationIntent, receipt: Mapping[str, Any]) -> Mapping[str, Any]:
if not isinstance(receipt, Mapping):
raise GovernedPublicationError("PUBLICATION_ADMISSION_INVALID")
for key, expected in (("task_id", intent.task_id), ("authority_base_oid", intent.authority_base_oid),
("target_branch", intent.target_branch), ("generation", intent.generation),
("execution_digest", intent.execution_digest)):
if receipt.get(key) != expected:
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_MISMATCH")
try:
binding = self.admission_binding(intent=intent, admission_receipt=receipt)
except Exception as exc:
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_UNAVAILABLE") from exc
if not isinstance(binding, Mapping):
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_INVALID")
expected = {
"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest,
}
if any(binding.get(key) != value for key, value in expected.items()):
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_MISMATCH")
job_id = binding.get("job_id")
if not isinstance(job_id, str) or not job_id:
raise GovernedPublicationError("PUBLICATION_JOB_BINDING_MISSING")
return dict(binding)
def _read_files(self, intent: SourcePublicationIntent) -> tuple[dict[str, bytes], dict[str, str]]:
try:
raw = {
path: self.git_tree_file(tree_oid=intent.source_tree_oid, path=path)
for path in intent.file_allowlist
}
except KeyError as exc:
raise GovernedPublicationError("PUBLICATION_FILE_SET_MISMATCH") from exc
except Exception as exc:
raise GovernedPublicationError("PUBLICATION_SOURCE_READ_FAILED") from exc
if not isinstance(raw, Mapping) or set(raw) != set(intent.file_allowlist):
raise GovernedPublicationError("PUBLICATION_FILE_SET_MISMATCH")
files: dict[str, bytes] = {}
hashes: dict[str, str] = {}
for path in intent.file_allowlist:
data = raw.get(path)
if isinstance(data, bytearray):
data = bytes(data)
if not isinstance(data, bytes):
raise GovernedPublicationError("PUBLICATION_FILE_BYTES_REQUIRED")
files[path] = bytes(data)
hashes[path] = hashlib.sha256(files[path]).hexdigest()
return files, hashes
def publish(self, intent: SourcePublicationIntent, admission_receipt: Mapping[str, Any]) -> dict[str, Any]:
if not isinstance(intent, SourcePublicationIntent):
raise GovernedPublicationError("PUBLICATION_INTENT_REQUIRED")
admission_binding = self._validate_admission(intent, admission_receipt)
job_id = str(admission_binding["job_id"])
files, hashes = self._read_files(intent)
request = GovernedWriterRequest(
subject=self.subject, task_id=intent.task_id, repository=intent.repository,
target_branch=intent.target_branch, risk=intent.risk, generation=intent.generation,
base_oid=intent.authority_base_oid,
required_capabilities=frozenset(intent.requested_capabilities),
file_allowlist=tuple(intent.file_allowlist),
plan_digest="sha256:" + intent.execution_digest,
manifest_id=self.manifest_id, runtime_fingerprint=self.runtime_fingerprint,
process_start_identity=self.process_start_identity,
)
try:
session = self.writer_executor.acquire(request=request, job_id=job_id)
commit_result = self.writer_executor.commit_and_readback(
session=session, request=request, files=files,
expected_sha256=hashes, message=self.commit_message,
)
if not isinstance(commit_result, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_READBACK_INVALID")
readback = self.authenticated_readback(
intent=intent, request=request, session=session,
commit_result=commit_result,
)
except GovernedPublicationError:
raise
except Exception as exc:
# A transport exception after a mutation is indeterminate. Never
# resubmit it here; the caller must reconcile through the existing
# Job/recovery service.
raise GovernedPublicationError("PUBLICATION_EFFECT_UNKNOWN") from exc
if not isinstance(readback, Mapping) or readback.get("authenticated") is not True:
raise GovernedPublicationError("PUBLICATION_AUTHENTICATED_READBACK_REQUIRED")
required = {
"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest,
}
if any(readback.get(key) != value for key, value in required.items()):
raise GovernedPublicationError("PUBLICATION_READBACK_BINDING_MISMATCH")
for key in ("task_lease", "task_principal", "writer_lease"):
if not isinstance(readback.get(key), Mapping) or readback[key].get("task_id") != intent.task_id:
raise GovernedPublicationError("PUBLICATION_LEASE_CHAIN_MISSING")
source_oid = _oid(readback.get("source_oid"))
tree_oid = _oid(readback.get("tree_oid"))
nested_commit = readback.get("commit_receipt")
if nested_commit is not None and not isinstance(nested_commit, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_BINDING_MISMATCH")
commit_oid = _oid(readback.get("commit_oid") or (nested_commit or {}).get("commit_oid"))
if source_oid != commit_oid or tree_oid != intent.source_tree_oid:
raise GovernedPublicationError("PUBLICATION_SOURCE_TREE_BINDING_MISMATCH")
commit_receipt = readback.get("commit_receipt")
if not isinstance(commit_receipt, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_RECEIPT_REQUIRED")
# The live governed receipt binds commit_oid; the independently read
# Git tree is carried by authenticated readback. Preserve the original
# receipt instead of inventing a tree field absent from its schema.
if (commit_receipt.get("commit_oid") != commit_oid
or ("tree_oid" in commit_receipt and commit_receipt["tree_oid"] != tree_oid)):
raise GovernedPublicationError("PUBLICATION_COMMIT_BINDING_MISMATCH")
publication = {
**dict(readback), "authenticated": True, "task_id": intent.task_id,
"job_id": job_id, "source_oid": source_oid, "tree_oid": tree_oid,
"generation": intent.generation, "target_branch": intent.target_branch,
"authority_base_oid": intent.authority_base_oid,
"execution_digest": intent.execution_digest,
"commit_receipt": dict(commit_receipt),
}
task_result = {
"schema": "aios.governed-source-publication-result.v1",
"task_id": intent.task_id,
"job_id": job_id,
"repository": intent.repository,
"target_branch": intent.target_branch,
"generation": intent.generation,
"execution_digest": intent.execution_digest,
"authority_base_oid": _oid(intent.authority_base_oid),
"candidate_source_oid": _oid(intent.source_commit),
"published_source_oid": source_oid,
"source_tree_oid": tree_oid,
"commit_receipt_sha256": hashlib.sha256(
_canonical(commit_receipt)
).hexdigest(),
}
result_sha256 = hashlib.sha256(_canonical(task_result)).hexdigest()
idempotency_key = "governed-source-result-" + result_sha256
try:
submitted = self.writer_executor.submit_task_result(
session=session, result=task_result,
idempotency_key=idempotency_key,
)
if (
not isinstance(submitted, Mapping)
or submitted.get("task_id") != intent.task_id
or submitted.get("status") != "VERIFYING"
or submitted.get("result") != task_result
):
raise GovernedPublicationError(
"PUBLICATION_TASK_RESULT_READBACK_INVALID"
)
except GovernedPublicationError:
raise
except Exception as exc:
# A lost result response may have changed the durable Task. Never
# re-register, reclaim or retry here.
raise GovernedPublicationError("PUBLICATION_EFFECT_UNKNOWN") from exc
publication.update(
task_result=task_result,
task_result_idempotency_key=idempotency_key,
task_result_submission={
"task_id": submitted["task_id"], "status": submitted["status"],
"revision": submitted.get("revision"),
"idempotent": submitted.get("idempotent", False),
"result_sha256": result_sha256,
},
)
return publication
@@ -0,0 +1,405 @@
"""Narrow C5 unattended source-publication policy lifecycle.
The policy is an Owner-signed, exact-scope admission object. It is checked
against the configured Owner policy at issuance and against a live revocation
source for every request. It is not an activation/configuration mechanism
and does not create release or production authority.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import re
import time
from pathlib import PurePosixPath
from typing import Any, Callable, Mapping
from aios_single_host_authority import (
OWNER_NAMESPACE,
OWNER_IDENTITY,
SOURCE_PUBLICATION_CAPABILITIES,
SOURCE_PUBLICATION_POLICY_ID,
SOURCE_PUBLICATION_POLICY_SCHEMA,
SignedArtifact,
SingleHostAuthorityError,
SourcePublicationPolicyRequest,
canonical_json_bytes,
sha256_json,
)
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_RISK_ORDER = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
_POLICY_FIELDS = frozenset(
{
"schema", "policy_id", "owner_identity", "repository", "trust_domain",
"policy_digest",
"authority_base_oid", "source_oid", "source_tree_oid", "target_agent",
"target_branch", "generation", "execution_digest", "exact_files",
"capabilities", "risk", "production_impact", "renewal", "nonce",
"issued_at", "not_before", "expires_at", "status", "signature_namespace",
"signature_key_id", "canonical_payload_digest",
}
)
_REQUEST_FIELDS = frozenset(SourcePublicationPolicyRequest.__dataclass_fields__)
# The typed Owner epoch producer uses the configured policy's production_scope.
# The installed C5 source-only policy contract uses ``none`` for production
# impact; keep this exact value bound to the C5 policy rather than accepting an
# arbitrary non-empty scope from a raw signer.
SOURCE_PUBLICATION_EPOCH_PRODUCTION_SCOPE = "none"
SOURCE_PUBLICATION_EPOCH_DESTRUCTIVE_POLICY = "forbidden"
@dataclass(frozen=True, slots=True)
class TaskAdmissionPolicyProjection:
"""The only policy projection exposed after an exact request guard."""
max_risk: str
repository: str
target_branch: str
capabilities: tuple[str, ...]
exact_files: tuple[str, ...]
def to_dict(self) -> dict[str, Any]:
return asdict(self)
def _request_dict(request: SourcePublicationPolicyRequest | Mapping[str, Any]) -> dict[str, Any]:
if isinstance(request, SourcePublicationPolicyRequest):
value = asdict(request)
elif isinstance(request, Mapping):
value = dict(request)
else:
raise TypeError("request must be SourcePublicationPolicyRequest or mapping")
if set(value) != _REQUEST_FIELDS:
raise ValueError("SOURCE_PUBLICATION_REQUEST_FIELDS_INVALID")
if "exact_files" in value:
value["exact_files"] = tuple(value["exact_files"])
return value
class SourcePublicationPolicyVerifier:
"""Strict verifier over the existing Owner registry verifier interface."""
def __init__(
self,
owner_verifier: Any,
*,
revocation_check: Callable[[Mapping[str, Any]], bool] | None,
expected_policy_digest: str | None = None,
clock: Callable[[], float] = time.time,
) -> None:
self.owner_verifier = owner_verifier
self.revocation_check = revocation_check
self.expected_policy_digest = expected_policy_digest
self.clock = clock
def _deny(self, code: str) -> None:
raise SingleHostAuthorityError(code)
def _validate_payload(self, artifact: SignedArtifact) -> dict[str, Any]:
if not isinstance(artifact, SignedArtifact):
self._deny("SOURCE_PUBLICATION_ARTIFACT_INVALID")
payload = dict(artifact.payload)
if set(payload) != _POLICY_FIELDS:
self._deny("SOURCE_PUBLICATION_UNKNOWN_FIELDS")
try:
if artifact.canonical_payload != canonical_json_bytes(payload):
self._deny("SOURCE_PUBLICATION_CANONICAL_BYTES_MISMATCH")
if payload["canonical_payload_digest"] != sha256_json(
{k: v for k, v in payload.items() if k != "canonical_payload_digest"}
):
self._deny("SOURCE_PUBLICATION_PAYLOAD_DIGEST_INVALID")
if payload["schema"] != SOURCE_PUBLICATION_POLICY_SCHEMA:
self._deny("SOURCE_PUBLICATION_SCHEMA_INVALID")
if payload["policy_id"] != SOURCE_PUBLICATION_POLICY_ID:
self._deny("SOURCE_PUBLICATION_POLICY_ID_INVALID")
if payload["owner_identity"] != OWNER_IDENTITY:
self._deny("SOURCE_PUBLICATION_OWNER_INVALID")
if not isinstance(payload["policy_digest"], str) or not _HEX64.fullmatch(payload["policy_digest"]):
self._deny("SOURCE_PUBLICATION_POLICY_DIGEST_INVALID")
if self.expected_policy_digest is not None and payload["policy_digest"] != self.expected_policy_digest:
self._deny("SOURCE_PUBLICATION_POLICY_DIGEST_MISMATCH")
if payload["signature_namespace"] != OWNER_NAMESPACE:
self._deny("SOURCE_PUBLICATION_NAMESPACE_INVALID")
if payload["signature_key_id"] != artifact.key_id or artifact.namespace != OWNER_NAMESPACE:
self._deny("SOURCE_PUBLICATION_ISSUER_MISMATCH")
for name in ("authority_base_oid", "source_oid", "source_tree_oid"):
if not isinstance(payload[name], str) or not _HEX40.fullmatch(payload[name]):
self._deny("SOURCE_PUBLICATION_OID_INVALID")
if not isinstance(payload["execution_digest"], str) or not _HEX64.fullmatch(payload["execution_digest"]):
self._deny("SOURCE_PUBLICATION_EXECUTION_DIGEST_INVALID")
branch = payload["target_branch"]
agent = payload["target_agent"]
if (
not isinstance(branch, str) or not branch.startswith("agent/")
or "\\" in branch or ".." in PurePosixPath(branch).parts
or branch.endswith(("/", "."))
or not isinstance(agent, str) or not agent.startswith("agent/")
):
self._deny("SOURCE_PUBLICATION_TARGET_INVALID")
if payload["risk"] != "engineering" or payload["production_impact"] != "none" or payload["renewal"] != "none":
self._deny("SOURCE_PUBLICATION_SCOPE_INVALID")
if payload["capabilities"] != sorted(SOURCE_PUBLICATION_CAPABILITIES):
self._deny("SOURCE_PUBLICATION_CAPABILITIES_INVALID")
if not isinstance(payload["exact_files"], list) or not payload["exact_files"] or len(set(payload["exact_files"])) != len(payload["exact_files"]):
self._deny("SOURCE_PUBLICATION_FILES_INVALID")
if type(payload["generation"]) is not int or payload["generation"] < 1:
self._deny("SOURCE_PUBLICATION_GENERATION_INVALID")
if type(payload["issued_at"]) is not int or type(payload["not_before"]) is not int or type(payload["expires_at"]) is not int:
self._deny("SOURCE_PUBLICATION_TIME_INVALID")
now = int(self.clock())
if not payload["not_before"] <= payload["issued_at"] <= now < payload["expires_at"]:
self._deny("SOURCE_PUBLICATION_EXPIRED")
if payload["expires_at"] <= payload["issued_at"]:
self._deny("SOURCE_PUBLICATION_LIFETIME_INVALID")
if payload["expires_at"] - payload["issued_at"] > 3600 or payload["status"] != "ACTIVE":
self._deny("SOURCE_PUBLICATION_LIFETIME_INVALID")
if self.revocation_check is None:
self._deny("SOURCE_PUBLICATION_REVOCATION_CHECK_MISSING")
if self.revocation_check(payload) is not True:
self._deny("SOURCE_PUBLICATION_REVOKED")
except SingleHostAuthorityError:
raise
except Exception as exc:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_INVALID") from exc
return payload
def verify(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any] | None = None,
) -> bool:
try:
payload = self._validate_payload(artifact)
# Verify exactly the bytes supplied by the signed artifact. Do not
# reconstruct or reserialize them before invoking the registry API.
if self.owner_verifier.verify(
namespace=artifact.namespace,
payload=artifact.canonical_payload,
signature=artifact.signature,
key_id=artifact.key_id,
) is not True:
return False
if request is not None:
expected = _request_dict(request)
for field in (
"policy_id", "repository", "authority_base_oid", "source_oid",
"source_tree_oid", "target_agent", "target_branch", "generation",
"execution_digest", "exact_files", "nonce", "risk",
"production_impact", "renewal",
):
actual = tuple(payload[field]) if field == "exact_files" else payload[field]
if actual != expected.get(field):
return False
if int(payload["expires_at"]) - int(payload["issued_at"]) != int(expected["ttl_seconds"]):
return False
return True
except (SingleHostAuthorityError, TypeError, ValueError, KeyError):
return False
except Exception:
return False
def require(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> dict[str, Any]:
if not self.verify(artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
return dict(artifact.payload)
def require_source_publication_epoch_scope(epoch: Any, policy_payload: Mapping[str, Any]) -> None:
"""Require v2 epoch provenance to match the signed C5 policy exactly."""
schema = getattr(epoch, "schema", None)
if schema is None and isinstance(epoch, Mapping):
schema = epoch.get("schema")
if schema != "aios.owner-delegation-epoch.v2":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_EPOCH_SCHEMA_REQUIRED")
def field(name: str) -> Any:
if isinstance(epoch, Mapping):
return epoch.get(name)
return getattr(epoch, name, None)
if (
field("production_scope") != SOURCE_PUBLICATION_EPOCH_PRODUCTION_SCOPE
or field("destructive_policy") != SOURCE_PUBLICATION_EPOCH_DESTRUCTIVE_POLICY
or field("policy_digest") != policy_payload.get("policy_digest")
or field("repository") != policy_payload.get("repository")
or field("trust_domain") != policy_payload.get("trust_domain")
):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_EPOCH_SCOPE_MISMATCH")
class ScopedSourcePublicationPolicyAdmission:
"""Rechecks a policy for every exact request before scoped admission."""
def __init__(
self,
verifier: SourcePublicationPolicyVerifier,
*,
bootstrap: Any | None = None,
artifact: SignedArtifact | None = None,
request: SourcePublicationPolicyRequest | Mapping[str, Any] | None = None,
expected_agent_id: str | None = None,
) -> None:
self.verifier = verifier
self.bootstrap = bootstrap
self.artifact = artifact
self.request = request
self.expected_agent_id = expected_agent_id
def _check_bootstrap_binding(self, payload: Mapping[str, Any]) -> None:
if self.bootstrap is None:
return
policy = getattr(self.bootstrap, "policy", None)
digest_provider = getattr(policy, "normalized_policy_digest", None)
digest = digest_provider() if callable(digest_provider) else None
trust_domain = getattr(policy, "trust_domain", None)
if not isinstance(digest, str) or digest != payload.get("policy_digest"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_POLICY_DIGEST_MISMATCH")
if not isinstance(trust_domain, str) or trust_domain != payload.get("trust_domain"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_TRUST_DOMAIN_MISMATCH")
if self.expected_agent_id is None or payload.get("target_agent") != self.expected_agent_id:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_TARGET_AGENT_MISMATCH")
def authorize(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> bool:
try:
payload = self.verifier._validate_payload(artifact)
if self.expected_agent_id is not None and payload.get("target_agent") != self.expected_agent_id:
return False
self._check_bootstrap_binding(payload)
except SingleHostAuthorityError:
return False
return self.verifier.verify(artifact, request)
verify = authorize
def _request_for_raw(self, raw: Mapping[str, Any]) -> SourcePublicationPolicyRequest:
if self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_ARTIFACT_REQUIRED")
expected_task = getattr(self, "expected_task_id", None)
if expected_task is not None and raw.get("task_id") != expected_task:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_PLAN_TASK_MISMATCH")
payload = self.verifier._validate_payload(self.artifact)
self._check_bootstrap_binding(payload)
if self.request is not None:
base = _request_dict(self.request)
else:
base = {
"policy_id": payload["policy_id"], "repository": payload["repository"],
"authority_base_oid": payload["authority_base_oid"], "source_oid": payload["source_oid"],
"source_tree_oid": payload["source_tree_oid"], "target_agent": payload["target_agent"],
"target_branch": payload["target_branch"], "generation": payload["generation"],
"execution_digest": payload["execution_digest"], "exact_files": tuple(payload["exact_files"]),
"nonce": payload["nonce"], "ttl_seconds": payload["expires_at"] - payload["issued_at"],
"risk": payload["risk"], "production_impact": payload["production_impact"], "renewal": payload["renewal"],
}
checks = {
"repository": base["repository"], "authority_base_oid": base["authority_base_oid"],
"target_branch": base["target_branch"], "generation": base["generation"],
"execution_digest": base["execution_digest"], "risk": base["risk"],
}
for field, expected in checks.items():
if raw.get(field) != expected:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_REQUEST_SCOPE_MISMATCH")
caps = tuple(raw.get("requested_capabilities", ()))
if set(caps) != set(base["capabilities"] if "capabilities" in base else payload["capabilities"]):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_CAPABILITY_SCOPE_MISMATCH")
if tuple(raw.get("file_allowlist", ())) != tuple(base["exact_files"]):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_FILE_SCOPE_MISMATCH")
return SourcePublicationPolicyRequest(**base)
def create_envelope(self, raw: Mapping[str, Any]) -> dict[str, Any]:
if self.bootstrap is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
request = self._request_for_raw(raw)
if not self.verifier.verify(self.artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
return self.bootstrap.create_envelope(raw)
def authorize_and_admit(self, raw: Mapping[str, Any]) -> dict[str, Any]:
if self.bootstrap is None or self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
if raw.get("authorization_kind") != "delegation_epoch":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_OWNER_SIGNATURE_BYPASS_DENIED")
envelope = self.bootstrap._load_envelope(str(raw.get("envelope_id") or ""))
envelope_raw = envelope.as_dict() if hasattr(envelope, "as_dict") else dict(envelope)
request = self._request_for_raw(envelope_raw)
if not self.verifier.verify(self.artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
epoch = self.bootstrap._verify_epoch(
epoch_id=str(raw.get("delegation_epoch_id") or ""),
envelope=envelope,
now=int(self.verifier.clock()),
)
require_source_publication_epoch_scope(
epoch, self.verifier._validate_payload(self.artifact)
)
return self.bootstrap.authorize_and_admit(raw)
def status(self, raw: Mapping[str, Any] | str) -> Any:
"""Read status only after the same policy and epoch guard as admission."""
if self.bootstrap is None or self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
if isinstance(raw, str):
raw = {"envelope_id": raw}
envelope = self.bootstrap._load_envelope(str(raw.get("envelope_id") or ""))
envelope_raw = envelope.as_dict() if hasattr(envelope, "as_dict") else dict(envelope)
request = self._request_for_raw(envelope_raw)
self.verifier.require(self.artifact, request)
epoch_id = str(raw.get("delegation_epoch_id") or "")
if not epoch_id:
grant = self.bootstrap.store.get_grant_by_task_id(envelope.task_id)
epoch_id = grant.parent_epoch_id if grant is not None else ""
if not epoch_id or not hasattr(self.bootstrap, "_verify_epoch"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_STATUS_EPOCH_REQUIRED")
epoch = self.bootstrap._verify_epoch(
epoch_id=epoch_id,
envelope=envelope,
now=int(self.verifier.clock()),
)
require_source_publication_epoch_scope(
epoch, self.verifier._validate_payload(self.artifact)
)
if hasattr(self.bootstrap, "status"):
return self.bootstrap.status(envelope.envelope_id)
return envelope_raw
def reconcile_status(self, envelope_id: str, authorization: Mapping[str, Any]) -> Any:
"""Carry the verified epoch identity when a Task effect has no receipt yet."""
if authorization.get("authorization_kind") != "delegation_epoch":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_OWNER_SIGNATURE_BYPASS_DENIED")
return self.status({"envelope_id": envelope_id,
"delegation_epoch_id": authorization.get("delegation_epoch_id")})
def project_task_admission_policy(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> TaskAdmissionPolicyProjection:
payload = self.verifier.require(artifact, request)
return TaskAdmissionPolicyProjection(
max_risk="engineering",
repository=payload["repository"],
target_branch=payload["target_branch"],
capabilities=tuple(payload["capabilities"]),
exact_files=tuple(payload["exact_files"]),
)
to_task_admission_policy = project_task_admission_policy
# Short aliases for callers that prefer the lifecycle terminology.
SourcePublicationPolicyAdmission = ScopedSourcePublicationPolicyAdmission
SourcePublicationVerifier = SourcePublicationPolicyVerifier
@@ -0,0 +1,372 @@
"""Thin unattended release runner.
This module is an adapter around the existing admission and silent-release
services. It deliberately owns no release state machine and never creates
approval evidence. The runner only binds immutable source facts, delegates
authority, and validates durable readbacks before moving to the next phase.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import re
import time
from typing import Any, Callable, Mapping
OID_RE = re.compile(r"^[0-9a-fA-F]{40}$")
SHA256_RE = re.compile(r"^[0-9a-fA-F]{64}$")
IDENT_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$")
RETRYABLE_CODES = {408, 425, 429, 500, 502, 503, 504}
RETRYABLE_NAMES = {
"UNAVAILABLE", "BUSY", "TEMPORARY_FAILURE", "TASK_ADMISSION_IN_PROGRESS",
}
RISKS = {"low", "medium", "engineering", "high"}
class UnattendedReleaseError(RuntimeError):
"""A fail-closed runner error with a stable code."""
def __init__(self, code: str, message: str | None = None):
self.code = code
super().__init__(message or code)
def _canon(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
def _digest(value: Any) -> str:
return hashlib.sha256(_canon(value)).hexdigest()
def _require_oid(name: str, value: Any) -> str:
value = str(value or "")
if not OID_RE.fullmatch(value):
raise UnattendedReleaseError("INVALID_" + name.upper())
return value.lower()
def _require_sha(name: str, value: Any) -> str:
value = str(value or "")
if not SHA256_RE.fullmatch(value):
raise UnattendedReleaseError("INVALID_" + name.upper())
return value.lower()
@dataclass(frozen=True)
class SourcePublicationIntent:
"""Immutable, deterministic source and execution binding."""
repository: str
source_commit: str
source_tree_oid: str
authority_base_oid: str
target_branch: str
file_allowlist: tuple[str, ...]
requested_capabilities: tuple[str, ...]
risk: str
execution_digest: str
generation: int = 1
event_id: str = "release"
version: str = ""
admission_task_id: str = ""
admission_idempotency_key: str = ""
def __post_init__(self) -> None:
if not self.repository or not isinstance(self.repository, str):
raise UnattendedReleaseError("INVALID_REPOSITORY")
for name in ("source_commit", "source_tree_oid", "authority_base_oid"):
object.__setattr__(self, name, _require_oid(name, getattr(self, name)))
if not isinstance(self.target_branch, str) or not self.target_branch or not self.target_branch.startswith("agent/") or self.target_branch.startswith("-"):
raise UnattendedReleaseError("INVALID_TARGET_BRANCH")
if ".." in self.target_branch or "//" in self.target_branch or "@{" in self.target_branch:
raise UnattendedReleaseError("INVALID_TARGET_BRANCH")
if not isinstance(self.generation, int) or isinstance(self.generation, bool) or self.generation < 1:
raise UnattendedReleaseError("INVALID_GENERATION")
object.__setattr__(self, "execution_digest", _require_sha("execution_digest", self.execution_digest))
if not self.file_allowlist or any(not isinstance(p, str) or not p or p.startswith(("/", "\\")) or ".." in p or "\\" in p or p == ".git" or p.startswith(".git/") for p in self.file_allowlist):
raise UnattendedReleaseError("INVALID_FILE_ALLOWLIST")
if len(set(self.file_allowlist)) != len(self.file_allowlist):
raise UnattendedReleaseError("DUPLICATE_FILE_ALLOWLIST")
if not self.requested_capabilities or any(not isinstance(c, str) or not c or "*" in c for c in self.requested_capabilities):
raise UnattendedReleaseError("INVALID_CAPABILITIES")
if not isinstance(self.risk, str) or self.risk.lower() not in RISKS:
raise UnattendedReleaseError("INVALID_RISK")
object.__setattr__(self, "risk", self.risk.lower())
object.__setattr__(self, "file_allowlist", tuple(sorted(self.file_allowlist)))
object.__setattr__(self, "requested_capabilities", tuple(sorted(set(self.requested_capabilities))))
if not IDENT_RE.fullmatch(self.event_id):
raise UnattendedReleaseError("INVALID_EVENT_ID")
if self.version and not IDENT_RE.fullmatch(self.version):
raise UnattendedReleaseError("INVALID_VERSION")
if bool(self.admission_task_id) != bool(self.admission_idempotency_key):
raise UnattendedReleaseError("INCOMPLETE_ADMISSION_IDENTITY")
for value in (self.admission_task_id, self.admission_idempotency_key):
if value and (not isinstance(value, str) or not IDENT_RE.fullmatch(value)):
raise UnattendedReleaseError("INVALID_ADMISSION_IDENTITY")
def as_dict(self) -> dict[str, Any]:
value = {
"repository": self.repository, "source_commit": self.source_commit.lower(),
"source_tree_oid": self.source_tree_oid.lower(), "authority_base_oid": self.authority_base_oid.lower(),
"target_branch": self.target_branch, "file_allowlist": list(self.file_allowlist),
"requested_capabilities": list(self.requested_capabilities), "risk": self.risk,
"execution_digest": self.execution_digest.lower(), "generation": self.generation,
"event_id": self.event_id, "version": self.version,
}
if self.admission_task_id:
value.update(admission_task_id=self.admission_task_id,
admission_idempotency_key=self.admission_idempotency_key)
return value
@property
def task_id(self) -> str:
return self.admission_task_id or "release-task-" + _digest(self.as_dict())[:32]
@property
def idempotency_key(self) -> str:
return self.admission_idempotency_key or "release-intake-" + _digest({"task_id": self.task_id, "execution_digest": self.execution_digest.lower()})[:32]
def admission_request(self) -> dict[str, Any]:
"""Return the sole normalized request accepted by Task admission."""
return {"idempotency_key": self.idempotency_key, "task_id": self.task_id,
"repository": self.repository, "authority_base_oid": self.authority_base_oid,
"target_branch": self.target_branch, "generation": self.generation,
"execution_digest": self.execution_digest, "requested_capabilities": list(self.requested_capabilities),
"file_allowlist": list(self.file_allowlist), "risk": self.risk}
def _error_code(exc: BaseException) -> str:
code = getattr(exc, "code", None) or getattr(exc, "status", None) or exc.__class__.__name__
value = str(code).upper()
return value if re.fullmatch(r"[A-Z0-9_]{1,100}", value) else "BACKEND_ERROR"
def _bound_receipt(receipt: Any, intent: SourcePublicationIntent) -> bool:
if not isinstance(receipt, Mapping):
return False
expected = {"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest}
return (receipt.get("schema") == "aios.task-admission-receipt.v1"
and all(_canon(receipt.get(k)) == _canon(v) for k, v in expected.items())
and receipt.get("task_state") in {"QUEUED", "CLAIMED", "RUNNING"}
and all(isinstance(receipt.get(k), str) and receipt[k] for k in ("envelope_id", "grant_id")))
def _retryable(exc: BaseException) -> bool:
status = getattr(exc, "status_code", None) or getattr(exc, "http_status", None) or getattr(exc, "status", None)
if isinstance(status, int):
return status in RETRYABLE_CODES
code = _error_code(exc)
return code in RETRYABLE_NAMES
def _unknown_outcome(exc: BaseException) -> bool:
return isinstance(exc, (TimeoutError, ConnectionError, OSError)) or _error_code(exc) in {"TASK_ADMISSION_TASK_STATE_UNKNOWN", "TIMEOUT", "TIMED_OUT", "CONNECTION_LOST"}
class GovernedUnattendedReleaseRunner:
"""Phase-separated runner; preparation has no release/deployment rights."""
def __init__(self, *, admission: Any, release_service: Any = None,
publication_adapter: Any = None, owner_approval_broker: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None = None,
deployment_enabled: bool = False, deployment_service: Any = None,
max_attempts: int = 3, deadline_seconds: float = 30.0,
environment_mode: str = "SIMULATION",
clock: Callable[[], float] = time.monotonic, sleep: Callable[[float], None] = time.sleep):
if admission is None:
raise ValueError("admission is required")
if max_attempts < 1 or max_attempts > 10 or deadline_seconds <= 0:
raise ValueError("invalid retry bounds")
self.admission = admission
self.environment_mode = str(environment_mode).upper()
if self.environment_mode not in {"SIMULATION", "AUTHORITY"}:
raise ValueError("environment_mode must be SIMULATION or AUTHORITY")
self.release_service = release_service
self.publication_adapter = publication_adapter
self.owner_approval_broker = owner_approval_broker
self.deployment_enabled = bool(deployment_enabled)
self.deployment_service = deployment_service
self.max_attempts = max_attempts
self.deadline_seconds = deadline_seconds
self.clock, self.sleep = clock, sleep
def intake(self, intent: SourcePublicationIntent) -> dict[str, Any]:
if not isinstance(intent, SourcePublicationIntent):
raise UnattendedReleaseError("INTENT_TYPE_REQUIRED")
return {"phase": "PREPARATION", "mode": self.environment_mode, "task_id": intent.task_id,
"idempotency_key": intent.idempotency_key, "intent": intent.as_dict(),
"release_rights": False, "deployment_rights": False}
def admit(self, intent: SourcePublicationIntent, authorization: Mapping[str, Any]) -> dict[str, Any]:
if not isinstance(authorization, Mapping):
raise UnattendedReleaseError("AUTHORIZATION_REQUIRED")
if self.environment_mode == "SIMULATION" and getattr(self.admission, "simulation_only", False) is not True:
return {"phase": "BLOCKED", "mode": self.environment_mode, "task_id": intent.task_id, "error": "AUTHORITY_MODE_REQUIRED"}
raw = intent.admission_request()
deadline = self.clock() + self.deadline_seconds
envelope_id = None
for attempt in range(1, self.max_attempts + 1):
if self.clock() >= deadline:
return {"phase": "RECONCILIATION_REQUIRED" if envelope_id else "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": "ADMISSION_DEADLINE_EXCEEDED"}
try:
created = self.admission.create_envelope(raw)
envelope = created.get("envelope", created) if isinstance(created, Mapping) else {}
envelope_id = envelope.get("envelope_id") or envelope_id
if not isinstance(envelope_id, str) or not envelope_id:
raise UnattendedReleaseError("ENVELOPE_BINDING_MISSING")
auth = dict(authorization)
auth["envelope_id"] = envelope_id
if self.clock() >= deadline:
return self._reconcile(envelope_id, intent, "ADMISSION_DEADLINE_EXCEEDED", authorization)
receipt = self.admission.authorize_and_admit(auth)
if not _bound_receipt(receipt, intent) or receipt["envelope_id"] != envelope_id:
raise UnattendedReleaseError("ADMISSION_RECEIPT_BINDING_MISMATCH")
if self.clock() >= deadline:
return self._reconcile(envelope_id, intent, "ADMISSION_DEADLINE_EXCEEDED", authorization)
return {"phase": "ADMITTED", "mode": self.environment_mode, "attempt": attempt,
"envelope": created, "receipt": receipt}
except Exception as exc:
status_code = getattr(exc, "status_code", None) or getattr(exc, "http_status", None) or getattr(exc, "status", None)
if status_code == 403 or _error_code(exc) in {"403", "FORBIDDEN"}:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": _error_code(exc)}
# Once authorization may have run, every failure reconciles.
# A 5xx is never sufficient proof of NOT_APPLIED.
if envelope_id:
return self._reconcile(envelope_id, intent, _error_code(exc), authorization)
if not _retryable(exc) or self.clock() >= deadline or attempt >= self.max_attempts:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": _error_code(exc)}
self.sleep(min(0.05 * (2 ** (attempt - 1)), max(0.0, deadline - self.clock())))
raise UnattendedReleaseError("ADMISSION_RETRY_EXHAUSTED")
def _reconcile(self, envelope_id: str | None, intent: SourcePublicationIntent, error: str,
authorization: Mapping[str, Any]) -> dict[str, Any]:
if not envelope_id:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": error}
try:
reconcile = getattr(self.admission, "reconcile_status", None)
status = reconcile(envelope_id, authorization) if callable(reconcile) else self.admission.status(envelope_id)
except Exception as exc:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": error, "status_error": _error_code(exc)}
state = str(status.get("status") or status.get("task_state") or "").upper() if isinstance(status, Mapping) else ""
if state in {"NOT_APPLIED", "ABSENT", "NOT_FOUND"}:
return {"phase": "RETRYABLE_RECONCILIATION", "mode": "AUTHORITY", "task_id": intent.task_id, "status": status, "error": error}
if state == "ADMITTED" and _bound_receipt(status.get("task"), intent):
return {"phase": "RECONCILED", "mode": self.environment_mode, "task_id": intent.task_id, "status": status, "receipt": status["task"]}
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "status": status, "error": error}
def publish(self, intent: SourcePublicationIntent, admission_receipt: Mapping[str, Any]) -> dict[str, Any]:
if self.environment_mode != "AUTHORITY":
return {"phase": "BLOCKED", "mode": self.environment_mode, "error": "AUTHORITY_MODE_REQUIRED"}
if self.publication_adapter is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_BACKEND_MISSING"}
required = ("task_lease", "task_principal", "writer_lease", "commit_receipt", "source_oid", "tree_oid", "authenticated")
try:
if not _bound_receipt(admission_receipt, intent):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "ADMISSION_RECEIPT_BINDING_MISMATCH"}
result = self.publication_adapter.publish(intent, admission_receipt)
if not isinstance(result, Mapping) or result.get("authenticated") is not True or any(not result.get(k) for k in required[:-1]):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_LEASE_CHAIN_MISSING"}
if any(not isinstance(result[k], Mapping) or result[k].get("task_id") != intent.task_id
for k in ("task_lease", "task_principal", "writer_lease")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_LEASE_BINDING_MISMATCH"}
commit = result["commit_receipt"]
# Existing continuation receipts carry remote OIDs, while the
# authenticated task/job readback carries scope. Preserve both
# originals; never fabricate missing fields inside a receipt.
optional_bindings = {"task_id": intent.task_id, "tree_oid": result["tree_oid"],
"execution_digest": intent.execution_digest,
"target_branch": intent.target_branch,
"base_oid": intent.authority_base_oid}
if (not isinstance(commit, Mapping) or commit.get("commit_oid") != result["source_oid"]
or any(key in commit and commit[key] != value for key, value in optional_bindings.items())
or result.get("generation") != intent.generation):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_COMMIT_BINDING_MISMATCH"}
if not OID_RE.fullmatch(str(result["source_oid"])) or str(result["tree_oid"]).lower() != intent.source_tree_oid.lower() or any(str(result.get(k, "")) != str(v) for k, v in (("task_id", intent.task_id), ("authority_base_oid", intent.authority_base_oid), ("target_branch", intent.target_branch), ("execution_digest", intent.execution_digest))):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_RC_BINDING_MISMATCH"}
return {"phase": "PUBLISHED", "mode": "AUTHORITY", "readback": dict(result)}
except Exception as exc:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "error": _error_code(exc)}
def postpublication_rc(self, *, event_id: str, source_commit: str, version: str = "", source_readback: Mapping[str, Any] | None = None) -> dict[str, Any]:
if self.environment_mode != "AUTHORITY":
return {"phase": "BLOCKED", "mode": self.environment_mode, "error": "AUTHORITY_MODE_REQUIRED"}
if not isinstance(source_readback, Mapping) or source_readback.get("authenticated") is not True or str(source_readback.get("source_commit", "")).lower() != str(source_commit).lower():
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "SOURCE_READBACK_REQUIRED"}
if self.release_service is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "RELEASE_BACKEND_MISSING"}
try:
# Only a host-injected authenticated resolver can admit a source.
# The caller's readback object alone is never authorization.
resolver = getattr(self.release_service, "source_admission_resolver", None)
admitted_source = resolver(source_commit) if callable(resolver) else None
if (not isinstance(admitted_source, Mapping) or not admitted_source.get("admission_id")
or admitted_source.get("source_commit") != source_commit
or admitted_source.get("tree_oid") != source_readback.get("tree_oid")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "AUTHENTICATED_SOURCE_ADMISSION_REQUIRED"}
intent = self.release_service.create_release_intent({"event_id": event_id, "source_commit": source_commit, **({"version": version} if version else {})})
candidate_id = str(intent.get("candidate_id") or intent.get("candidate", {}).get("candidate_id") or "")
if not candidate_id:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "CANDIDATE_BINDING_MISSING"}
readers = ("get_release_candidate", "get_release_plan", "get_acceptance")
if any(not callable(getattr(self.release_service, name, None)) for name in readers):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "RC_READBACK_BACKEND_MISSING"}
candidate = self.release_service.get_release_candidate(candidate_id)
plan = self.release_service.get_release_plan(candidate_id)
acceptance = self.release_service.get_acceptance(candidate_id)
deployment = plan.get("deployment") if isinstance(plan, Mapping) else None
if (not isinstance(candidate, Mapping) or not isinstance(plan, Mapping) or not isinstance(deployment, Mapping) or not isinstance(acceptance, Mapping)
or str(candidate.get("candidate_id")) != candidate_id
or str(candidate.get("source_commit")) .lower() != str(source_commit).lower()
or str(plan.get("candidate_id", candidate_id)) != candidate_id
or str(deployment.get("source_commit")) .lower() != str(source_commit).lower()
or str(deployment.get("tree_oid", "")).lower() != str(source_readback.get("tree_oid", "")).lower()
or candidate.get("tree_oid") != deployment.get("tree_oid")
or candidate.get("artifact_sha256") != deployment.get("artifact_sha256")
or candidate.get("manifest_sha256") != deployment.get("manifest_sha256")
or str(acceptance.get("candidate_id")) != candidate_id
or acceptance.get("release_plan_sha256") != plan.get("sha256")
or str(acceptance.get("status")) != "ACCEPTED"):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "RC_READBACK_BINDING_MISMATCH"}
# Public readback is redacted. Reverify original signed bytes inside
# the existing service, not by reconstructing them from API output.
verify_acceptance = getattr(self.release_service, "verify_release_acceptance", None)
if not callable(verify_acceptance):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "BUILDER_ATTESTATION_REQUIRED"}
verified = verify_acceptance(candidate_id)
if (not isinstance(verified, Mapping) or verified.get("status") != "VERIFIED"
or verified.get("candidate_id") != candidate_id
or verified.get("release_plan_sha256") != plan.get("sha256")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "BUILDER_ATTESTATION_NOT_VERIFIED"}
envelope = self.release_service.get_approval_envelope(candidate_id)
if self.owner_approval_broker is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "OWNER_APPROVAL_BACKEND_MISSING"}
approval = self.owner_approval_broker(dict(envelope))
if not isinstance(approval, Mapping):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "OWNER_APPROVAL_MISSING"}
accepted = self.release_service.submit_owner_approval(dict(approval))
if not isinstance(accepted, Mapping) or accepted.get("status") != "VERIFIED_AND_STORED":
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "APPROVAL_NOT_VERIFIED"}
return {"phase": "APPROVED", "mode": "AUTHORITY", "candidate_id": candidate_id,
"readback": {"candidate": dict(candidate), "plan": dict(plan), "acceptance": dict(acceptance)},
"approval": accepted}
except Exception as exc:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": _error_code(exc)}
def deploy(self, candidate_id: str) -> dict[str, Any]:
if (self.environment_mode != "AUTHORITY" or not self.deployment_enabled or self.deployment_service is None
or getattr(self.deployment_service, "execution_enabled", False) is not True):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "DEPLOYMENT_NOT_ENABLED"}
try:
result = self.deployment_service.start_deployment(candidate_id)
saga = self.deployment_service.get_release_saga(candidate_id)
if (not isinstance(result, Mapping) or result.get("candidate_id") != candidate_id
or result.get("state") != "COMPLETED" or result.get("status") not in {"VERIFIED", "IDEMPOTENT"}
or not isinstance(saga, Mapping) or saga.get("state") != "COMPLETED"):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "DEPLOYMENT_NOT_VERIFIED", "result": dict(result) if isinstance(result, Mapping) else {}}
return {"phase": "DEPLOYED", "mode": "AUTHORITY", "result": dict(result)}
except Exception as exc:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": _error_code(exc)}
@@ -0,0 +1,136 @@
"""Local adapter for the root-owned registered Owner signer service.
The adapter intentionally has no public arbitrary-payload signing method.
``LocalOwnerAuthority`` calls the private typed seam after validating and
canonicalizing a structured Owner authorization object.
"""
from __future__ import annotations
import os
from pathlib import Path
import stat
import subprocess
import tempfile
from typing import Any, Callable
DEFAULT_REGISTERED_SIGNER_COMMAND = Path("/usr/local/libexec/aios-grant-signer-adapter")
OWNER_NAMESPACE = "aios-owner-approval-v1"
MAX_SIGNATURE_BYTES = 128 * 1024
MAX_KEY_ID_BYTES = 512
class RegisteredOwnerSignerError(RuntimeError):
pass
class RegisteredOwnerSignerAdapter:
"""Invoke the registered signer without reading or copying private keys."""
def __init__(
self,
command: Path | str = DEFAULT_REGISTERED_SIGNER_COMMAND,
*,
key_id: str,
owner_verifier: Any,
require_root: bool = True,
test_mode: bool = False,
runner: Callable[..., Any] = subprocess.run,
) -> None:
self.command = str(command)
self.key_id = key_id
self.owner_verifier = owner_verifier
self.require_root = require_root
self.test_mode = test_mode
self.runner = runner
if not isinstance(key_id, str) or not key_id:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_KEY_ID_REQUIRED")
if not test_mode:
self._validate_command()
@property
def fingerprint(self) -> str:
return self.key_id
def _validate_command(self) -> None:
path = Path(self.command)
if not path.is_absolute() or path.is_symlink() or not path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_INVALID")
st = path.stat()
if not hasattr(os, "geteuid") or st.st_uid != os.geteuid():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_OWNER_INVALID")
if stat.S_IMODE(st.st_mode) & 0o022:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_PERMISSIONS_INVALID")
def _read_output(self, path: Path, maximum: int) -> bytes:
if path.is_symlink() or not path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_INVALID")
st = path.stat()
if not self.test_mode and (not hasattr(os, "geteuid") or st.st_uid != os.geteuid()):
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_OWNER_INVALID")
if st.st_size > maximum:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_TOO_LARGE")
return path.read_bytes()
def _sign_canonical_payload(self, namespace: str, payload: bytes) -> str:
"""Private seam used only by typed ``LocalOwnerAuthority`` issuance."""
if namespace != OWNER_NAMESPACE:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_NAMESPACE_INVALID")
if not isinstance(payload, bytes) or not payload:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_PAYLOAD_INVALID")
if self.require_root:
if not hasattr(os, "geteuid") or os.geteuid() != 0:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_ROOT_REQUIRED")
if not self.test_mode:
self._validate_command()
with tempfile.TemporaryDirectory(prefix="aios-owner-sign-") as directory:
root = Path(directory)
input_path = root / "payload"
signature_path = root / "payload.sig"
key_id_path = root / "key-id"
input_path.write_bytes(payload)
try:
result = self.runner(
[
self.command,
"sign",
"--namespace",
namespace,
"--input",
str(input_path),
"--signature-out",
str(signature_path),
"--key-id-out",
str(key_id_path),
],
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=30,
)
except Exception as exc:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_FAILED") from exc
if getattr(result, "returncode", None) != 0:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_FAILED")
if not signature_path.is_file() or not key_id_path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_MISSING")
supplied_key_id = self._read_output(key_id_path, MAX_KEY_ID_BYTES).decode("utf-8").strip()
if supplied_key_id != self.key_id:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_KEY_ID_MISMATCH")
signature = self._read_output(signature_path, MAX_SIGNATURE_BYTES).decode("utf-8")
if not signature.startswith("-----BEGIN SSH SIGNATURE-----"):
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_SIGNATURE_INVALID")
try:
verified = self.owner_verifier.verify(
namespace=namespace,
payload=payload,
signature=signature,
key_id=self.key_id,
)
except Exception as exc:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_VERIFY_FAILED") from exc
if verified is not True:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_VERIFY_REJECTED")
return signature
@@ -4203,6 +4203,32 @@ class SilentReleaseActionService:
"side_effects_executed": False,
}
def verify_release_acceptance(self, candidate_id: str) -> dict[str, Any]:
"""Reverify durable originals without exposing signed data via the API."""
if self.builder_attestation_verifier is None:
raise CapabilityAuthorizationError("builder verifier is required")
candidate = self.candidates.load(candidate_id)
plan = self.release_plans.load(candidate_id)
record = json.loads(
_safe_child(self.acceptance_root, candidate_id).read_text(encoding="utf-8")
)
if (record.get("candidate_id") != candidate_id
or record.get("release_plan_sha256") != plan.sha256
or record.get("status") != "ACCEPTED"
or not isinstance(record.get("attestation"), Mapping)):
raise CapabilityAuthorizationError("acceptance binding is invalid")
attestation = BuilderAttestation.from_dict(
record["attestation"], require_fingerprint=True
)
self.builder_attestation_verifier.verify(attestation, candidate, plan)
return {
"candidate_id": candidate_id,
"candidate_fingerprint": candidate.fingerprint,
"release_plan_sha256": plan.sha256,
"attestation_fingerprint": attestation.fingerprint,
"status": "VERIFIED",
}
def get_capability_status(
self, candidate_id: str
) -> dict[str, Any]:
@@ -0,0 +1,271 @@
"""Exact source-only admission composition within the existing Bridge.
This module neither installs itself nor issues authority. A host activation must
pin an Owner-signed policy and an independently verified epoch. The existing
Task admission store, V3 producer, Task Plane and principal verifier are reused.
"""
from __future__ import annotations
import hashlib
import base64
import json
from pathlib import Path
from typing import Any, Mapping
from aios_single_host_authority import (
SignedArtifact, canonical_json_bytes, _open_secure,
)
from aios_task_admission_bootstrap import TaskAdmissionError
from aios_task_admission_bridge_contract import TaskAdmissionBridgeContract
from aios_task_admission_live_adapters import TaskAdmissionRuntimeBundle
from governed_source_publication_policy import (
ScopedSourcePublicationPolicyAdmission, SourcePublicationPolicyVerifier,
)
def _read_object(path: Path) -> tuple[bytes, dict[str, Any]]:
raw = _open_secure(path, private=False, require_root_owner=True, maximum=1024 * 1024)
value = json.loads(raw)
if not isinstance(value, dict):
raise ValueError("SOURCE_ACTIVATION_OBJECT_REQUIRED")
return raw, value
class ScopedPrincipalProvider:
"""Policy revocation also fences principals from already admitted Tasks."""
def __init__(self, guarded: ScopedSourcePublicationPolicyAdmission, delegate: Any):
self.guarded = guarded
self.delegate = delegate
def readiness(self) -> Mapping[str, Any]:
try:
self.guarded.verifier.require(self.guarded.artifact, self.guarded.request)
return self.delegate.readiness()
except Exception as exc:
return {"ready": False, "mode": "source-publication-only", "error": str(exc)}
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]) -> Any:
grant = self.delegate.store.get_grant_by_task_id(str(request.get("task_id") or ""))
if grant is None or grant.parent_epoch_id != self.delegate.epoch_id:
raise TaskAdmissionError("SOURCE_PRINCIPAL_GRANT_REQUIRED", 403)
# status rechecks the signed policy, exact durable envelope and original
# signed epoch. The delegate then verifies the live TaskLease/state.
self.guarded.status({"envelope_id": grant.envelope_id,
"delegation_epoch_id": grant.parent_epoch_id})
return self.delegate(transport, request)
class _AdmissionContext:
"""Metadata only for legacy hosts retaining their signed principal verifier."""
def __init__(self, bundle):
self.store, self.authorization = bundle.store, bundle.authorization
self.task_port, self.epoch_id = bundle.task_port, bundle.epoch_id
def readiness(self):
return {"ready": False, "error": "REGISTERED_SIGNED_PRINCIPAL_VERIFIER_REQUIRED"}
def __call__(self, *_args, **_kwargs):
raise TaskAdmissionError("REGISTERED_SIGNED_PRINCIPAL_VERIFIER_REQUIRED", 403)
def guard_source_runtime(
runtime: TaskAdmissionRuntimeBundle, *, artifact: SignedArtifact,
verifier: SourcePublicationPolicyVerifier, expected_agent_id: str,
) -> TaskAdmissionRuntimeBundle:
service = runtime.service
if service is None:
raise ValueError("SOURCE_ACTIVATION_BOOTSTRAP_REQUIRED")
guarded = ScopedSourcePublicationPolicyAdmission(
verifier, bootstrap=service, artifact=artifact,
expected_agent_id=expected_agent_id,
)
# Build the typed request through the same exact-envelope guard used by the
# HTTP contract, instead of accepting a caller-supplied request projection.
payload = artifact.payload
request = guarded._request_for_raw({
"repository": payload["repository"],
"authority_base_oid": payload["authority_base_oid"],
"target_branch": payload["target_branch"], "generation": payload["generation"],
"execution_digest": payload["execution_digest"], "risk": payload["risk"],
"file_allowlist": payload["exact_files"], "requested_capabilities": payload["capabilities"],
})
guarded.request = request
verifier.require(artifact, request)
principal = runtime.principal_verifier or _AdmissionContext(runtime)
epoch = principal.authorization.verify_delegation_epoch(principal.epoch_id)
# The complete per-envelope check runs again on authorization and every
# principal use; reject a production-capable epoch already at composition.
if (getattr(epoch, "production_scope", None) != "none"
or getattr(epoch, "destructive_policy", None) != "forbidden"):
raise TaskAdmissionError("SOURCE_ACTIVATION_EPOCH_SCOPE_DENIED", 403)
return TaskAdmissionRuntimeBundle(
handlers=TaskAdmissionBridgeContract(guarded).handlers(),
principal_verifier=ScopedPrincipalProvider(guarded, principal),
service=guarded,
)
def build_source_runtime_from_activation_file(path: Path, *, post_admission: Any,
ordinary_activation_file: Path):
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
load_owner_approval_verifier,
)
_, config = _read_object(path)
if set(config) != {"schema", "policy_artifact", "policy_sha256", "policy_revocations",
"admission_activation", "expected_agent_id"} or config["schema"] != "aios.source-publication-host-activation.v1":
raise ValueError("SOURCE_ACTIVATION_FIELDS_INVALID")
for key in ("policy_artifact", "policy_revocations", "admission_activation"):
value = config[key]
if not isinstance(value, str) or not value.startswith("/") or ".." in Path(value).parts:
raise ValueError("SOURCE_ACTIVATION_PATH_INVALID")
raw, signed = _read_object(Path(config["policy_artifact"]))
if hashlib.sha256(raw).hexdigest() != config["policy_sha256"]:
raise ValueError("SOURCE_ACTIVATION_POLICY_HASH_MISMATCH")
if set(signed) != {"payload", "signature", "key_id", "namespace"}:
raise ValueError("SOURCE_ACTIVATION_ARTIFACT_INVALID")
artifact = SignedArtifact(canonical_payload=canonical_json_bytes(signed["payload"]), **signed)
_, ordinary_config = _read_object(ordinary_activation_file)
_, source_config = _read_object(Path(config["admission_activation"]))
for field in ("state_db", "task_api_url", "task_control_token_file", "owner_verifier_module"):
if not ordinary_config.get(field) or source_config.get(field) != ordinary_config[field]:
raise ValueError("SOURCE_ACTIVATION_CONTROL_PLANE_BINDING_MISMATCH")
def not_revoked(payload: Mapping[str, Any]) -> bool:
_, value = _read_object(Path(config["policy_revocations"]))
if (set(value) != {"schema", "revoked_nonces"}
or value["schema"] != "aios.source-publication-revocations.v1"
or not isinstance(value["revoked_nonces"], list)
or any(not isinstance(nonce, str) or not nonce for nonce in value["revoked_nonces"])):
return False
return payload["nonce"] not in value["revoked_nonces"]
runtime = build_task_admission_runtime_from_activation_file(
Path(config["admission_activation"]), post_admission=post_admission)
verifier = SourcePublicationPolicyVerifier(
load_owner_approval_verifier(Path(source_config["owner_verifier_module"])),
revocation_check=not_revoked,
expected_policy_digest=runtime.service.policy.normalized_policy_digest(),
)
guarded_runtime = guard_source_runtime(runtime, artifact=artifact, verifier=verifier,
expected_agent_id=config["expected_agent_id"])
payload = artifact.payload
plan_path = Path(source_config["execution_plan_dir"]) / (payload["execution_digest"] + ".json")
plan_bytes, plan = _read_object(plan_path)
if (hashlib.sha256(plan_bytes).hexdigest() != payload["execution_digest"]
or canonical_json_bytes(plan) != plan_bytes):
raise ValueError("SOURCE_ACTIVATION_PLAN_DIGEST_MISMATCH")
hashes = plan.get("file_sha256")
if not isinstance(hashes, dict) or set(hashes) != set(payload["exact_files"]):
raise ValueError("SOURCE_ACTIVATION_FILE_HASH_SET_MISMATCH")
for field in ("repository", "authority_base_oid", "target_branch", "generation", "risk"):
if plan.get(field) != payload[field]:
raise ValueError("SOURCE_ACTIVATION_PLAN_SCOPE_MISMATCH")
if sorted(plan.get("capabilities", [])) != sorted(payload["capabilities"]):
raise ValueError("SOURCE_ACTIVATION_PLAN_CAPABILITIES_MISMATCH")
if not isinstance(plan.get("task_id"), str) or not plan["task_id"]:
raise ValueError("SOURCE_ACTIVATION_PLAN_TASK_REQUIRED")
guarded_runtime.service.expected_file_sha256 = dict(hashes)
guarded_runtime.service.expected_task_id = plan["task_id"]
return guarded_runtime
class AdmissionRuntimeRouter:
"""Route an exact branch/epoch without widening the ordinary policy."""
def __init__(self, ordinary: TaskAdmissionRuntimeBundle, source: TaskAdmissionRuntimeBundle):
self.ordinary = ordinary
self.source = source
self.guard = source.service
self.store = self.guard.bootstrap.store
def _source_request(self, operation: str, request: Mapping[str, Any]) -> bool:
if operation == "getTaskAdmissionEnvelope":
return request.get("target_branch") == self.guard.artifact.payload["target_branch"]
envelope_id = str(request.get("envelope_id") or "")
row = self.store.get_envelope(envelope_id)
if row is None:
# Never fall through after finding a source envelope, including
# when it has expired or been revoked.
return False
envelope = self.guard.bootstrap._load_envelope(envelope_id)
return envelope.target_branch == self.guard.artifact.payload["target_branch"]
def handlers(self) -> dict[str, Any]:
def dispatch(operation: str, request: Mapping[str, Any]):
runtime = self.source if self._source_request(operation, request) else self.ordinary
return runtime.handlers[operation](request)
return {op: (lambda request, op=op: dispatch(op, request)) for op in self.source.handlers}
def readiness(self) -> Mapping[str, Any]:
return {"mode": "scoped-admission-router", "ordinary": self.ordinary.principal_verifier.readiness(),
"source_publication": self.source.principal_verifier.readiness()}
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]):
grant = self.store.get_grant_by_task_id(str(request.get("task_id") or ""))
provider = (self.source.principal_verifier if grant is not None
and grant.target_branch == self.guard.artifact.payload["target_branch"]
else self.ordinary.principal_verifier)
return provider(transport, request)
def bundle(self) -> TaskAdmissionRuntimeBundle:
return TaskAdmissionRuntimeBundle(handlers=self.handlers(), principal_verifier=self)
class ExistingPrincipalPolicyFence:
"""Add a C5 policy fence while retaining the registered signed verifier.
Layered staging hosts use a signed TaskPrincipal verifier with their own
storage scope. They must not be migrated to a server attestation merely to
add source admission. Every request still reaches the original verifier.
"""
def __init__(self, delegate: Any, source_runtime: TaskAdmissionRuntimeBundle):
self.delegate = delegate
self.guard = source_runtime.service
def __getattr__(self, name: str):
return getattr(self.delegate, name)
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]):
grant = self.guard.bootstrap.store.get_grant_by_task_id(str(request.get("task_id") or ""))
if grant is not None and grant.target_branch == self.guard.artifact.payload["target_branch"]:
self.guard.status({"envelope_id": grant.envelope_id,
"delegation_epoch_id": grant.parent_epoch_id})
if transport.get("operation") in {"commitFilesGoverned", "commitFileGoverned"}:
expected = getattr(self.guard, "expected_file_sha256", None)
if not isinstance(expected, dict) or not expected:
raise TaskAdmissionError("SOURCE_COMMIT_FILE_HASHES_REQUIRED", 403)
try:
raw = request.get("files")
if raw is None:
raw = {request["path"]: request["content_base64"]}
pairs = list(raw.items()) if isinstance(raw, dict) else [(item["path"], item["content_base64"]) for item in raw]
if len({path for path, _ in pairs}) != len(pairs):
raise ValueError("duplicate path")
actual = {path: hashlib.sha256(base64.b64decode(data, validate=True)).hexdigest()
for path, data in pairs}
except Exception as exc:
raise TaskAdmissionError("SOURCE_COMMIT_FILE_PAYLOAD_INVALID", 403) from exc
if actual != expected:
raise TaskAdmissionError("SOURCE_COMMIT_CONTENT_SCOPE_MISMATCH", 403)
return self.delegate(transport, request)
def expired_admission_runtime() -> TaskAdmissionRuntimeBundle:
"""Keep a verified-expired ordinary epoch denied while serving other routes."""
def deny(*_args: Any, **_kwargs: Any):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_EXPIRED", 403)
class ClosedPrincipal:
__call__ = staticmethod(deny)
def readiness(self):
return {"ready": False, "error": "TASK_ADMISSION_EPOCH_EXPIRED"}
from aios_task_admission_bridge_contract import TASK_ADMISSION_OPERATION_REQUIRED_FIELDS
return TaskAdmissionRuntimeBundle(
handlers={op: deny for op in TASK_ADMISSION_OPERATION_REQUIRED_FIELDS},
principal_verifier=ClosedPrincipal(),
)
+3
View File
@@ -117,6 +117,9 @@ class TaskPlaneService:
else None
)
self.store.probe_attestation_verifier = self.policy.probe_attestation_verifier
self.store.evidence_attestation_verifier = (
self.policy.evidence_attestation_verifier
)
def consume_one_root_action(
self,
@@ -46,6 +46,7 @@ class GovernedWriterState(str, Enum):
RISK_ORDER = {
"low": 10,
"medium": 20,
"engineering": 25,
"high": 30,
"critical": 40,
}
@@ -37,7 +37,7 @@ import urllib.parse
import urllib.request
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Mapping, Sequence
from typing import Any, Callable, Mapping, Sequence
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
@@ -205,11 +205,14 @@ class LiveClaim:
fencing_generation: int | None
lease_expires_at: float | None
task_revision: int | None
public_receipt: Mapping[str, Any] = field(default_factory=dict)
@dataclass(frozen=True)
class LiveWriterLease:
writer_lease_id: str
# /v2/writer-lease is a validated binding projection. The durable branch
# lease is created only by the continuation executor during commit.
writer_lease_id: str | None
task_id: str
job_id: str
branch_mode: str
@@ -229,13 +232,16 @@ class LiveWriterSession:
job_id: str
branch_mode: str
expected_branch_oid: str
writer_lease_id: str
writer_lease_id: str | None
continuation_key: str
principal_issue_schema: str
agent_token_handle: str = field(repr=False)
lease_handle: str = field(repr=False)
task_lease_receipt: Mapping[str, Any] = field(default_factory=dict)
writer_lease_binding: Mapping[str, Any] = field(default_factory=dict)
def public_dict(self) -> dict[str, Any]:
return {
public = {
"authorization_id": self.authorization_id,
"task_id": self.task_id,
"agent_id": self.agent_id,
@@ -243,10 +249,11 @@ class LiveWriterSession:
"job_id": self.job_id,
"branch_mode": self.branch_mode,
"expected_branch_oid": self.expected_branch_oid,
"writer_lease_id": self.writer_lease_id,
"continuation_key": self.continuation_key,
"principal_issue_schema": self.principal_issue_schema,
"writer_lease_binding": dict(self.writer_lease_binding),
}
return public
def _parse_epoch(value: Any) -> float | None:
@@ -420,6 +427,10 @@ class LiveAgentTaskClient:
if result.body.get("receipt_revision") is not None
else None
),
public_receipt={k: result.body[k] for k in (
"task_id", "status", "claim_id", "lease_id", "fencing_generation",
"receipt_revision", "lease_expires_at", "attempt", "revision",
) if k in result.body},
)
def heartbeat(
@@ -456,6 +467,49 @@ class LiveAgentTaskClient:
)
return task
def submit_result(
self,
*,
task_id: str,
agent_id: str,
agent_token_handle: str,
lease_handle: str,
idempotency_key: str,
result_value: Mapping[str, Any],
) -> Mapping[str, Any]:
if not isinstance(idempotency_key, str) or not 1 <= len(idempotency_key) <= 160:
raise LiveProtocolError("task result idempotency key invalid")
if not isinstance(result_value, Mapping):
raise LiveProtocolError("task result mapping required")
quoted = urllib.parse.quote(task_id, safe="")
agent_token = self.secrets.get(agent_token_handle)
lease_token = self.secrets.get(lease_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/result",
body={
"lease_token": lease_token,
"idempotency_key": idempotency_key,
"result": dict(result_value),
},
headers={"Authorization": "Bearer " + agent_token},
timeout=15,
)
if result.status == 409:
raise LiveProtocolConflict(
str(result.body.get("error") or "task result conflict")
)
if result.status not in (200, 201):
raise LiveProtocolError(f"task result HTTP {result.status}")
if (
result.body.get("task_id") != task_id
or result.body.get("status") != "VERIFYING"
or result.body.get("claimed_by") != agent_id
or result.body.get("result") != dict(result_value)
):
raise LiveProtocolConflict("task result readback mismatch")
return dict(result.body)
class LiveTaskPrincipalClient:
def __init__(
@@ -468,6 +522,7 @@ class LiveTaskPrincipalClient:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.secrets = secrets_store
self.public_receipts: list[dict[str, Any]] = []
def issue_assertion(
self,
@@ -499,6 +554,11 @@ class LiveTaskPrincipalClient:
last = result
assertion = result.body.get("assertion")
if result.status in (200, 201) and isinstance(assertion, str) and assertion:
self.public_receipts.append({
"task_id": task_id, "operation": operation, "request_id": request_id,
"assertion_sha256": hashlib.sha256(assertion.encode()).hexdigest(),
"issuer_response": {k: result.body[k] for k in ("jti", "expires_at", "replayed") if k in result.body},
})
return assertion
if not (
attempt == 0
@@ -608,23 +668,35 @@ class LiveBridgeClient:
if result.status not in (200, 201):
raise LiveProtocolError(f"writer lease HTTP {result.status}")
body = result.body
if body.get("schema") != "aios.task-writer-lease.v2":
raise LiveProtocolError("writer lease schema mismatch")
if body.get("task_id") != task_id:
raise LiveProtocolError("writer lease task mismatch")
if body.get("expected_branch_oid") not in (None, expected_branch_oid):
raise LiveProtocolConflict("writer lease expected_branch_oid mismatch")
writer_lease_id = body.get("writer_lease_id") or body.get("lease_id")
if not isinstance(writer_lease_id, str) or not writer_lease_id:
raise LiveProtocolError("writer lease response missing lease identifier")
expected_binding = {
"schema": "aios.task-writer-lease.v2",
"task_id": task_id,
"repository": repository,
"base_oid": base_oid,
"expected_branch_oid": expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"lease_seconds": int(self.config.lease_seconds),
}
for field, expected in expected_binding.items():
if body.get(field) != expected:
raise LiveProtocolConflict(f"writer lease {field} mismatch")
projected_branch_mode = body.get("branch_mode")
allowed_branch_modes = {branch_mode}
# CREATE_NEW is materialized by the Bridge before it returns the
# descriptor, so the returned binding is CONTINUE_EXISTING.
if branch_mode == "CREATE_NEW":
allowed_branch_modes.add("CONTINUE_EXISTING")
if projected_branch_mode not in allowed_branch_modes:
raise LiveProtocolConflict("writer lease branch_mode mismatch")
return LiveWriterLease(
writer_lease_id=writer_lease_id,
writer_lease_id=None,
task_id=task_id,
job_id=job_id,
branch_mode=branch_mode,
branch_mode=str(projected_branch_mode),
expected_branch_oid=expected_branch_oid,
generation=int(generation),
expires_at=_parse_epoch(body.get("expires_at")),
expires_at=None,
continuation_key=continuation_key,
raw=dict(body),
)
@@ -696,10 +768,16 @@ class LiveGovernedWriterExecutor:
owner_policy: OwnerAuthorizationPolicy,
authorization_ref: str,
transport: JsonHttpTransport | None = None,
agent_id: str | None = None,
scope_enrollment: Callable[..., Mapping[str, Any]] | None = None,
) -> None:
self.config = config.normalized()
self.owner_policy = owner_policy
self.authorization_ref = authorization_ref
if scope_enrollment is not None and (not callable(scope_enrollment) or not agent_id):
raise ValueError("scope enrollment requires a configured agent identity")
self.agent_id = agent_id
self.scope_enrollment = scope_enrollment
self.transport = transport or JsonHttpTransport()
self.secret_store = SecretHandleStore()
self.agent = LiveAgentTaskClient(
@@ -759,6 +837,7 @@ class LiveGovernedWriterExecutor:
task_id=request.task_id,
capabilities=sorted(request.required_capabilities),
route=route,
agent_id=self.agent_id,
)
claim = self.agent.claim(
task_id=request.task_id,
@@ -778,6 +857,21 @@ class LiveGovernedWriterExecutor:
"remaining governed task window too short after heartbeat"
)
if self.scope_enrollment is not None:
# The existing issuer and Bridge still verify every assertion.
# Enrollment only happens after a real claim has established the
# agent/attempt/lease binding; failures stop before any assertion.
enrollment = self.scope_enrollment(
request=request, job_id=job_id, agent_id=agent_id,
claim=claim, task=running,
)
if (not isinstance(enrollment, Mapping)
or enrollment.get("status") != "ENROLLED"
or enrollment.get("task_id") != request.task_id
or enrollment.get("agent_id") != agent_id
or enrollment.get("job_id") != job_id):
raise LiveProtocolConflict("verified principal scope enrollment required")
live_head = self.git.branch_head(request.target_branch)
if live_head is None:
branch_mode = "CREATE_NEW"
@@ -815,7 +909,10 @@ class LiveGovernedWriterExecutor:
writer_lease_id=writer.writer_lease_id,
continuation_key=writer.continuation_key,
principal_issue_schema=self.config.issue_schema,
agent_token_handle=agent_token_handle,
lease_handle=claim.lease_handle,
task_lease_receipt=dict(claim.public_receipt),
writer_lease_binding=dict(writer.raw),
)
def commit_and_readback(
@@ -857,3 +954,19 @@ class LiveGovernedWriterExecutor:
"branch_head": new_head,
"readback_sha256": readback,
}
def submit_task_result(
self,
*,
session: LiveWriterSession,
result: Mapping[str, Any],
idempotency_key: str,
) -> Mapping[str, Any]:
return self.agent.submit_result(
task_id=session.task_id,
agent_id=session.agent_id,
agent_token_handle=session.agent_token_handle,
lease_handle=session.lease_handle,
idempotency_key=idempotency_key,
result_value=result,
)
+11 -3
View File
@@ -600,9 +600,17 @@ class EvidenceEnvelope:
or self.evidence_digest != compute_sha256_hex(self.digest_payload())
):
return False
if self.signature or self.attestation_ref:
return True if attestation_verifier is None else bool(attestation_verifier(self))
return True
# An unsigned envelope has no proof of origin and must never verify.
if not (self.signature or self.attestation_ref):
return False
# Every proof form requires an injected verifier. A reference or
# signature must never become trusted merely because it is present.
if attestation_verifier is None:
return False
try:
return bool(attestation_verifier(self))
except Exception:
return False
def to_dict(self) -> dict[str, Any]:
return {
+4
View File
@@ -92,6 +92,7 @@ class PolicyEngine:
max_risk: str = "medium",
result_signature_verifier: Callable[[ResultEnvelope], bool] | None = None,
probe_attestation_verifier: Callable[[ProbeEvidence], bool] | None = None,
evidence_attestation_verifier: Callable[[object], bool] | None = None,
identity_capabilities: dict[str, set[str]] | None = None,
admin_identities: set[str] | None = None,
readonly_worker_execution_enabled: bool = False,
@@ -105,6 +106,7 @@ class PolicyEngine:
self.max_risk = max_risk
self.result_signature_verifier = result_signature_verifier
self.probe_attestation_verifier = probe_attestation_verifier
self.evidence_attestation_verifier = evidence_attestation_verifier
self.identity_capabilities = {
identity: set(capabilities)
for identity, capabilities in (identity_capabilities or {}).items()
@@ -195,6 +197,8 @@ class PolicyEngine:
"worker.register": "worker.self_register",
"worker.admin": "worker.admin",
}
if action not in action_capabilities:
return PolicyDecision(False, f"unknown action {action}")
required_capability = action_capabilities.get(action)
if required_capability:
granted = set(
+4
View File
@@ -65,6 +65,7 @@ class TaskPlaneStore:
self.high_assurance = high_assurance
self.fault_injector = None
self.probe_attestation_verifier = None
self.evidence_attestation_verifier = None
self._lock = threading.RLock()
self._tx_state = threading.local()
self._closed = False
@@ -1408,6 +1409,7 @@ class TaskPlaneStore:
expected_task_id=task_id,
expected_claim_id=claim_id,
expected_previous_hash=previous,
attestation_verifier=self.evidence_attestation_verifier,
):
raise ConflictError("evidence envelope invalid")
self.add_evidence_envelope(envelope)
@@ -1731,6 +1733,7 @@ class TaskPlaneStore:
expected_task_id=envelope.task_id,
expected_claim_id=envelope.claim_id,
expected_previous_hash=expected_previous,
attestation_verifier=self.evidence_attestation_verifier,
):
raise ConflictError("evidence envelope invalid")
self.conn.execute(
@@ -1798,6 +1801,7 @@ class TaskPlaneStore:
expected_task_id=task_id,
expected_claim_id=envelope.claim_id,
expected_previous_hash=previous,
attestation_verifier=self.evidence_attestation_verifier,
)
):
return False
@@ -0,0 +1,30 @@
# C4 capability contract provenance
The C4 checkout has no `aios_capability_contract.py`, although
`active-runtime/app/aios_agent_task_protocol.py` imports it at module scope.
The read-only QA capture used for the missing source closure is:
- source: `C:\\Users\\ImRuY\\.codex-restored\\work\\v2r2c5-release-20260926\\qa\\capability-contract.txt`
- SHA256: `c97c9f8a3cf199a9c0348da1212aa26d161aad8ddaed84db534e46f490f2422f`
- 140 lines
The contract preserves separate task and worker alias maps, maps unknown task
names to `exact:<token>`, ignores unknown worker names, and returns false for
unknown or mismatched eligibility. It is used by formal AgentTask poll and
claim paths only. Generic `task_plane` matching remains outside this lane.
The authoritative runtime overlay is captured separately by QA:
- base SHA256: `953de1d491b4f1bc47c71f502027fd488e6c48ecc49da8a91dc9d8754c320d17`
- overlay SHA256: `7555aa067fc01c6a25276a834dda4924b404f3b0c529ed3f99e9f1d83d18798e`
The consolidated module preserves the base mappings and adds only the seven
explicit lifecycle labels: `agent-task:control`, `governed.task.create`,
`governed.write.files`, `task.principal.issue`, `testing`, `verification`, and
`writer.lease`. It does not depend on an absolute deployed path.
Validation logs:
- `PYTHONPATH=active-runtime/app python -m pytest -q tests/test_v2r2c5_capability_contract.py` -> 6 passed.
- `PYTHONPATH=active-runtime/app;active-runtime python -m pytest -q tests/test_task_admission_bootstrap.py tests/test_task_admission_bootstrap_chain_contract.py` -> 45 passed.
- `tests/test_task_admission_bootstrap_http.py` could not collect on Windows because the existing `gitea_tag_publication.py` imports unavailable `fcntl`; no capability assertion ran in that file.
@@ -0,0 +1,73 @@
# C5 控制面闭合报告
状态:本地工程闭合候选已形成;live control plane 尚未闭合。本文是交付准备记录,不是生产发布、activation、Task admission 或 RC 成功证明。
## 已闭合的工程边界
架构决定和实施计划将 unattended runner 定义为编排客户端。它不拥有 Owner authority、epoch issuer、Task DB、Git writer、approval verifier 或 production executor。现有组件仍是唯一权威:`TaskAdmissionBootstrapService`/`AgentTaskAdmissionPort` 负责 envelope、grant 和 Task admission;既有 lease/principal/writer chain 负责写入;trusted builder 和 `SilentReleaseActionService` 负责 candidate/provider evidence;`ReleaseSagaStore`、`ReleaseOrchestrator` 和 `ReleaseRecoveryWorker` 负责部署、回滚与未知副作用恢复。旧的 `release_state_machine.py` 不应再作为第二状态权威。
本地 runner `active-runtime/app/governed_unattended_release.py` 已提供以下受限组合接口:
```text
GovernedUnattendedReleaseRunner.intake(intent)
GovernedUnattendedReleaseRunner.admit(intent, authorization)
GovernedUnattendedReleaseRunner.publish(intent, admission_receipt)
GovernedUnattendedReleaseRunner.postpublication_rc(event_id, source_commit, version, source_readback)
GovernedUnattendedReleaseRunner.deploy(candidate_id)
```
`SourcePublicationIntent` 对 repository、source/tree/base OID、精确 target branch、generation、execution digest、file allowlist、capability set 和 risk 做不可变规范化,并派生稳定 `task_id` 与 idempotency key。`admit` 在 envelope 已创建后遇到超时、连接断开或 5xx 时先 reconcile,不把未知结果当作未应用,也不重复提交;禁止错误不会重试。`publish` 要求同一 Task 的 `task_lease`、`task_principal`、`writer_lease`、authenticated commit receipt、base/branch/generation/execution digest/source/tree 全部绑定。缺一即阻断。
本地模拟器 `scripts/simulate_c5_unattended_release.py` 从 Git object 读取 source/base/tree/diff/archive,运行候选 C5 contract tests,使用临时 Owner key 和真实 admission/bootstrap/TaskStore 实现完成隔离签名与 admission,再写入现有模型的 candidate/manifest/RC JSON。输出明确标记 `SIMULATION`、`official_release=false`、`authority_source_commit=null`、`acceptance=PENDING_AUTHENTICATED_PRODUCTION_PROVIDER_EVIDENCE`、`approval=NOT_REQUESTED`、`deployment=NOT_ATTEMPTED`。这条链路可证明结构、签名验证、隔离 TaskStore、幂等和非权威 artifact 写出;不能证明 live authority、live publication、trusted production provider、release approval、runtime health 或 deployment。
`governed_release_evidence_producer.py` 的 `produce_evidence_closure(...)` 可生成现有 RC evidence closure,但要求 tests、canary、Gitea、rollback、secret-scan 每一类均有 trusted-provider receipt,并校验 artifact/manifest/package closure/source/tree。相同 closure 幂等,内容变化为冲突。该闭合器不得被调用来伪造 provider pass。
现有 release service 的正式接口仍应按以下顺序复用:
```text
SilentReleaseActionService.create_release_intent(payload)
-> get_release_candidate(candidate_id)
-> get_release_plan(candidate_id)
-> get_acceptance(candidate_id)
-> verify_release_acceptance(candidate_id)
-> get_approval_envelope(candidate_id)
-> submit_owner_approval(signed_payload)
-> start_deployment(candidate_id)
```
`create_release_intent` 只能接收 event/source/version,并从 authenticated source admission resolver 得到版本与 admission ID;不能接收 caller evidence。`submit_owner_approval` 必须经既有 signed release-plan verifier 和 durable approval store。`start_deployment` 会重新加载 durable approval,调用既有 saga/orchestrator;caller 传入的 approval 不作为授权来源。
## 当前 live 未闭合
以下条件仍未证实,不能写成 PASS 或 COMPLETE:
- `authority_source_commit` 仍为 `null`;没有正式 live source publication receipt、authority Git readback、TaskLease/TaskPrincipal/WriterLease/commit closure。
- 没有 live C5 exact source-only policy 的 Owner-signed activation、policy digest、issuer、revocation source 和 fresh epoch。不能用本地 policy/request hash、旧 activation binding、simulation epoch 或借用 credential 替代。
- 没有 live registered machine caller 的可用 admission route;当前 runner 不能自行成为 authority issuer 或把 `engineering` 风险提升为全局允许值。
- 没有真实 trusted builder/provider resolver 的完整绑定,尤其是 authenticated tests、canary、Gitea、rollback、secret-scan receipts;本地 candidate artifact 不是生产 evidence closure。
- 没有 pre-authorized Owner approval broker 为精确 candidate/release plan 签名。source-only authorization 不等于 release approval。
- 没有完成 live `SilentReleaseActionService` 的 candidate、acceptance、release plan、saga readback,也没有 deployment composition、runtime health、rollback 和 post-tag/finalization readback。
- C6 已冻结;C5 自动化支持若进入后续发布,必须重新计算 source/tree/file binding、execution plan 和 signed authority,不能把支持代码自动并入既有 candidate。
## 最终本地验证
2026-09-26,在 Linux/Python 3.12.3 的隔离目录中完成 **184 passed** 定向回归(5.53 秒)。范围包含 epoch v1/v2、source policy、runner、真实 release service/Owner signature 集成、既有 admission/bootstrap/recovery、production contract 和 provider evidence producer。没有重跑历史全量业务测试,也没有把旧的 598 passed 计为本次新增代码的结果。
新增集成覆盖实际 signed builder、signed Owner approval、source substitution 拒绝、无 verifier 拒绝及 durable attestation 篡改拒绝。公开 acceptance 接口包含脱敏字段;`verify_release_acceptance` 在现有 service 内加载原始 durable 数据重新验签,保持公开脱敏规则,向 runner 返回验证摘要。
最终 `demo-run-4` 从不可变候选 `536a2af0f07962495c88526061ce43fb27994c15` 的 archive 执行 C5 capability N01/N02/N03,经过真实 SSHSIG 验签、真实隔离 Task admission、幂等重放和 scope/revocation 负例,成功写出 `V2R2C5_RC.simulation.json`。该 candidate 保持 `BUILDING`;正式 source OID 为空,provider acceptance 待补、approval 未请求、deployment 未执行。
可复核证据位于集成工作树相邻的 `../evidence/final-targeted-tests.xml`、`../evidence/demo-run-4/V2R2C5_RC.simulation.json` 和 `../evidence/demo-run-4/SHA256_MANIFEST.json`。这些结果证明隔离工程闭环,不证明 live 发布闭环。
## 可恢复下一步
1. 在现有 governed Task → TaskLease → TaskPrincipal → WriterLease → Governed Commit 链上发布 runner/epoch compatibility/exact policy guard 支持,并 read back commit/tree/receipt。
2. 由已有 Owner authority 通过控制面激活精确 C5 source-only policy,记录 signed activation、policy digest、issuer、expiry、revocation source;随后请求 fresh epoch。禁止直接写受信任目录或复用旧 epoch。
3. 用真实 registered caller 执行 admission;对 envelope、admission receipt、Task 状态和 authority OID 做 readback。任何 unknown effect 先 reconcile。
4. 仅在 publication receipt、lease chain、commit receipt 和 authority readback 完整后,调用 `create_release_intent`,再由真实 provider 生成并验证 candidate、acceptance、plan、saga 和 RC evidence。
5. 由预授权 Owner approval broker 对精确 release plan 签名,经 `submit_owner_approval` 验证并 durable 存储。缺 broker 时保持阻断,不降低 risk、不 mint client signature。
6. 若另有明确生产授权,再调用 `start_deployment`;依赖既有 pre-switch/canary、health、rollback、tag/finalization reconciliation。否则将结果保留在已签名、可重放的 RC/approval-ready evidence,而不触碰生产。
## 运行时假设
真实组合依赖 Linux/POSIX `fcntl` locks、`fsync`/atomic rename、受限 symlink/open、Git authoritative bare repo、Gitea/ref readback、systemd service identity/restart、health endpoint、Ed25519 verification,以及 KMS/HSM/secret-manager reference。`execution_enabled=false`/shadow mode 是默认安全状态;生产执行缺少 approval verifier、trusted builder、builder attestation verifier、orchestrator、pre-switch validator 或 trusted policy 时必须 fail closed。
@@ -0,0 +1,52 @@
# C5 release automation implementation and rollout
The engineering task implements a repeatable orchestration path and isolated executable proof. It does not treat local artifacts as an activated production capability. V2R2C6 is frozen.
## Parallel implementation ownership
1. Epoch compatibility: admission bootstrap and signed-epoch provider; v1 regression, original-byte v2 verification, tamper/schema/revocation tests.
2. Exact policy lifecycle: existing structured Owner issuer plus an exact source-publication guard; TTL, current trust, revocation, scope and configured-risk checks.
3. Thin runner: generated immutable intake, admission/reconciliation, publication proof gate, existing trusted candidate service and signed approval/deployment entry points.
4. Integration: isolated executable demo, targeted contract tests, architecture/gap reports and immutable local commit/archive evidence.
Each implementation lane uses a separate Git branch/worktree. Integration uses ordinary local commits without force-push, main mutation, TaskDB mutation or production writes.
## Minimum executable closure
Run the simulation CLI with a Git repository, committed source/base and a new output directory. It must derive the file list, tree and source archive from Git objects; run the selected checks; issue a fresh test-only Owner policy/epoch using ephemeral keys; verify them with the actual registry verifier and dual-schema provider; submit an envelope through the actual admission service against an isolated Task transport; and write an existing-model RC artifact with all evidence hashes.
The output must say `SIMULATION`, `official_release=false`, and retain `authority_source_commit=null`. It must not invent live provider receipts or set `READY_FOR_APPROVAL` without the existing acceptance path. Ephemeral private keys are confined to a temporary directory and excluded from exported evidence. A signed test receipt authenticates only its stated isolated environment.
From this implementation checkout on Linux/POSIX, with Git, OpenSSH, pytest and cryptography available:
```bash
python3 scripts/simulate_c5_unattended_release.py \
--repository /path/to/local/candidate/repository \
--source 536a2af0f07962495c88526061ce43fb27994c15 \
--base e5f8209f57b43fc39e55c4555776e6362ce326eb \
--output /path/to/new/isolated/evidence-directory
```
The command creates its own isolated Task store and ephemeral signing domain. It has no live transport or deployment adapter. No manually assembled JSON, existing Owner credential, service restart or production endpoint is needed.
## Required checks
- v1/v2 consumer compatibility, original bytes, issuer/key/namespace/digest, unknown schema, invalid fields, expiry and revocation.
- Existing medium policy still denies engineering; exact source policy denies any changed branch/base/source/tree/files/caps/execution binding.
- Missing verifier and missing/failing revocation lookup deny; admission replay rechecks authorization.
- Stable intake identity, bounded retry, no retry for policy rejection, unknown side-effect reconciliation and conflicting receipt rejection.
- Missing TaskLease/principal/WriterLease/commit/readback cannot produce a live RC.
- Existing signed approval, provider evidence, saga integrity, deployment verification, rollback and post-tag reconciliation contracts.
- C5 capability negatives N01/N02/N03 and affected regression tests only; historical full-suite counts are not reused as evidence for new changes.
## Governed rollout after local verification
1. Publish the support commit through the existing Task -> TaskLease -> TaskPrincipal -> WriterLease -> Governed Commit chain. Read back its OID/tree/receipt.
2. Governed-install the dual-schema consumer and exact source-only policy guard on the existing control-plane service, preserving the old artifact for rollback.
3. Activate the exact policy and dedicated machine caller using the existing Owner authority. Record signed activation, policy digest, issuer and revocation source. This is a provisioning step, not an implicit authority granted by the runner.
4. Request a fresh epoch and execute C5 admission/publication. Rebind evidence to the authority-readback OID; it may differ from the local candidate.
5. Run actual build/test/security/canary/rollback providers and their authenticated resolver. Invoke `create_release_intent` against the authority source; read back candidate, acceptance, plan and saga.
6. Have the pre-authorized Owner broker sign the exact release plan, and submit it through `submit_owner_approval`. Source-only authorization is insufficient here.
7. Call the existing deployment controller with approved durable state. Canary/pre-switch, health, promotion, registry/baseline finalization and rollback follow the existing saga. Verify runtime identity and final consistency.
Missing live authority or provider composition is a durable blocker, never a reason to lower risk, bypass G0, mint signatures in the client or call production restart directly.
@@ -0,0 +1,60 @@
# C5 governed unattended release: architecture decision
Status: selected for isolated implementation; live activation is not implied.
Task: TASK-AIOS-C5-GOVERNED-UNATTENDED-RELEASE-PLATFORM-DESIGN-AND-IMPLEMENTATION-20260926-R1
## Decision
Select **B, additive v1/v2 epoch consumption**. Verify the producer's original canonical signed bytes and retain the actual schema, issuer, key, policy digest and revocation semantics. Project common typed fields only after verification. A v2 payload is never relabelled v1 and its signature is never copied onto another payload.
The producer's `policy_digest` hashes the Owner policy. It is not interchangeable with the default Task Admission policy digest. The existing policy factory propagates this digest; the compatibility change preserves that binding. A v2 `canonical_payload_digest` covers the payload before that field is added, while the signature authenticates the complete original canonical payload.
Option A would either be the same dual-schema verification hidden behind a misleading v1 label, or require a new signature. Option C would change more producers and consumers without resolving the immediate authority/composition gap. B preserves v1 operation and has the smallest rollback surface.
## One control plane
The unattended runner is an orchestration client. It does not become an authority issuer, Task database writer, Git writer, approval verifier, release state machine, or production executor.
| Responsibility | Existing authority/component |
|---|---|
| Owner signatures and narrowing | `LocalOwnerAuthority`, registered Owner key and configured Owner policy |
| Epoch verification | `SignedEpochDirectoryProvider`, `RegistryOwnerApprovalVerifier` |
| Exact source-only policy | Structured Owner-signed policy, verified request guard |
| Envelope, grant and Task admission | `TaskAdmissionBootstrapService`, `AgentTaskAdmissionPort` |
| Claim, principal and WriterLease | Existing Task/lease/governed writer chain |
| Source commit proof | Governed commit receipt plus authority Git readback |
| Build, provider evidence and candidate | Trusted builder, provider resolver, `SilentReleaseActionService` |
| Approval | Existing signed release-plan approval verifier and durable approval store |
| Deployment and recovery | `ReleaseSagaStore`, `ReleaseOrchestrator`, `ReleaseRecoveryWorker` |
The older linear `release_state_machine.py` is not added as another release state authority. The runner consumes durable state from the existing stores. Recovery of an unknown mutation reconciles that state before another write.
## Least privilege and unattended authority
The source-only policy binds repository, source commit/tree, base commit, exact target branch, generation, execution digest, file set, capability set, engineering risk, expiry and fresh identity. It cannot authorize main writes, tags, deployment, baseline mutation or historical Task mutation. The global admission ceiling stays `medium`; an exact verified request may use its separately signed engineering policy. A different request must be denied before envelope creation.
Recognizing the `engineering` rank in the issuer does not raise any configured ceiling. An existing `medium` Owner policy still rejects engineering issuance. The structured policy issuer must narrow its configured Owner policy; it cannot install or activate a policy for itself.
Each authorization rechecks signature, current key trust, expiry, revocation and scope. No automatic extension of expired epochs is allowed. A controller requests a fresh epoch under an already activated Owner policy. Source-only authority cannot be exchanged for release approval. Automatic release approval requires a separately delegated Owner broker to sign the exact candidate and release plan; missing or revoked delegation stops the workflow.
“No interaction” is an execution property after governed provisioning. It is not permission to create a trust root or grant the controller new powers. Current production has not been provisioned for this new composition.
## Retry and recovery
Task and event identities derive from immutable input. Retries retain them. Policy rejection is terminal until policy/input changes. A timeout is not proof that a write failed: use the admission receipt/official Task readback, publication receipt and authority OID, or existing release saga/operation journal to reconcile. Unknown or conflicting effects remain blocked. Transport calls require bounded timeouts; a controller deadline alone cannot cancel a remote mutation.
Build and acceptance evidence are obtained through trusted providers, not caller-supplied pass flags. A local simulation artifact is explicitly marked non-authoritative. Its source OID, archive hash and cryptographic checks can be real without proving live publication, runtime acceptance or deployment.
## Risks and rollout
1. Schema acceptance alone does not activate a usable policy. Roll out the consumer and exact policy guard together through governed control-plane publication.
2. The existing deployed Owner policy does not authorize the C5 scope, and the current caller has no usable authenticated activation/epoch route. Provision the narrowly scoped service identity and policy through the existing governance plane. Do not use direct root file writes as a substitute.
3. Signed source policy and epoch must agree on the Owner policy digest. Mixing epochs, digests or request identities fails closed.
4. The local source candidate `536a2af0f07962495c88526061ce43fb27994c15` is preserved. Automation support changes are separate commits. Any decision to include them in the release must regenerate the governed execution plan and exact file/source bindings.
5. The existing deployment engine contains canary/pre-switch, runtime verification, rollback and post-tag reconciliation logic. Tests establish adapter contracts; they do not prove current production composition or health.
## Rollback
Disable the new runner, revoke its source-only policy/epoch and stop new admissions. Preserve journals, receipts, immutable Tasks and candidate records. Revert the consumer/support module deployment to the prior governed control-plane artifact; v1 remains supported throughout migration. Never rewrite already issued signed payloads or historical Task state.
Production deployment rollback remains the existing signed plan's rollback operation and release saga. A post-tag/finalization uncertainty requires reconciliation; it must not be hidden by an automatic tag rewrite. C4 stays the production baseline until a separately approved C5 deployment completes runtime verification.
+75
View File
@@ -0,0 +1,75 @@
"""Build an additive overlay from the captured live Bridge; never install it."""
import argparse
import hashlib
from pathlib import Path
BASE_SHA256 = "38fc3e54e920ac3f6c992a651b7ef1ec0d2bb994848c0cb9e113bea3383403ca"
START = " if task_admission_handlers is None:\n"
END = " from recovery_bridge import install_recovery\n"
REPLACEMENT = ''' if task_admission_handlers is None:
source_activation = os.environ.get("AIOS_SOURCE_PUBLICATION_ACTIVATION_FILE", "").strip()
try:
if os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip():
from aios_task_admission_live_adapters import build_task_admission_handlers_from_environment
task_admission_handlers = build_task_admission_handlers_from_environment(
post_admission=runtime.project_admitted_task)
elif activation_raw and Path(activation_raw).is_file():
from aios_task_admission_live_adapters import build_task_admission_handlers_from_activation_file
task_admission_handlers = build_task_admission_handlers_from_activation_file(
Path(activation_raw), post_admission=runtime.project_admitted_task)
except TaskAdmissionError as exc:
if not source_activation or str(exc) != "TASK_ADMISSION_EPOCH_EXPIRED":
raise
from source_publication_runtime import expired_admission_runtime
task_admission_handlers = expired_admission_runtime().handlers
if source_activation:
from source_publication_runtime import (
AdmissionRuntimeRouter, ExistingPrincipalPolicyFence,
build_source_runtime_from_activation_file,
)
from aios_task_admission_live_adapters import TaskAdmissionRuntimeBundle
if task_admission_handlers is None:
raise RuntimeError("SOURCE_ACTIVATION_ORDINARY_RUNTIME_REQUIRED")
source_runtime = build_source_runtime_from_activation_file(
Path(source_activation), post_admission=runtime.project_admitted_task,
ordinary_activation_file=Path(activation_raw))
ordinary = TaskAdmissionRuntimeBundle(task_admission_handlers, verifier)
task_admission_handlers = AdmissionRuntimeRouter(ordinary, source_runtime).handlers()
BridgeHandler.principal_verifier = ExistingPrincipalPolicyFence(verifier, source_runtime)
'''
def build(base: bytes) -> bytes:
if hashlib.sha256(base).hexdigest() != BASE_SHA256:
raise ValueError("LIVE_BRIDGE_BASE_HASH_MISMATCH")
source = base.decode("utf-8")
if source.count(START) != 1 or source.count(END) != 1:
raise ValueError("LIVE_BRIDGE_ANCHOR_MISMATCH")
start, end = source.index(START), source.index(END)
result = source[:start] + REPLACEMENT + source[end:]
runtime_line = ' if parsed.path == "/v2/runtime": self._json(self.adapter.getRuntimeStatus()); return\n'
if result.count(runtime_line) != 1:
raise ValueError("LIVE_RUNTIME_ROUTE_ANCHOR_MISMATCH")
result = result.replace(runtime_line, ''' if parsed.path == "/v2/runtime":
import hashlib as _source_hashlib
source_path = Path(__file__).resolve(strict=True)
self._json({**self.adapter.getRuntimeStatus(),
"source_module_path": str(source_path),
"source_module_sha256": _source_hashlib.sha256(source_path.read_bytes()).hexdigest()})
return
''', 1)
compile(result, "aios_chatgpt_bridge_v2_server.py", "exec")
return result.encode("utf-8")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
output = build(args.base.read_bytes())
args.output.parent.mkdir(parents=True, exist_ok=True)
with args.output.open("xb") as handle:
handle.write(output)
print(hashlib.sha256(output).hexdigest())
+268
View File
@@ -0,0 +1,268 @@
from __future__ import annotations
import argparse
import ast
import difflib
import hashlib
import json
from pathlib import Path
FILES = ("aios_task_admission_bootstrap.py", "aios_task_admission_live_adapters.py")
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def replace_once(text: str, old: str, new: str, label: str) -> str:
count = text.count(old)
if count != 1:
raise RuntimeError(f"{label}: expected one match, got {count}")
return text.replace(old, new, 1)
def patch_bootstrap(text: str) -> str:
text = replace_once(
text,
'SCHEMA_EPOCH = "aios.owner-delegation-epoch.v1"\n',
'SCHEMA_EPOCH = "aios.owner-delegation-epoch.v1"\n'
'SCHEMA_EPOCH_V2 = "aios.owner-delegation-epoch.v2"\n'
'SUPPORTED_EPOCH_SCHEMAS = frozenset({SCHEMA_EPOCH, SCHEMA_EPOCH_V2})\n',
"bootstrap schema constants",
)
text = replace_once(
text,
' policy_digest: str\n schema: str = SCHEMA_EPOCH\n\n def as_public_dict',
' policy_digest: str\n'
' schema: str = SCHEMA_EPOCH\n'
' production_scope: str | None = None\n'
' destructive_policy: str | None = None\n'
' status: str = ""\n'
' signature_namespace: str = ""\n'
' signature_key_id: str = ""\n'
' canonical_payload_digest: str = ""\n'
' canonical_payload: bytes = b""\n\n'
' def as_public_dict',
"epoch provenance fields",
)
text = replace_once(
text,
' value = asdict(self)\n value["allowed_capabilities"]',
' value = asdict(self)\n'
' value.pop("canonical_payload", None)\n'
' value["allowed_capabilities"]',
"epoch public serialization",
)
text = replace_once(
text,
' if epoch.schema != SCHEMA_EPOCH:\n',
' if epoch.schema not in SUPPORTED_EPOCH_SCHEMAS:\n',
"bootstrap epoch schema check",
)
text = replace_once(
text,
' ranks = {"low": 0, "medium": 1, "engineering": 2, "high": 3}\n',
' ranks = {"low": 0, "medium": 1, "engineering": 2, "high": 3}\n',
"risk compatibility assertion",
)
return text
def patch_adapters(text: str) -> str:
text = replace_once(
text,
' OwnerDelegationEpoch,\n TaskAdmissionError,',
' OwnerDelegationEpoch,\n SCHEMA_EPOCH,\n SCHEMA_EPOCH_V2,\n TaskAdmissionError,',
"adapter schema imports",
)
marker = 'OWNER_SIGNER_IDENTITY = "aios-task-grant-signer"\n'
fields = marker + (
'_EPOCH_V1_FIELDS = frozenset({\n'
' "epoch_id", "owner_identity", "trust_domain", "repository",\n'
' "allowed_branch_prefixes", "allowed_capabilities", "allowed_file_prefixes",\n'
' "risk_ceiling", "issued_at", "not_before", "expires_at",\n'
' "revocation_epoch", "policy_digest", "schema",\n'
'})\n'
'_EPOCH_V2_FIELDS = frozenset({\n'
' *_EPOCH_V1_FIELDS, "business_goal", "production_scope",\n'
' "destructive_policy", "signature_namespace", "signature_key_id",\n'
' "canonical_payload_digest", "status",\n'
'})\n'
'_SSH_FINGERPRINT_RE = re.compile(r"^SHA256:[A-Za-z0-9+/]{43}$")\n'
)
text = replace_once(text, marker, fields, "adapter epoch field sets")
text = replace_once(
text,
' if value.get("schema") != "aios.owner-delegation-epoch.v1":\n'
' raise TaskAdmissionError(\n'
' "TASK_ADMISSION_EPOCH_SCHEMA_INVALID",\n'
' 403,\n'
' )\n',
' schema = value.get("schema")\n'
' if schema not in {SCHEMA_EPOCH, SCHEMA_EPOCH_V2}:\n'
' raise TaskAdmissionError("TASK_ADMISSION_EPOCH_SCHEMA_INVALID", 403)\n'
' expected_fields = _EPOCH_V1_FIELDS if schema == SCHEMA_EPOCH else _EPOCH_V2_FIELDS\n'
' if set(value) != expected_fields:\n'
' raise TaskAdmissionError("TASK_ADMISSION_EPOCH_FIELDS_INVALID", 403)\n',
"adapter schema and fields",
)
anchor = ' if epoch_id in self._revoked_ids():\n raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REVOKED", 403)\n\n'
block = anchor + (
' if schema == SCHEMA_EPOCH_V2:\n'
' if (\n'
' value.get("status") != "ACTIVE"\n'
' or not isinstance(value.get("business_goal"), str)\n'
' or not value["business_goal"]\n'
' or not isinstance(value.get("production_scope"), str)\n'
' or not value["production_scope"]\n'
' or not isinstance(value.get("destructive_policy"), str)\n'
' or value["destructive_policy"] not in {"forbidden", "rollback-only"}\n'
' or value.get("signature_namespace") != OWNER_NAMESPACE\n'
' or not isinstance(value.get("signature_key_id"), str)\n'
' or not _SSH_FINGERPRINT_RE.fullmatch(value["signature_key_id"])\n'
' or not isinstance(value.get("canonical_payload_digest"), str)\n'
' or not re.fullmatch(r"[0-9a-f]{64}", value["canonical_payload_digest"])\n'
' ):\n'
' raise TaskAdmissionError("TASK_ADMISSION_EPOCH_V2_BINDING_INVALID", 403)\n'
' digest_value = dict(value)\n'
' supplied_digest = digest_value.pop("canonical_payload_digest", None)\n'
' if sha256_json(digest_value) != supplied_digest:\n'
' raise TaskAdmissionError("TASK_ADMISSION_EPOCH_PAYLOAD_DIGEST_INVALID", 403)\n\n'
)
text = replace_once(text, anchor, block, "v2 payload binding")
text = replace_once(
text,
' key_id = key_id_file.read_text(encoding="utf-8").strip()\n',
' key_id = key_id_file.read_text(encoding="utf-8").strip()\n'
' if schema == SCHEMA_EPOCH_V2 and key_id != value["signature_key_id"]:\n'
' raise TaskAdmissionError("TASK_ADMISSION_EPOCH_KEY_BINDING_INVALID", 403)\n',
"v2 key binding",
)
text = replace_once(
text,
' schema=str(value["schema"]),\n )\n',
' schema=str(value["schema"]),\n'
' production_scope=str(value["production_scope"]) if schema == SCHEMA_EPOCH_V2 else None,\n'
' destructive_policy=str(value["destructive_policy"]) if schema == SCHEMA_EPOCH_V2 else None,\n'
' status=str(value.get("status", "")),\n'
' signature_namespace=str(value.get("signature_namespace", "")),\n'
' signature_key_id=str(value.get("signature_key_id", "")),\n'
' canonical_payload_digest=str(value.get("canonical_payload_digest", "")),\n'
' canonical_payload=payload,\n'
' )\n',
"epoch provenance construction",
)
return text
def patch_runtime_bundle(text: str) -> str:
marker = 'def _build_task_admission_handlers(\n'
text = replace_once(text, marker, '@dataclass(frozen=True, slots=True)\n'
'class TaskAdmissionRuntimeBundle:\n'
' handlers: Mapping[str, Any]\n'
' principal_verifier: Any = None\n'
' service: Any = None\n'
' authorization: Any = None\n'
' store: Any = None\n'
' task_port: Any = None\n'
' epoch_id: str = ""\n\n\n'
'def _build_task_admission_runtime_bundle(\n', "runtime bundle declaration")
text = replace_once(
text,
') -> Mapping[str, Any]:\n """Compose handlers from already-trusted server-side coordinates."""',
') -> TaskAdmissionRuntimeBundle:\n """Compose handlers from already-trusted server-side coordinates."""',
"runtime bundle return type",
)
text = replace_once(
text,
' store=TaskAdmissionStore(state_db),\n',
' store=(store := TaskAdmissionStore(state_db)),\n',
"runtime bundle store capture",
)
text = replace_once(
text,
' return TaskAdmissionBridgeContract(service).handlers()\n\n\ndef build_task_admission_handlers_from_activation_file(',
' return TaskAdmissionRuntimeBundle(\n'
' handlers=TaskAdmissionBridgeContract(service).handlers(),\n'
' service=service, authorization=authorization, store=store,\n'
' task_port=task_port, epoch_id=epoch_id,\n'
' )\n\n\n'
'def _build_task_admission_handlers(**kwargs: Any) -> Mapping[str, Any]:\n'
' return _build_task_admission_runtime_bundle(**kwargs).handlers\n\n\n'
'def build_task_admission_handlers_from_activation_file(',
"runtime bundle legacy wrapper",
)
# Clone the original strict activation parser, retaining all path/token/API
# checks. The new bundle entry point still passes through the live V3
# producer hook. It never replaces the registered signed principal verifier.
tree = ast.parse(text)
node = next(n for n in tree.body if isinstance(n, ast.FunctionDef)
and n.name == "build_task_admission_handlers_from_activation_file")
body = ast.get_source_segment(text, node)
body = body.replace("def build_task_admission_handlers_from_activation_file(",
"def _c5_build_runtime_from_activation_file(", 1)
body = body.replace("return _build_task_admission_handlers(",
"return _build_task_admission_runtime_bundle(", 1)
text += "\n\n" + body + '''
def build_task_admission_runtime_from_activation_file(activation_file, *, post_admission=None,
caller_service="aios-chatgpt-bridge-v2-staging.service"):
# Dedicated source-only composition. Ordinary handler construction continues
# to use the original medium policy and does not enter this function.
from dataclasses import replace
bundle = _fl_call(_c5_build_runtime_from_activation_file, activation_file,
caller_service=caller_service, post_admission=post_admission)
epoch = bundle.authorization.verify_delegation_epoch(bundle.epoch_id)
if (epoch.schema != SCHEMA_EPOCH_V2 or epoch.production_scope != "none"
or epoch.destructive_policy != "forbidden"):
raise TaskAdmissionError("SOURCE_ACTIVATION_EPOCH_SCOPE_DENIED", 403)
bundle.service.policy = replace(bundle.service.policy,
repository=epoch.repository, trust_domain=epoch.trust_domain,
allowed_capabilities=frozenset(epoch.allowed_capabilities),
max_risk=epoch.risk_ceiling, policy_digest=epoch.policy_digest)
return bundle
'''
return text
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--release-root", type=Path, required=True)
args = parser.parse_args()
root = args.release_root.resolve()
snapshot = root / "live-release" / "staging-source-snapshot"
identity_path = root / "live-release" / "STAGING_MODULE_IDENTITIES.json"
overlay = root / "live-release" / "staging-overlay" / "app"
identity = json.loads(identity_path.read_text(encoding="utf-8"))
outputs: dict[str, dict[str, str | int]] = {}
diffs: list[str] = []
for name in FILES:
source = snapshot / name
expected = identity["modules"][name.removesuffix(".py")]["sha256"]
if sha256(source) != expected:
raise SystemExit(f"BASE_HASH_MISMATCH:{name}")
before = source.read_text(encoding="utf-8")
after = patch_bootstrap(before) if name.endswith("bootstrap.py") else patch_adapters(before)
after = patch_runtime_bundle(after) if name.endswith("live_adapters.py") else after
target = overlay / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(after, encoding="utf-8", newline="")
diffs.extend(difflib.unified_diff(
before.splitlines(keepends=True), after.splitlines(keepends=True),
fromfile=f"staging-source-snapshot/{name}", tofile=f"staging-overlay/app/{name}",
))
outputs[name] = {
"base_sha256": expected, "base_bytes": len(before.encode()),
"result_sha256": sha256(target), "result_bytes": len(after.encode()),
}
diff_path = root / "live-release" / "staging-overlay" / "c5-epoch-v2-overlay.diff"
diff_path.write_text("".join(diffs), encoding="utf-8", newline="")
result_path = root / "live-release" / "staging-overlay" / "c5-epoch-v2-overlay.result.json"
result_path.write_text(json.dumps({"files": outputs, "diff_sha256": sha256(diff_path)}, indent=2) + "\n", encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+357
View File
@@ -0,0 +1,357 @@
#!/usr/bin/env python3
"""Build the hash-pinned additive C5 TaskPrincipal enrollment overlay."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import shutil
import stat
from typing import Any
PINNED = {
# The first admission module on both live unit PYTHONPATH values. It
# preserves the exact9/R10/generic chain and the governed TTL clamp.
"aios_task_principal_admission.py": "d56fb0078cfcb907f2afb35f0163051a962a89520b63f6c53149d478fc7777c8",
# Effective staging entrypoint. This preserves the 4 MiB body-limit
# hardening wrapper, which in turn pins and loads the original a95b core.
"aios_task_principal_issuer_server.py": "53b30a4a094f4abda176dbbf931ed2d9baec0a6ef3590c9ae498e2c22af1057c",
"aios_task_principal_governed_adapter.py": "9bdfa7bd20bd1f4d5c2f5a58f458d0999f593a3f9326920971e357ba52939914",
"governed_runtime_scope.py": "c56bd709c1e0c8956a5be7a4a8dc2a12591bdec2b84dcfba5c53f6bced746bcb",
}
EFFECTIVE_ADMISSION_SOURCE = (
"/var/lib/aios-gitops/bridge-v2-staging/releases/"
"p0-task-principal-governed-runtime-20260910-02/app/"
"aios_task_principal_admission.py"
)
ADMISSION_OVERLAY = '''"""C5-only overlay over the hash-pinned effective admission chain."""
from __future__ import annotations
import hashlib
import importlib.util
from pathlib import Path
from typing import Any
BASE_SOURCE = Path(
"/var/lib/aios-gitops/bridge-v2-staging/releases/"
"p0-task-principal-governed-runtime-20260910-02/app/"
"aios_task_principal_admission.py"
)
BASE_SOURCE_SHA256 = "d56fb0078cfcb907f2afb35f0163051a962a89520b63f6c53149d478fc7777c8"
C5_TASK_ID = "TASK-V2R2C5-SOURCE-PUBLICATION-20260926-R1"
def _load_base():
metadata = BASE_SOURCE.lstat()
if not BASE_SOURCE.is_file() or BASE_SOURCE.is_symlink() or metadata.st_mode & 0o022:
raise RuntimeError("BASE_ADMISSION_SOURCE_INVALID")
digest = hashlib.sha256(BASE_SOURCE.read_bytes()).hexdigest()
if digest != BASE_SOURCE_SHA256:
raise RuntimeError("BASE_ADMISSION_SOURCE_DRIFT")
spec = importlib.util.spec_from_file_location(
"_aios_task_principal_admission_governed_base", BASE_SOURCE,
)
if spec is None or spec.loader is None:
raise RuntimeError("BASE_ADMISSION_SOURCE_UNLOADABLE")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
_effective_base = _load_base()
for _name, _value in _effective_base.__dict__.items():
if not _name.startswith("__"):
globals()[_name] = _value
_legacy = _effective_base._legacy
_ORIGINAL_SCOPE_AND_TRUST = _legacy._scope_and_trust
def _c5_scope_and_trust(
con, trust, task, now, *, lease_token=None, pepper=None,
):
# Every non-C5 task follows the effective governed/exact9/R10 chain
# unchanged. The exact C5 identity has one exclusive enrollment route.
if str(task["task_id"]) != C5_TASK_ID:
return _ORIGINAL_SCOPE_AND_TRUST(
con, trust, task, now, lease_token=lease_token, pepper=pepper,
)
active_digest, active_scope = _legacy._active_scope_chain(con)
digest = _legacy.sha256_canonical(trust)
if active_digest != digest:
_legacy._fail("TRUST_NOT_ACTIVE")
event = con.execute(
"SELECT * FROM task_principal_trust_events WHERE event_type='REGISTER' "
"AND issuer_id=? AND key_id=? AND public_key=? AND trust_manifest_sha256=?",
(trust["issuer_id"], trust["key_id"], trust["public_key"], digest),
).fetchone()
if event is None or event["scope_id"] != active_scope["scope_id"]:
_legacy._fail("TRUST_REGISTER_MISSING_OR_CORRUPT")
if (
active_scope["issuer_id"] != trust["issuer_id"]
or active_scope["key_id"] != trust["key_id"]
):
_legacy._fail("SCOPE_MISSING_OR_CORRUPT")
_legacy._check_revocations(con, task, active_scope, trust, None)
from c5_governed_principal_scope import (
C5PrincipalScopeError,
resolve_enrolled_scope_from_environment,
)
try:
scope = resolve_enrolled_scope_from_environment(
task=task,
execution=_legacy._execution(task),
trust=trust,
now=now,
active_static_task_id=str(active_scope["task_id"]),
)
except C5PrincipalScopeError as exc:
_legacy._fail(exc.code)
if scope is None:
_legacy._fail("C5_SCOPE_ROUTING_INVALID")
claim_digest = _legacy._claim(task, lease_token, pepper, now)
_legacy._check_revocations(con, task, scope, trust, None)
return scope, claim_digest
_legacy._scope_and_trust = _c5_scope_and_trust
globals()["_scope_and_trust"] = _c5_scope_and_trust
def build_task_principal_issuer(*args: Any, **kwargs: Any) -> Any:
from c5_governed_principal_scope import wrap_principal_runtime_from_environment
return wrap_principal_runtime_from_environment(
TaskPrincipalIssuer(*args, **kwargs), role="issuer",
)
def build_task_principal_verifier(*args: Any, **kwargs: Any) -> Any:
from c5_governed_principal_scope import wrap_principal_runtime_from_environment
return wrap_principal_runtime_from_environment(
TaskPrincipalVerifier(*args, **kwargs), role="verifier",
)
C5_GOVERNED_PRINCIPAL_SCOPE_OVERLAY = {
"schema": "aios.c5-governed-principal-scope-overlay.v1",
"base_admission": str(BASE_SOURCE),
"base_admission_sha256": BASE_SOURCE_SHA256,
"task_id": C5_TASK_ID,
}
'''
ISSUER_MAIN_ANCHOR = '''
if __name__ == "__main__":
main()
'''
ISSUER_MAIN_WITH_BRIDGE_ABI = '''
# The effective Bridge imports this existing private helper. Delegate it to
# the body-limit wrapper's already hash-pinned base module so its validation
# semantics and the wrapper's 4 MiB MAX_BODY setting remain authoritative.
_read_public_manifest = _BASE._read_public_manifest
if __name__ == "__main__":
main()
'''
ENTRYPOINT = '''#!/usr/bin/env python3
import sys
sys.dont_write_bytecode = True
from c5_governed_principal_scope import main
if __name__ == "__main__":
raise SystemExit(main())
'''
ENV_EXAMPLE = '''# Required identically for the issuer and Bridge units. Prefix overlay/app
# on both PYTHONPATH values; their existing python -m entry points are unchanged.
AIOS_C5_PRINCIPAL_SCOPE_CONFIG=/var/lib/aios-gitops/bridge-v2-staging/releases/REPLACE/principal-activation.json
'''
ACTIVATION_EXAMPLE = {
"schema": "aios.c5-task-principal-activation.v1",
"task_id": "TASK-V2R2C5-SOURCE-PUBLICATION-20260926-R1",
"agent_id": "agent/codex-c5-release",
"execution_digest": "1ba534d551991e8c99014285eeea36afcbc1500686035cb8bcd7f9f4efd6215e",
"store_path": "/var/lib/aios-gitops/c5-source-principal/enrollment.sqlite3",
"grant_db": "/var/lib/aios-gitops/bridge-v2-staging/task-admission.sqlite3",
"plan_dir": "/var/lib/aios-gitops/bridge-v2-staging/releases/REPLACE/plans",
"producer_state_root": "/var/lib/aios-gitops/bridge-v2-staging",
"policy_artifact": "/var/lib/aios-gitops/bridge-v2-staging/releases/REPLACE/policy.json",
"policy_revocations": "/var/lib/aios-gitops/bridge-v2-staging/releases/REPLACE/policy-revocations.json",
"epoch_root": "/var/lib/aios-gitops/bridge-v2-staging/releases/REPLACE/epoch",
"epoch_revocations": "/var/lib/aios-gitops/bridge-v2-staging/releases/REPLACE/epoch-revocations.json",
"owner_registry": "/etc/aios/single-host-signer-registry.json",
"owner_signer_id": "local-owner-authority",
"registered_signer_command": "/usr/local/libexec/aios-grant-signer-adapter",
"require_root_plan_owner": True,
"grant_db_allowed_group_gid": None,
}
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def copy_pinned(source: Path, target: Path, name: str) -> dict[str, Any]:
observed = sha256(source)
if observed != PINNED[name]:
raise SystemExit(f"BASE_HASH_MISMATCH:{name}:{observed}")
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, target)
return {
"source": str(source.resolve()),
"base_sha256": observed,
"result_sha256": sha256(target),
"bytes": target.stat().st_size,
}
def build_issuer_overlay(source: Path, target: Path) -> dict[str, Any]:
name = "aios_task_principal_issuer_server.py"
observed = sha256(source)
if observed != PINNED[name]:
raise SystemExit(f"BASE_HASH_MISMATCH:{name}:{observed}")
text = source.read_text(encoding="utf-8")
if text.count(ISSUER_MAIN_ANCHOR) != 1:
raise SystemExit(
"PATCH_ANCHOR_INVALID:issuer_bridge_private_abi:"
+ str(text.count(ISSUER_MAIN_ANCHOR))
)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(
text.replace(ISSUER_MAIN_ANCHOR, ISSUER_MAIN_WITH_BRIDGE_ABI, 1),
encoding="utf-8", newline="",
)
return {
"source": str(source.resolve()),
"base_sha256": observed,
"result_sha256": sha256(target),
"bytes": target.stat().st_size,
"preserved_max_body_bytes": 4 * 1024 * 1024,
"bridge_private_helper": "_read_public_manifest",
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--principal-source-root", type=Path, required=True)
parser.add_argument(
"--issuer-source", type=Path,
help="Exact issuer server source; defaults to principal-source-root/aios_task_principal_issuer_server.py",
)
parser.add_argument("--adapter-source-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
principal = args.principal_source_root.resolve()
adapter = args.adapter_source_root.resolve()
output = args.output_root.resolve()
if output.exists() and any(output.iterdir()):
raise SystemExit("OUTPUT_ROOT_NOT_EMPTY")
app = output / "app"
app.mkdir(parents=True, exist_ok=True)
admission_source = principal / "aios_task_principal_admission.py"
observed = sha256(admission_source)
if observed != PINNED["aios_task_principal_admission.py"]:
raise SystemExit(
"BASE_HASH_MISMATCH:aios_task_principal_admission.py:" + observed
)
admission_target = app / "aios_task_principal_admission.py"
admission_target.write_text(ADMISSION_OVERLAY, encoding="utf-8", newline="")
outputs: dict[str, Any] = {
"aios_task_principal_admission.py": {
"source": str(admission_source),
"runtime_base_source": EFFECTIVE_ADMISSION_SOURCE,
"base_sha256": observed,
"result_sha256": sha256(admission_target),
"bytes": admission_target.stat().st_size,
}
}
issuer_source = (
args.issuer_source.resolve()
if args.issuer_source is not None
else principal / "aios_task_principal_issuer_server.py"
)
outputs["aios_task_principal_issuer_server.py"] = build_issuer_overlay(
issuer_source,
app / "aios_task_principal_issuer_server.py",
)
for name in (
"aios_task_principal_governed_adapter.py", "governed_runtime_scope.py",
):
outputs[name] = copy_pinned(adapter / name, app / name, name)
module_source = Path(__file__).resolve().parents[1] / "active-runtime" / "app" / "c5_governed_principal_scope.py"
module_target = app / module_source.name
shutil.copyfile(module_source, module_target)
outputs[module_source.name] = {
"source": str(module_source),
"result_sha256": sha256(module_target),
"bytes": module_target.stat().st_size,
}
bin_dir = output / "bin"
bin_dir.mkdir(parents=True, exist_ok=True)
entry = bin_dir / "aios-c5-principal-scope"
entry.write_text(ENTRYPOINT, encoding="utf-8", newline="")
entry.chmod(entry.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP)
config_dir = output / "config"
config_dir.mkdir(parents=True, exist_ok=True)
env_path = config_dir / "c5-principal-scope.env.example"
env_path.write_text(ENV_EXAMPLE, encoding="utf-8", newline="")
activation_path = config_dir / "principal-activation.example.json"
activation_path.write_text(
json.dumps(ACTIVATION_EXAMPLE, sort_keys=True, separators=(",", ":")),
encoding="utf-8", newline="",
)
manifest = {
"schema": "aios.c5-task-principal-overlay.v1",
"task_id": "TASK-V2R2C5-SOURCE-PUBLICATION-20260926-R1",
"execution_digest": "1ba534d551991e8c99014285eeea36afcbc1500686035cb8bcd7f9f4efd6215e",
"files": outputs,
"entrypoints": {
"enrollment": "bin/aios-c5-principal-scope",
"issuer": "python3 -B -m aios_task_principal_issuer_server",
"bridge": "python3 -B -m aios_chatgpt_bridge_v2_server",
},
"runtime_imports": [
"aios_single_host_authority.RegistryOwnerApprovalVerifier",
"aios_single_host_authority.SignerRegistry",
"registered_owner_signer_adapter.RegisteredOwnerSignerAdapter",
],
"activation": {
"pythonpath_requirement": "prefix overlay/app for both issuer and Bridge",
"same_admission_overlay_for_both_units": True,
"environment_example": "config/c5-principal-scope.env.example",
"activation_example": "config/principal-activation.example.json",
"live_mutation_performed": False,
},
}
manifest_path = output / "overlay-manifest.json"
manifest_path.write_text(
json.dumps(manifest, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8", newline="",
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+669
View File
@@ -0,0 +1,669 @@
"""Build the unsigned, fixed-scope C5 staging installation artifact.
This module is deliberately a packager only. It does not import the live
installer, invoke a signer, call systemd, or mutate a remote host. Code that
belongs to the support commit is read with ``git show`` so a dirty checkout
cannot silently change the artifact.
"""
from __future__ import annotations
import argparse
import ast
from dataclasses import dataclass
import hashlib
import io
import json
import os
from pathlib import Path, PurePosixPath
import re
import secrets
import subprocess
import tarfile
import time
from typing import Any, Iterable, Mapping
REPOSITORY = "aios/aios"
RELEASE_BASE = "/var/lib/aios-gitops/bridge-v2-staging/releases"
UNIT = "aios-chatgpt-bridge-v2-staging.service"
BRIDGE_TARGET = "bridge-18892"
ISSUER_TARGET = "principal-issuer-18946"
ISSUER_UNIT = "aios-task-principal-issuer.service"
DROPIN_NAME = "~" * 32 + "-c5-source.conf"
DROPIN = f"/etc/systemd/system/{UNIT}.d/{DROPIN_NAME}"
ISSUER_DROPIN = f"/etc/systemd/system/{ISSUER_UNIT}.d/{DROPIN_NAME}"
RUNTIME_URL = "http://127.0.0.1:18892/v2/runtime"
ISSUER_RUNTIME_URL = "http://127.0.0.1:18946/health"
PORT = 18892
ISSUER_PORT = 18946
REQUEST_SCHEMA = "aios.c5-staging-installation-request.v2"
OWNER_NAMESPACE = "aios-owner-approval-v1"
MAX_TTL = 900
DEFAULT_TIMEOUT = 3
SOURCE_MODULES = {
"active-runtime/app/source_publication_runtime.py":
"app/source_publication_runtime.py",
"active-runtime/app/governed_source_publication_policy.py":
"app/governed_source_publication_policy.py",
"active-runtime/app/registered_owner_signer_adapter.py":
"app/registered_owner_signer_adapter.py",
"active-runtime/app/aios_single_host_authority.py":
"app/c5_source_authority.py",
}
GENERATED_OVERLAYS = (
"aios_chatgpt_bridge_v2_server.py",
"aios_task_admission_bootstrap.py",
"aios_task_admission_live_adapters.py",
)
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_NONCE = re.compile(r"^[A-Za-z0-9._:-]{16,128}$")
_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/-]{2,255}$")
_SOURCE_MODES = {0o444, 0o640, 0o644, 0o755}
class BuildError(ValueError):
"""Stable fail-closed error raised before any output is written."""
def _error(code: str) -> "NoReturn":
raise BuildError(code)
def _sha(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _canonical(value: Any) -> bytes:
return json.dumps(
value, sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False,
).encode("utf-8")
def _strict_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
_error("JSON_DUPLICATE_FIELD")
result[key] = value
return result
def _json(path: Path) -> tuple[dict[str, Any], bytes]:
try:
raw = path.read_bytes()
value = json.loads(raw.decode("utf-8"), object_pairs_hook=_strict_pairs)
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
raise BuildError(f"JSON_INPUT_INVALID:{path.name}") from exc
if not isinstance(value, dict):
_error("JSON_OBJECT_REQUIRED")
return value, raw
def _regular(path: Path, *, label: str) -> bytes:
try:
info = path.lstat()
except OSError as exc:
raise BuildError(f"INPUT_MISSING:{label}") from exc
if not info or not path.is_file() or path.is_symlink():
_error(f"INPUT_NOT_REGULAR:{label}")
try:
return path.read_bytes()
except OSError as exc:
raise BuildError(f"INPUT_READ_FAILED:{label}") from exc
def _safe_rel(value: str) -> PurePosixPath:
if not isinstance(value, str) or not value or "\\" in value:
_error("RELATIVE_PATH_INVALID")
path = PurePosixPath(value)
if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
_error("RELATIVE_PATH_INVALID")
return path
def _support_oid(repo: Path, support_commit: str) -> str:
if not _HEX40.fullmatch(support_commit):
_error("SUPPORT_COMMIT_INVALID")
try:
result = subprocess.run(
["git", "-C", str(repo), "rev-parse", "--verify", f"{support_commit}^{{commit}}"],
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=30,
)
except (OSError, subprocess.SubprocessError) as exc:
raise BuildError("SUPPORT_COMMIT_NOT_FOUND") from exc
if result.stdout.strip() != support_commit:
_error("SUPPORT_COMMIT_NOT_EXACT")
return support_commit
def _git_blob(repo: Path, support_commit: str, path: str) -> bytes:
_safe_rel(path)
try:
result = subprocess.run(
["git", "-C", str(repo), "show", f"{support_commit}:{path}"],
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30,
)
except (OSError, subprocess.SubprocessError) as exc:
raise BuildError(f"SUPPORT_BLOB_MISSING:{path}") from exc
return bytes(result.stdout)
def _mode(path: Path) -> int:
mode = path.stat().st_mode
return 0o755 if mode & 0o111 else 0o644
@dataclass(frozen=True)
class ArtifactFile:
path: str
data: bytes
mode: int
kind: str = "release_file"
@property
def record(self) -> dict[str, Any]:
return {
"path": self.path,
"sha256": _sha(self.data),
"size": len(self.data),
"mode": self.mode,
"kind": self.kind,
}
def _root(support_commit: str) -> str:
return f"{RELEASE_BASE}/c5-source-{support_commit}"
def _read_existing(snapshot_path: Path, target: str = BRIDGE_TARGET) -> tuple[dict[str, Any], dict[str, Any]]:
snapshot, _ = _json(snapshot_path)
profiles = snapshot.get("profiles")
if isinstance(profiles, Mapping):
profile = profiles.get(target)
if not isinstance(profile, Mapping):
_error("EXISTING_SNAPSHOT_TARGET_MISSING")
value = profile.get("existing", profile)
selected_snapshot: dict[str, Any] = dict(profile)
else:
value = snapshot.get("existing", snapshot)
selected_snapshot = dict(snapshot)
if not isinstance(value, Mapping):
_error("EXISTING_SNAPSHOT_INVALID")
required = {
"unit_active", "effective_config_sha256", "dropin", "pythonpath", "source_files",
"activation_file", "principal_scope_config",
}
if not required.issubset(value):
_error("EXISTING_SNAPSHOT_FIELDS_INVALID")
# Keep only the installer-owned snapshot projection. Live capture files
# also carry target/unit/readiness metadata; copying those keys into a
# request would fail the typed request's exact-field check.
existing = {key: value[key] for key in required}
if existing["unit_active"] is not True:
_error("EXISTING_UNIT_NOT_ACTIVE")
if not isinstance(existing["effective_config_sha256"], str) or not _HEX64.fullmatch(existing["effective_config_sha256"]):
_error("EXISTING_CONFIG_HASH_INVALID")
pythonpath = existing["pythonpath"]
if not isinstance(pythonpath, list) or not pythonpath or len(set(pythonpath)) != len(pythonpath):
_error("EXISTING_PYTHONPATH_INVALID")
for item in pythonpath:
if not isinstance(item, str) or not item.startswith("/") or ".." in PurePosixPath(item).parts:
_error("EXISTING_PYTHONPATH_INVALID")
if value["activation_file"] is not None or value["principal_scope_config"] is not None:
_error("EXISTING_ACTIVATION_ALREADY_PRESENT")
dropin = existing["dropin"]
if not isinstance(dropin, Mapping) or set(dropin) != {"exists", "sha256"}:
_error("EXISTING_DROPIN_INVALID")
if type(dropin["exists"]) is not bool:
_error("EXISTING_DROPIN_INVALID")
if dropin["exists"] is True and (not isinstance(dropin["sha256"], str) or not _HEX64.fullmatch(dropin["sha256"])):
_error("EXISTING_DROPIN_INVALID")
if dropin["exists"] is False and dropin["sha256"] is not None:
_error("EXISTING_DROPIN_INVALID")
source_files = existing["source_files"]
if not isinstance(source_files, list) or not source_files:
_error("EXISTING_SOURCE_FILES_INVALID")
for record in source_files:
if not isinstance(record, Mapping) or set(record) != {"path", "sha256", "size", "mode", "kind"}:
_error("EXISTING_SOURCE_FILES_INVALID")
if (
not isinstance(record["path"], str) or not record["path"].startswith("/")
or any(part in {".", ".."} for part in PurePosixPath(record["path"]).parts)
or "\\" in record["path"]
or not _HEX64.fullmatch(str(record["sha256"]))
or type(record["size"]) is not int or record["size"] < 0
or record["mode"] not in _SOURCE_MODES or record["kind"] != "existing_source"
):
_error("EXISTING_SOURCE_FILES_INVALID")
if not any(record["path"] != base and record["path"].startswith(base.rstrip("/") + "/") for base in pythonpath):
_error("EXISTING_SOURCE_OUTSIDE_PYTHONPATH")
return existing, selected_snapshot
def _old_module(existing: Mapping[str, Any], target: str) -> dict[str, Any]:
suffix = (
"/aios_chatgpt_bridge_v2_server.py"
if target == BRIDGE_TARGET else "/aios_task_principal_issuer_server.py"
)
for record in existing["source_files"]:
if str(record["path"]).endswith(suffix):
return dict(record)
_error("EXISTING_MODULE_IDENTITY_MISSING")
def _old_bridge(existing: Mapping[str, Any]) -> dict[str, Any]:
return _old_module(existing, BRIDGE_TARGET)
def _authority_inputs(authority: Path, support_commit: str) -> tuple[list[ArtifactFile], str, dict[str, Any]]:
if not authority.is_dir() or authority.is_symlink():
_error("AUTHORITY_INPUT_DIRECTORY_INVALID")
collected: list[ArtifactFile] = []
for item in sorted(authority.rglob("*")):
if item.is_symlink():
_error("AUTHORITY_INPUT_SYMLINK")
if item.is_dir():
continue
relative = item.relative_to(authority).as_posix()
_safe_rel(relative)
collected.append(ArtifactFile(f"__AUTHORITY__/{relative}", _regular(item, label=relative), _mode(item)))
names = {item.path.removeprefix("__AUTHORITY__/") for item in collected}
if "source-activation.json" not in names or "policy.json" not in names:
_error("AUTHORITY_INPUTS_INCOMPLETE")
result_item = next((item for item in collected if item.path == "__AUTHORITY__/AUTHORITY_PREPARATION_RESULT.json"), None)
result: dict[str, Any] = {}
if result_item is not None:
result = json.loads(result_item.data.decode("utf-8"), object_pairs_hook=_strict_pairs)
if not isinstance(result, dict):
_error("AUTHORITY_RESULT_INVALID")
policy_item = next(item for item in collected if item.path == "__AUTHORITY__/policy.json")
try:
policy = json.loads(policy_item.data.decode("utf-8"), object_pairs_hook=_strict_pairs)
except (UnicodeError, json.JSONDecodeError) as exc:
raise BuildError("AUTHORITY_POLICY_INVALID") from exc
payload = policy.get("payload") if isinstance(policy, dict) else None
source_commit = result.get("source_commit") if result else (payload or {}).get("source_oid")
if not isinstance(source_commit, str) or not _HEX40.fullmatch(source_commit):
_error("SOURCE_COMMIT_INVALID")
if result and result.get("support_commit") != support_commit:
_error("AUTHORITY_SUPPORT_COMMIT_MISMATCH")
if isinstance(payload, Mapping) and payload.get("source_oid") != source_commit:
_error("AUTHORITY_SOURCE_COMMIT_MISMATCH")
root = _root(support_commit)
activation_item = next(item for item in collected if item.path == "__AUTHORITY__/source-activation.json")
try:
activation = json.loads(activation_item.data.decode("utf-8"), object_pairs_hook=_strict_pairs)
except (UnicodeError, json.JSONDecodeError) as exc:
raise BuildError("SOURCE_ACTIVATION_INVALID") from exc
if not isinstance(activation, Mapping) or activation.get("schema") != "aios.source-publication-host-activation.v1":
_error("SOURCE_ACTIVATION_INVALID")
if activation.get("policy_sha256") != _sha(policy_item.data):
_error("SOURCE_ACTIVATION_POLICY_HASH_MISMATCH")
for key in ("policy_artifact", "policy_revocations", "admission_activation"):
value = activation.get(key)
if not isinstance(value, str) or not value.startswith(root + "/"):
_error("SOURCE_ACTIVATION_PATH_INVALID")
output: list[ArtifactFile] = []
for item in collected:
rel = item.path.removeprefix("__AUTHORITY__/")
output.append(ArtifactFile(f"{root}/{rel}", item.data, item.mode))
return output, source_commit, dict(activation)
def _bridge_base_hash(repo: Path, support_commit: str) -> str:
blob = _git_blob(repo, support_commit, "scripts/build_c5_live_bridge_overlay.py")
tree = ast.parse(blob.decode("utf-8"))
for node in tree.body:
if isinstance(node, ast.Assign) and len(node.targets) == 1 and getattr(node.targets[0], "id", None) == "BASE_SHA256":
value = ast.literal_eval(node.value)
if isinstance(value, str) and _HEX64.fullmatch(value):
return value
_error("BRIDGE_BASE_IDENTITY_MISSING")
def _verify_overlays(overlay_app: Path, repo: Path, support_commit: str) -> list[ArtifactFile]:
if not overlay_app.is_dir() or overlay_app.is_symlink():
_error("OVERLAY_DIRECTORY_INVALID")
result_path = overlay_app.parent / "c5-epoch-v2-overlay.result.json"
result, _ = _json(result_path)
entries = result.get("files")
if not isinstance(entries, Mapping):
_error("EPOCH_OVERLAY_RESULT_INVALID")
snapshot = overlay_app.parent.parent / "staging-source-snapshot"
for name in ("aios_task_admission_bootstrap.py", "aios_task_admission_live_adapters.py"):
entry = entries.get(name)
source = snapshot / name
target = overlay_app / name
if not isinstance(entry, Mapping) or set(entry) != {"base_sha256", "base_bytes", "result_sha256", "result_bytes"}:
_error("EPOCH_OVERLAY_RESULT_INVALID")
base = _regular(source, label=f"overlay-base:{name}")
data = _regular(target, label=f"overlay-result:{name}")
if _sha(base) != entry["base_sha256"] or len(base) != entry["base_bytes"]:
_error("EPOCH_OVERLAY_BASE_MISMATCH")
if _sha(data) != entry["result_sha256"] or len(data) != entry["result_bytes"]:
_error("EPOCH_OVERLAY_RESULT_MISMATCH")
bridge_base = _regular(snapshot / "aios_chatgpt_bridge_v2_server.py", label="overlay-base:bridge")
if _sha(bridge_base) != _bridge_base_hash(repo, support_commit):
_error("BRIDGE_OVERLAY_BASE_MISMATCH")
files: list[ArtifactFile] = []
seen: set[str] = set()
for path in sorted(overlay_app.rglob("*")):
if path.is_symlink():
_error("OVERLAY_SYMLINK")
if path.is_dir():
continue
rel = path.relative_to(overlay_app).as_posix()
_safe_rel(rel)
if rel.endswith(".pyc") or "__pycache__" in PurePosixPath(rel).parts:
_error("OVERLAY_BYTECODE_FORBIDDEN")
if rel in seen:
_error("OVERLAY_DUPLICATE")
seen.add(rel)
files.append(ArtifactFile(f"app/{rel}", _regular(path, label=f"overlay:{rel}"), _mode(path)))
if not set(GENERATED_OVERLAYS).issubset(seen):
_error("OVERLAY_FILE_SET_INCOMPLETE")
# The principal builder emits a sibling result manifest. When present,
# bind every reported result byte to the exact file that will be archived.
principal_manifest = overlay_app.parent / "overlay-manifest.json"
if principal_manifest.exists():
manifest, _ = _json(principal_manifest)
entries = manifest.get("files")
if not isinstance(entries, Mapping) or not entries:
_error("PRINCIPAL_OVERLAY_RESULT_INVALID")
by_name = {item.path.removeprefix("app/"): item for item in files}
for name, entry in entries.items():
if not isinstance(name, str) or not isinstance(entry, Mapping):
_error("PRINCIPAL_OVERLAY_RESULT_INVALID")
target = by_name.get(name)
if target is None:
_error("PRINCIPAL_OVERLAY_RESULT_MISSING")
if entry.get("result_sha256") != _sha(target.data) or entry.get("bytes") != len(target.data):
_error("PRINCIPAL_OVERLAY_RESULT_MISMATCH")
return files
def _rewrite_c5_imports(data: bytes, name: str) -> bytes:
text = data.decode("utf-8")
old = "from aios_single_host_authority import"
new = "from c5_source_authority import"
if text.count(old) < 1:
_error(f"C5_IMPORT_ANCHOR_INVALID:{name}")
return text.replace(old, new).encode("utf-8")
def _dropin(
root: str, pythonpath: Iterable[str], principal: str | None,
*, target: str = BRIDGE_TARGET,
) -> bytes:
path = ":".join((f"{root}/app", *pythonpath))
value = "[Service]\n" f"Environment=PYTHONPATH={path}\n"
if target == BRIDGE_TARGET:
value += f"Environment=AIOS_SOURCE_PUBLICATION_ACTIVATION_FILE={root}/source-activation.json\n"
elif target != ISSUER_TARGET:
_error("INSTALL_TARGET_INVALID")
if principal is not None:
value += f"Environment=AIOS_C5_PRINCIPAL_SCOPE_CONFIG={principal}\n"
return value.encode("utf-8")
def _captured_rollback(
snapshot: Mapping[str, Any], existing: Mapping[str, Any], target: str,
) -> dict[str, Any] | None:
"""Return the captured pre-activation readiness projection when present.
The live capture contains richer profile metadata around ``existing``;
only the installer readiness object is carried into the request. Keep it
byte-for-byte as captured after checking the fields that bind it to the
selected existing snapshot.
"""
value = snapshot.get("rollback_readiness")
if not isinstance(value, Mapping):
if "existing" in snapshot:
_error("ROLLBACK_READINESS_MISSING")
return None
required = {
"url", "timeout_seconds", "unit_active", "pythonpath", "activation_file",
"principal_scope_config", "module_path", "module_sha256", "runtime_fields",
}
if set(value) != required or not isinstance(value["runtime_fields"], Mapping):
_error("ROLLBACK_READINESS_INVALID")
profile_url = RUNTIME_URL if target == BRIDGE_TARGET else ISSUER_RUNTIME_URL
if (
value["url"] != profile_url or value["timeout_seconds"] != DEFAULT_TIMEOUT
or value["unit_active"] is not True
or value["pythonpath"] != existing["pythonpath"]
or value["activation_file"] != existing["activation_file"]
or value["principal_scope_config"] != existing["principal_scope_config"]
):
_error("ROLLBACK_READINESS_BINDING_INVALID")
old = _old_module(existing, target)
if value["module_path"] != old["path"] or value["module_sha256"] != old["sha256"]:
_error("ROLLBACK_READINESS_BINDING_INVALID")
return dict(value)
def _request(
*, source_commit: str, support_commit: str, files: list[ArtifactFile], existing: Mapping[str, Any],
root: str, activation: Mapping[str, Any], owner_signer_id: str, contract_id: str,
nonce: str, ttl_seconds: int, issued_at: int, principal_path: str | None,
target: str = BRIDGE_TARGET, rollback_readiness: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
module_name = (
"aios_chatgpt_bridge_v2_server.py"
if target == BRIDGE_TARGET else "aios_task_principal_issuer_server.py"
)
module = next((file for file in files if file.path == f"{root}/app/{module_name}"), None)
if module is None:
_error("TARGET_MODULE_MISSING")
old_module = _old_module(existing, target)
new_pythonpath = [f"{root}/app", *existing["pythonpath"]]
new_module = f"{root}/app/{module_name}"
new_activation = f"{root}/source-activation.json" if target == BRIDGE_TARGET else None
new_principal = principal_path
if target == BRIDGE_TARGET:
mandatory = {"status": "active", "health": 200, "deployment_state": "STAGING", "repository": REPOSITORY,
"governed_writes_enabled": True}
runtime_url = RUNTIME_URL
elif target == ISSUER_TARGET:
mandatory = {"ready": True}
if principal_path is not None:
mandatory.update(c5_scope_enabled=True)
runtime_url = ISSUER_RUNTIME_URL
else:
_error("INSTALL_TARGET_INVALID")
readiness_fields = {
**mandatory, "source_module_path": new_module,
"source_module_sha256": _sha(module.data),
}
old_fields = {"status": "active", "health": 200, "deployment_state": "STAGING", "repository": REPOSITORY} if target == BRIDGE_TARGET else {"ready": True}
if target == ISSUER_TARGET and principal_path is not None:
readiness_fields.update(c5_scope_role="issuer", c5_scope_enrolled=False)
readiness = {
"url": runtime_url, "timeout_seconds": DEFAULT_TIMEOUT, "unit_active": True,
"pythonpath": new_pythonpath, "activation_file": new_activation,
"principal_scope_config": new_principal,
"module_path": new_module, "module_sha256": _sha(module.data),
"runtime_fields": readiness_fields,
}
rollback = dict(rollback_readiness) if rollback_readiness is not None else {
"url": runtime_url, "timeout_seconds": DEFAULT_TIMEOUT, "unit_active": True,
"pythonpath": list(existing["pythonpath"]), "activation_file": existing["activation_file"],
"principal_scope_config": existing["principal_scope_config"],
"module_path": old_module["path"], "module_sha256": old_module["sha256"],
"runtime_fields": old_fields,
}
return {
"schema": REQUEST_SCHEMA,
"target": target,
"contract_id": contract_id, "owner_signer_id": owner_signer_id,
"source_commit": source_commit, "support_commit": support_commit,
"files": [file.record for file in sorted(files, key=lambda item: item.path)],
"existing": dict(existing), "readiness": readiness,
"rollback_readiness": rollback, "nonce": nonce, "ttl_seconds": ttl_seconds,
}
def build_artifact(
repo: Path, support_commit: str, overlay_app: Path, authority_inputs: Path,
snapshot: Path, output: Path, *, owner_signer_id: str = "local-owner-authority",
contract_id: str | None = None, nonce: str | None = None, ttl_seconds: int = MAX_TTL,
issued_at: int | None = None, principal_scope_config: Path | None = None,
target: str = BRIDGE_TARGET,
) -> dict[str, Any]:
"""Build the artifact and unsigned request, returning their paths and hashes."""
repo, overlay_app, authority_inputs, snapshot, output = map(Path, (repo, overlay_app, authority_inputs, snapshot, output))
if not repo.is_dir() or repo.is_symlink():
_error("REPOSITORY_INVALID")
if target not in {BRIDGE_TARGET, ISSUER_TARGET}:
_error("INSTALL_TARGET_INVALID")
support_commit = _support_oid(repo, support_commit)
if not _ID.fullmatch(owner_signer_id):
_error("OWNER_SIGNER_ID_INVALID")
if type(ttl_seconds) is not int or not 1 <= ttl_seconds <= MAX_TTL:
_error("TTL_INVALID")
nonce = secrets.token_hex(32) if nonce is None else nonce
if not isinstance(nonce, str) or not _NONCE.fullmatch(nonce):
_error("NONCE_INVALID")
issued_at = int(time.time()) if issued_at is None else issued_at
if type(issued_at) is not int or issued_at < 0:
_error("ISSUED_AT_INVALID")
if principal_scope_config is not None and (principal_scope_config.is_symlink() or not principal_scope_config.is_file()):
_error("PRINCIPAL_CONFIG_INVALID")
existing, snapshot_value = _read_existing(snapshot, target)
authority_files, source_commit, activation = _authority_inputs(authority_inputs, support_commit)
overlay_files = _verify_overlays(overlay_app, repo, support_commit)
rewritten_overlays: list[ArtifactFile] = []
for item in overlay_files:
if item.path == "app/c5_governed_principal_scope.py":
rewritten_overlays.append(
ArtifactFile(item.path, _rewrite_c5_imports(item.data, item.path), item.mode, item.kind)
)
else:
rewritten_overlays.append(item)
overlay_files = rewritten_overlays
root = _root(support_commit)
c5_files: list[ArtifactFile] = []
for source_path, destination in sorted(SOURCE_MODULES.items()):
data = _git_blob(repo, support_commit, source_path)
if destination in {"app/source_publication_runtime.py", "app/governed_source_publication_policy.py"}:
data = _rewrite_c5_imports(data, destination)
c5_files.append(ArtifactFile(f"{root}/{destination}", data, 0o644))
files: list[ArtifactFile] = []
files.extend(authority_files)
files.extend(ArtifactFile(f"{root}/{item.path}", item.data, item.mode) for item in overlay_files)
files.extend(c5_files)
principal_path: str | None = next(
(item.path for item in files if item.path == f"{root}/principal-activation.json"),
None,
)
if principal_scope_config is not None:
data = _regular(principal_scope_config, label="principal-scope-config")
principal_path = f"{root}/principal-activation.json"
if any(item.path == principal_path for item in files):
_error("PRINCIPAL_CONFIG_DUPLICATE")
files.append(ArtifactFile(principal_path, data, 0o644))
if target == ISSUER_TARGET and principal_path is None:
_error("PRINCIPAL_CONFIG_REQUIRED")
dropin_path = DROPIN if target == BRIDGE_TARGET else ISSUER_DROPIN
dropin_data = _dropin(root, existing["pythonpath"], principal_path, target=target)
files.append(ArtifactFile(dropin_path, dropin_data, 0o644, "systemd_dropin"))
names = [item.path for item in files]
if len(names) != len(set(names)):
_error("ARTIFACT_DUPLICATE_PATH")
if not contract_id:
contract_id = f"c5-installation-{support_commit[:12]}-{nonce[:12]}"
if not _ID.fullmatch(contract_id):
_error("CONTRACT_ID_INVALID")
request = _request(
source_commit=source_commit, support_commit=support_commit, files=files,
existing=existing, root=root, activation=activation,
owner_signer_id=owner_signer_id, contract_id=contract_id,
nonce=nonce, ttl_seconds=ttl_seconds, issued_at=issued_at,
principal_path=principal_path, target=target,
rollback_readiness=_captured_rollback(snapshot_value, existing, target),
)
manifest = {
"schema": "aios.c5-staging-artifact-manifest.v1",
"repository": REPOSITORY, "source_commit": source_commit,
"support_commit": support_commit, "release_root": root,
"target": target,
"unit": UNIT if target == BRIDGE_TARGET else ISSUER_UNIT,
"listen": {"host": "127.0.0.1", "port": PORT if target == BRIDGE_TARGET else ISSUER_PORT},
"files": [item.record for item in sorted(files, key=lambda item: item.path)],
}
output.mkdir(parents=True, exist_ok=True)
suffix = "" if target == BRIDGE_TARGET else "-principal-issuer"
artifact_path = output / f"c5-source-{support_commit}{suffix}.tar"
request_path = output / ("installation-request.json" if target == BRIDGE_TARGET else "principal-issuer-installation-request.json")
manifest_path = output / "artifact-manifest.json"
sums_path = output / "SHA256SUMS"
if any(path.exists() for path in (artifact_path, request_path, manifest_path, sums_path)):
_error("OUTPUT_ALREADY_EXISTS")
with tarfile.open(artifact_path, "w", format=tarfile.PAX_FORMAT) as archive:
for item in sorted(files, key=lambda value: value.path):
member_name = "dropin.conf" if item.path == dropin_path else item.path.removeprefix(root + "/")
info = tarfile.TarInfo(member_name)
info.type = tarfile.REGTYPE
info.mode = item.mode
info.uid = info.gid = 0
info.uname = info.gname = ""
info.mtime = 0
info.size = len(item.data)
archive.addfile(info, io.BytesIO(item.data))
manifest_path.write_bytes(_canonical(manifest))
request_path.write_bytes(_canonical(request))
sums = "".join(
f"{_sha(path.read_bytes())} {path.name}\n"
for path in (artifact_path, manifest_path, request_path)
).encode("ascii")
sums_path.write_bytes(sums)
return {
"schema": "aios.c5-staging-artifact-build-result.v1",
"status": "BUILT_UNSIGNED",
"artifact": str(artifact_path), "artifact_sha256": _sha(artifact_path.read_bytes()),
"request": str(request_path), "request_sha256": _sha(request_path.read_bytes()),
"manifest": str(manifest_path), "sha256_manifest": str(sums_path),
"source_commit": source_commit, "support_commit": support_commit,
"release_root": root, "signed": False, "installed": False,
}
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo", "--repository", dest="repo", type=Path, required=True)
parser.add_argument("--support-commit", required=True)
parser.add_argument("--overlay-app", "--overlay", dest="overlay_app", type=Path, required=True)
parser.add_argument("--authority-inputs", "--authority-input-dir", dest="authority_inputs", type=Path, required=True)
parser.add_argument("--snapshot", "--unit-snapshot", dest="snapshot", type=Path, required=True)
parser.add_argument("--output", "--output-dir", dest="output", type=Path, required=True)
parser.add_argument("--owner-signer-id", default="local-owner-authority")
parser.add_argument("--contract-id")
parser.add_argument("--nonce")
parser.add_argument("--ttl-seconds", type=int, default=MAX_TTL)
parser.add_argument("--issued-at", type=int)
parser.add_argument("--principal-scope-config", type=Path)
parser.add_argument("--target", choices=(BRIDGE_TARGET, ISSUER_TARGET), default=BRIDGE_TARGET)
return parser
def main(argv: list[str] | None = None) -> int:
args = _parser().parse_args(argv)
try:
result = build_artifact(
args.repo, args.support_commit, args.overlay_app, args.authority_inputs,
args.snapshot, args.output, owner_signer_id=args.owner_signer_id,
contract_id=args.contract_id, nonce=args.nonce,
ttl_seconds=args.ttl_seconds, issued_at=args.issued_at,
principal_scope_config=args.principal_scope_config, target=args.target,
)
except (BuildError, OSError, ValueError) as exc:
print(json.dumps({"status": "REJECTED", "error": str(exc)}, sort_keys=True))
return 2
print(json.dumps(result, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Issue, preflight, or apply the fixed-scope C5 staging installation."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "active-runtime" / "app"))
from c5_staging_installation import ( # noqa: E402
InstallationError,
InstallationOutcomeUnknown,
Installer,
RegistryOwnerVerifier,
canonical_bytes,
issue_signed_contract,
load_installation_request,
)
from registered_owner_signer_adapter import ( # noqa: E402
DEFAULT_REGISTERED_SIGNER_COMMAND,
RegisteredOwnerSignerAdapter,
)
DEFAULT_REGISTRY = Path("/etc/aios/single-host-signer-registry.json")
def _write_new(path: Path, value: dict) -> None:
data = canonical_bytes(value) + b"\n"
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
fd = os.open(path, flags, 0o600)
try:
view = memoryview(data)
while view:
view = view[os.write(fd, view) :]
os.fsync(fd)
finally:
os.close(fd)
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
for name in ("preflight", "apply", "reconcile-unknown", "rollback-applied"):
command = commands.add_parser(name)
command.add_argument("--contract", type=Path, required=True)
command.add_argument("--artifact", type=Path, required=True)
command.add_argument("--registry", type=Path, default=DEFAULT_REGISTRY)
if name == "rollback-applied":
command.add_argument("--issuer-contract", type=Path)
command.add_argument("--issuer-artifact", type=Path)
issue = commands.add_parser("issue")
issue.add_argument("--request", type=Path, required=True)
issue.add_argument("--artifact", type=Path, required=True)
issue.add_argument("--registry", type=Path, default=DEFAULT_REGISTRY)
issue.add_argument("--output", type=Path, required=True)
issue.add_argument("--signer-command", type=Path, default=DEFAULT_REGISTERED_SIGNER_COMMAND)
return parser
def main(argv: list[str] | None = None) -> int:
args = _parser().parse_args(argv)
try:
if args.command == "issue":
request = load_installation_request(args.request)
verifier = RegistryOwnerVerifier(
args.registry, owner_signer_id=request.owner_signer_id
)
signer = RegisteredOwnerSignerAdapter(
args.signer_command,
key_id=verifier.record.fingerprint,
owner_verifier=verifier,
)
contract = issue_signed_contract(
request, args.artifact, signer, args.registry
)
_write_new(args.output, contract)
result = {
"status": "ISSUED",
"contract_id": contract["payload"]["contract_id"],
"output": str(args.output),
}
else:
installer = Installer(args.registry)
if args.command == "apply":
result = installer.apply(args.contract, args.artifact)
elif args.command == "reconcile-unknown":
result = installer.reconcile_unknown(args.contract, args.artifact)
elif args.command == "rollback-applied":
result = installer.rollback_applied(
args.contract, args.artifact,
issuer_contract=args.issuer_contract,
issuer_artifact=args.issuer_artifact,
)
else:
result = installer.preflight(args.contract, args.artifact)
print(json.dumps(result, sort_keys=True))
return 0
except InstallationOutcomeUnknown as exc:
print(
json.dumps({"status": "UNKNOWN", "error": exc.code}, sort_keys=True),
file=sys.stderr,
)
return 3
except (InstallationError, OSError, ValueError, TypeError) as exc:
print(
json.dumps(
{"status": "REJECTED", "error": getattr(exc, "code", str(exc))},
sort_keys=True,
),
file=sys.stderr,
)
return 2
if __name__ == "__main__":
raise SystemExit(main())
+189
View File
@@ -0,0 +1,189 @@
"""Isolated composition for the C5 simulation CLI, never a production adapter."""
from __future__ import annotations
from dataclasses import asdict
import hashlib
import json
import os
from pathlib import Path
import secrets
import time
from aios_single_host_authority import (
LocalOwnerAuthority, OwnerEpochRequest, RegistryOwnerApprovalVerifier,
SingleHostOwnerPolicy, SourcePublicationPolicyRequest,
bootstrap_key, canonical_json_bytes, write_signed_epoch_directory,
write_signer_registry,
)
from aios_task_admission_bootstrap import TaskAdmissionBootstrapService, TaskAdmissionPolicy, TaskAdmissionStore
from aios_task_admission_live_adapters import (
AgentTaskAdmissionPort, BoundOwnerAuthorizationBackend, SignedEpochDirectoryProvider,
TaskAdmissionPrincipalProvider, TaskAdmissionRuntimeBundle,
)
from governed_source_publication_policy import SourcePublicationPolicyVerifier, ScopedSourcePublicationPolicyAdmission
from governed_unattended_release import SourcePublicationIntent, GovernedUnattendedReleaseRunner
from source_publication_runtime import guard_source_runtime
def _write(path: Path, value: dict) -> str:
raw = canonical_json_bytes(value)
path.write_bytes(raw)
path.chmod(0o600)
return hashlib.sha256(raw).hexdigest()
def signed_admission(tmp: Path, output: Path, source: dict, execution: dict, transport_type) -> dict:
now = int(time.time())
nonce = secrets.token_hex(24)
caps = frozenset(source["requested_capabilities"])
files = tuple(source["file_allowlist"])
# These new keys never leave TemporaryDirectory and are not loaded from any
# host registry, credential store or production environment variable.
key_root = tmp / "ephemeral-keys"
key_root.mkdir(mode=0o700)
owner_key = key_root / "owner.pem"
machine_key = key_root / "machine.pem"
owner_public = bootstrap_key(owner_key, require_root_owner=False)
machine_public = bootstrap_key(machine_key, require_root_owner=False)
registry = tmp / "simulation-signers.json"
write_signer_registry(
registry, owner_public_key=owner_public, machine_public_key=machine_public,
authority_generation=1, require_root_owner=False,
)
owner_policy = SingleHostOwnerPolicy(
repository=source["repository"], trust_domain="aios.simulation",
allowed_branch_prefixes=(source["target_branch"],), allowed_capabilities=caps,
allowed_file_prefixes=files, risk_ceiling="engineering",
production_scope="none", destructive_policy="forbidden",
maximum_epoch_ttl_seconds=3600, maximum_machine_capability_ttl_seconds=300,
allowed_caller_uids=(os.getuid(),),
)
owner_policy.validate()
authority = LocalOwnerAuthority(owner_key, owner_policy, require_root_owner=False)
request = SourcePublicationPolicyRequest(
policy_id="C5_SOURCE_PUBLICATION_ONLY", repository=source["repository"],
authority_base_oid=source["authority_base_oid"], source_oid=source["source_commit"],
source_tree_oid=source["tree_oid"], target_agent="agent/c5-simulation-controller",
target_branch=source["target_branch"], generation=source["generation"],
execution_digest=source["execution_digest"], exact_files=files,
nonce=nonce, ttl_seconds=3600,
)
signed_policy = authority.issue_source_publication_policy(request)
epoch = authority.issue_epoch(OwnerEpochRequest(
epoch_id="c5-simulation-epoch-" + nonce,
allowed_branch_prefixes=(source["target_branch"],), allowed_capabilities=caps,
allowed_file_prefixes=files, risk_ceiling="engineering",
business_goal="Isolated C5 source publication simulation only",
production_scope="none", destructive_policy="forbidden", ttl_seconds=3600,
))
epoch_root = tmp / "epoch"
write_signed_epoch_directory(epoch_root, epoch, require_root_owner=False)
epoch_revocations = tmp / "epoch-revocations.json"
policy_revocations = tmp / "policy-revocations.json"
_write(epoch_revocations, {"schema": "aios.owner-delegation-revocations.v1", "revoked_epoch_ids": []})
_write(policy_revocations, {"revoked_nonces": []})
owner_verifier = RegistryOwnerApprovalVerifier(registry, require_root_owner=False)
caller = {"service": "c5-simulation-controller", "trust_domain": "aios.simulation"}
provider = SignedEpochDirectoryProvider(
root=epoch_root, verifier=owner_verifier, revocation_file=epoch_revocations,
expected_caller=caller, require_root_owner=False,
)
verified_epoch = provider(epoch_id=epoch.payload["epoch_id"], caller=caller)
if verified_epoch.schema != "aios.owner-delegation-epoch.v2" or verified_epoch.policy_digest != owner_policy.digest:
raise RuntimeError("SIMULATION_EPOCH_PROVENANCE_MISMATCH")
authorization = BoundOwnerAuthorizationBackend(
authenticated_caller=caller, verified_epoch_provider=provider, signature_verifier=owner_verifier,
)
verifier = SourcePublicationPolicyVerifier(
owner_verifier,
revocation_check=lambda payload: payload["nonce"] not in json.loads(policy_revocations.read_text())["revoked_nonces"],
expected_policy_digest=owner_policy.digest,
)
verifier.require(signed_policy, request)
transport = transport_type(tmp / "isolated-tasks.sqlite3")
port = AgentTaskAdmissionPort(transport, execution_plan_provider=lambda grant: {
"external_job_id": "simulation-job-" + nonce,
"repository": grant.repository, "target_branch": grant.target_branch,
"generation": grant.generation, "execution_digest": grant.execution_digest,
"simulation_plan": execution,
})
bootstrap = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp / "isolated-admission.sqlite3"),
authorization=authorization, task_port=port,
policy=TaskAdmissionPolicy(
repository=owner_policy.repository, trust_domain=owner_policy.trust_domain,
branch_prefix=source["target_branch"], allowed_capabilities=caps,
max_file_count=len(files), max_risk="engineering", policy_digest=owner_policy.digest,
),
)
principal = TaskAdmissionPrincipalProvider(
store=bootstrap.store, authorization=authorization, task_port=port,
epoch_id=epoch.payload["epoch_id"],
)
guarded = guard_source_runtime(
TaskAdmissionRuntimeBundle(handlers={}, principal_verifier=principal, service=bootstrap),
artifact=signed_policy, verifier=verifier,
expected_agent_id="agent/c5-simulation-controller",
).service
guarded.simulation_only = True # this composition contains only the isolated TaskStore
intent = SourcePublicationIntent(
repository=source["repository"], source_commit=source["source_commit"],
source_tree_oid=source["tree_oid"], authority_base_oid=source["authority_base_oid"],
target_branch=source["target_branch"], file_allowlist=files,
requested_capabilities=tuple(sorted(caps)), risk="engineering",
execution_digest=source["execution_digest"], generation=source["generation"],
event_id="c5-simulation", version="V2R2C5",
)
runner = GovernedUnattendedReleaseRunner(admission=guarded)
auth_request = {"authorization_kind": "delegation_epoch", "delegation_epoch_id": epoch.payload["epoch_id"]}
first = runner.admit(intent, auth_request)
replay = runner.admit(intent, auth_request)
if first.get("phase") != "ADMITTED" or replay.get("phase") != "ADMITTED":
raise RuntimeError("SIMULATION_ADMISSION_FAILED:" + json.dumps({"first": first, "replay": replay}))
receipt = first["receipt"]
if replay["receipt"].get("idempotent") is not True or transport.creates != 1:
raise RuntimeError("SIMULATION_IDEMPOTENCY_FAILED")
raw_request = intent.admission_request()
try:
guarded.create_envelope({**raw_request, "target_branch": "agent/out-of-policy"})
except Exception:
widening_denied = True
else:
raise RuntimeError("SIMULATION_SCOPE_WIDENING_WAS_ALLOWED")
_write(policy_revocations, {"revoked_nonces": [nonce]})
try:
guarded.authorize_and_admit({**auth_request, "envelope_id": receipt["envelope_id"]})
except Exception:
revoked_policy_denied = True
else:
raise RuntimeError("SIMULATION_REVOKED_POLICY_WAS_ALLOWED")
_write(policy_revocations, {"revoked_nonces": []})
_write(epoch_revocations, {"schema": "aios.owner-delegation-revocations.v1", "revoked_epoch_ids": [epoch.payload["epoch_id"]]})
try:
guarded.authorize_and_admit({**auth_request, "envelope_id": receipt["envelope_id"]})
except Exception:
revoked_epoch_denied = True
else:
raise RuntimeError("SIMULATION_REVOKED_EPOCH_WAS_ALLOWED")
_write(output / "RELEASE_INTENT.simulation.json", runner.intake(intent))
receipt_sha = _write(output / "ADMISSION_RECEIPT.simulation.json", {"environment": "SIMULATION", "receipt": receipt})
policy_sha = _write(output / "POLICY_RECEIPT.simulation.json", {
"environment": "SIMULATION", "payload": dict(signed_policy.payload),
"signature": signed_policy.signature, "key_id": signed_policy.key_id,
"namespace": signed_policy.namespace, "payload_sha256": signed_policy.payload_sha256,
})
for name in ("owner-delegation-epoch.canonical.json", "owner-delegation-epoch.sig", "owner-delegation-epoch.key-id"):
(output / ("SIMULATION_" + name)).write_bytes((epoch_root / name).read_bytes())
(output / "SIMULATION_signer_registry.json").write_bytes(registry.read_bytes())
return {
"mode": "SIMULATION", "task_id": receipt["task_id"], "task_state": receipt["task_state"],
"receipt_sha256": receipt_sha, "policy_receipt_sha256": policy_sha,
"epoch_payload_sha256": epoch.payload_sha256, "epoch_schema": verified_epoch.schema,
"issuer_key_id": epoch.key_id, "policy_digest": owner_policy.digest,
"canonical_signature_verified": True, "idempotent_replay": True,
"task_creations": transport.creates, "scope_widening_denied": widening_denied,
"revoked_policy_denied": revoked_policy_denied, "revoked_epoch_denied": revoked_epoch_denied,
"started_at": now, "ended_at": int(time.time()),
"task_lease": None, "task_principal": None, "writer_lease": None,
"authority_source_commit": None,
}
+150
View File
@@ -0,0 +1,150 @@
"""Issue exact C5 source authority through the registered Owner signer.
Output is an immutable installation input. This command does not install files,
activate policy, submit admission, or grant release/deployment permission.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import secrets
import subprocess
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "active-runtime" / "app"))
from aios_single_host_authority import (
LocalOwnerAuthority, OwnerEpochRequest, RegistryOwnerApprovalVerifier,
SignerRegistry, SingleHostOwnerPolicy, SourcePublicationPolicyRequest,
canonical_json_bytes, write_signed_epoch_directory, _open_secure,
)
from registered_owner_signer_adapter import RegisteredOwnerSignerAdapter
SOURCE = "536a2af0f07962495c88526061ce43fb27994c15"
TREE = "9f27f76e7fa5561f90ff8bfe26fd18865b4a5ebb"
BASE = "e5f8209f57b43fc39e55c4555776e6362ce326eb"
PLAN_SHA = "1ba534d551991e8c99014285eeea36afcbc1500686035cb8bcd7f9f4efd6215e"
TASK = "TASK-V2R2C5-SOURCE-PUBLICATION-20260926-R1"
BRANCH = "agent/v2r2c5-source-publication-20260926"
AGENT = "agent/codex-c5-release"
CAPS = frozenset(("agent-task:control", "governed.task.create", "governed.write.files",
"task.principal.issue", "writer.lease"))
REGISTRY = Path("/etc/aios/single-host-signer-registry.json")
def prepare(candidate: Path, plan_path: Path, ordinary_activation: Path,
support_commit: str, output: Path) -> dict:
if os.geteuid() != 0 or not re.fullmatch(r"[0-9a-f]{40}", support_commit):
raise ValueError("ROOT_AND_EXACT_SUPPORT_COMMIT_REQUIRED")
def git(*args):
return subprocess.run(["git", "-C", str(candidate), *args], check=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30).stdout
if git("rev-parse", SOURCE + "^{tree}").decode().strip() != TREE:
raise ValueError("CANDIDATE_TREE_MISMATCH")
git("merge-base", "--is-ancestor", BASE, SOURCE)
raw = plan_path.read_bytes()
plan = json.loads(raw)
if hashlib.sha256(raw).hexdigest() != PLAN_SHA or canonical_json_bytes(plan) != raw:
raise ValueError("EXACT_PLAN_REQUIRED")
expected = {"schema": "aios.task-admission-execution-plan.v3", "task_id": TASK,
"repository": "aios/aios", "authority_base_oid": BASE,
"target_branch": BRANCH, "generation": 1, "risk": "engineering"}
if any(plan.get(k) != v for k, v in expected.items()) or set(plan["capabilities"]) != CAPS:
raise ValueError("PLAN_SCOPE_MISMATCH")
files = tuple(sorted(git("diff", "--name-only", "-z", BASE, SOURCE).decode().rstrip("\0").split("\0")))
hashes = {p: hashlib.sha256(git("show", TREE + ":" + p)).hexdigest() for p in files}
if hashes != plan["file_sha256"] or set(files) != set(plan["file_allowlist"]):
raise ValueError("PLAN_COMMITTED_BYTES_MISMATCH")
ordinary_raw = _open_secure(ordinary_activation, private=False, require_root_owner=True, maximum=1024 * 1024)
activation = json.loads(ordinary_raw)
if activation.get("schema") != "aios.task-admission-host-activation.v1":
raise ValueError("ORDINARY_ACTIVATION_REQUIRED")
# No existing authority policy is rewritten. This separate exact policy is
# enforced by the source router and signed by the existing registered Root.
policy = SingleHostOwnerPolicy(repository="aios/aios", trust_domain="aios.owner",
allowed_branch_prefixes=(BRANCH,), allowed_capabilities=CAPS,
allowed_file_prefixes=files, risk_ceiling="engineering", production_scope="none",
destructive_policy="forbidden", maximum_epoch_ttl_seconds=3600,
maximum_machine_capability_ttl_seconds=300, allowed_caller_uids=(0,))
registry = SignerRegistry(REGISTRY)
record = registry.require("local-owner-authority", role="OWNER_AUTHORITY")
verifier = RegistryOwnerApprovalVerifier(REGISTRY)
adapter = RegisteredOwnerSignerAdapter(key_id=record.fingerprint, owner_verifier=verifier)
authority = LocalOwnerAuthority(None, policy, registered_signer=adapter)
nonce = secrets.token_hex(32)
request = SourcePublicationPolicyRequest(policy_id="C5_SOURCE_PUBLICATION_ONLY",
repository="aios/aios", authority_base_oid=BASE, source_oid=SOURCE,
source_tree_oid=TREE, target_agent=AGENT, target_branch=BRANCH, generation=1,
execution_digest=PLAN_SHA, exact_files=files, nonce=nonce, ttl_seconds=3600)
signed_policy = authority.issue_source_publication_policy(request)
epoch = authority.issue_epoch(OwnerEpochRequest(epoch_id="c5-source-" + nonce,
allowed_branch_prefixes=(BRANCH,), allowed_capabilities=CAPS,
allowed_file_prefixes=files, risk_ceiling="engineering",
business_goal="V2R2C5 exact governed source publication only",
production_scope="none", destructive_policy="forbidden", ttl_seconds=3600))
output.mkdir(mode=0o700, parents=False, exist_ok=False)
final_root = Path("/var/lib/aios-gitops/bridge-v2-staging/releases") / ("c5-source-" + support_commit)
def write(relative, value):
path = output / relative
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
path.write_bytes(canonical_json_bytes(value))
path.chmod(0o600)
write("policy.json", {"payload": dict(signed_policy.payload), "signature": signed_policy.signature,
"key_id": signed_policy.key_id, "namespace": signed_policy.namespace})
write_signed_epoch_directory(output / "epoch", epoch)
write("epoch-revocations.json", {"schema": "aios.owner-delegation-revocations.v1", "revoked_epoch_ids": []})
write("policy-revocations.json", {"schema": "aios.source-publication-revocations.v1", "revoked_nonces": []})
write("plans/" + PLAN_SHA + ".json", plan)
activation.update(epoch_root=str(final_root / "epoch"), epoch_id=epoch.payload["epoch_id"],
revocation_file=str(final_root / "epoch-revocations.json"), execution_plan_dir=str(final_root / "plans"))
write("admission-activation.json", activation)
write("source-activation.json", {"schema": "aios.source-publication-host-activation.v1",
"policy_artifact": str(final_root / "policy.json"),
"policy_sha256": hashlib.sha256((output / "policy.json").read_bytes()).hexdigest(),
"policy_revocations": str(final_root / "policy-revocations.json"),
"admission_activation": str(final_root / "admission-activation.json"), "expected_agent_id": AGENT})
grant_info = Path(activation["state_db"]).stat()
write("principal-activation.json", {
"schema": "aios.c5-task-principal-activation.v1",
"task_id": TASK, "agent_id": AGENT, "execution_digest": PLAN_SHA,
"store_path": "/var/lib/aios-gitops/c5-source-principal/enrollment.sqlite3",
"grant_db": activation["state_db"],
"plan_dir": str(final_root / "plans"),
"producer_state_root": "/var/lib/aios-gitops/bridge-v2-staging",
"policy_artifact": str(final_root / "policy.json"),
"policy_revocations": str(final_root / "policy-revocations.json"),
"epoch_root": str(final_root / "epoch"),
"epoch_revocations": str(final_root / "epoch-revocations.json"),
"owner_registry": str(REGISTRY), "owner_signer_id": "local-owner-authority",
"registered_signer_command": "/usr/local/libexec/aios-grant-signer-adapter",
"require_root_plan_owner": True,
"grant_db_allowed_group_gid": grant_info.st_gid if grant_info.st_mode & 0o020 else None,
})
result = {"schema": "aios.c5-source-authority-preparation.v1", "status": "SIGNED_NOT_ACTIVATED",
"source_commit": SOURCE, "tree": TREE, "support_commit": support_commit,
"policy_id": "C5_SOURCE_PUBLICATION_ONLY", "policy_digest": policy.digest,
"epoch_id": epoch.payload["epoch_id"], "expires_at": epoch.payload["expires_at"],
"owner_fingerprint": record.fingerprint, "installation_root": str(final_root),
"ordinary_activation_sha256": hashlib.sha256(ordinary_raw).hexdigest(),
"admission": "NOT_ATTEMPTED", "authority_source_commit": None}
write("AUTHORITY_PREPARATION_RESULT.json", result)
return result
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--candidate", required=True, type=Path)
parser.add_argument("--plan", required=True, type=Path)
parser.add_argument("--ordinary-activation", required=True, type=Path)
parser.add_argument("--support-commit", required=True)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
print(json.dumps(prepare(args.candidate, args.plan, args.ordinary_activation,
args.support_commit, args.output), indent=2))
+209
View File
@@ -0,0 +1,209 @@
"""Execute exact C5 source publication through the installed governed services.
Run on the verified host as the controller caller. Tokens stay in process memory.
Any indeterminate mutation is recorded and stops; this command never edits refs,
Task state, trust manifests, or release state to recover an operation.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import socket
import subprocess
import sys
import time
from typing import Any
import urllib.parse
def run(candidate: Path, installation: Path, output: Path) -> dict[str, Any]:
if os.geteuid() != 0 or socket.gethostname() != "cn-network-mkt":
raise ValueError("EXACT_CONTROLLER_HOST_REQUIRED")
source = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(installation / "app"), str(source / "active-runtime"),
str(source / "active-runtime" / "app")]
from prepare_c5_source_authority import SOURCE, TREE, BASE, PLAN_SHA, TASK, BRANCH, AGENT, CAPS
sys.path[:0] = [str(installation / "app"), str(source / "active-runtime"),
str(source / "active-runtime" / "app")]
from c5_governed_principal_scope import RuntimeConfig, _service_from_config, C5ScopeEnrollmentIssuer
from governed_unattended_release import SourcePublicationIntent, GovernedUnattendedReleaseRunner
from governed_publication_client import GovernedPublicationClient
from app.task_plane.governed_writer_automation import OwnerAuthorizationPolicy
from app.task_plane.live_governed_writer_adapters import LiveProtocolConfig, LiveGovernedWriterExecutor, JsonHttpTransport
os.environ["AIOS_C5_PRINCIPAL_SCOPE_CONFIG"] = str(installation / "principal-activation.json")
config = RuntimeConfig.from_environment()
if config is None:
raise ValueError("INSTALLED_PRINCIPAL_CONFIG_REQUIRED")
service = _service_from_config(config)
owner = service.owner.verify(int(time.time()))
activation = json.loads((installation / "admission-activation.json").read_bytes())
plan_raw = (installation / "plans" / (PLAN_SHA + ".json")).read_bytes()
plan = json.loads(plan_raw)
if hashlib.sha256(plan_raw).hexdigest() != PLAN_SHA:
raise ValueError("EXACT_PLAN_REQUIRED")
def git(*args: str, authority: bool = False) -> bytes:
command = ["git", "--git-dir=/data/aios-gitops/repo.git"] if authority else ["git", "-C", str(candidate)]
return subprocess.run(command + list(args), check=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=30).stdout
if git("rev-parse", SOURCE + "^{tree}").decode().strip() != TREE:
raise ValueError("CANDIDATE_TREE_MISMATCH")
if git("rev-parse", "refs/heads/main", authority=True).decode().strip() != BASE:
raise ValueError("AUTHORITY_BASE_CHANGED")
for path, digest in plan["file_sha256"].items():
if hashlib.sha256(git("show", TREE + ":" + path)).hexdigest() != digest:
raise ValueError("COMMITTED_FILE_HASH_MISMATCH")
intent = SourcePublicationIntent(repository="aios/aios", source_commit=SOURCE,
source_tree_oid=TREE, authority_base_oid=BASE, target_branch=BRANCH,
file_allowlist=tuple(plan["file_allowlist"]), requested_capabilities=tuple(sorted(CAPS)),
risk="engineering", execution_digest=PLAN_SHA, generation=1, version="V2R2C5",
event_id="v2r2c5-source-publication-20260926", admission_task_id=TASK,
admission_idempotency_key="c5-source-publication-20260926-r1-1ba534d551991e8c9901")
output.mkdir(mode=0o700, parents=False, exist_ok=False)
def save(name: str, value: Any) -> None:
raw = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
fd = os.open(output / name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
with os.fdopen(fd, "wb") as handle:
handle.write(raw)
handle.flush()
os.fsync(handle.fileno())
save("intent.json", intent.as_dict())
transport = JsonHttpTransport()
class AdmissionError(RuntimeError):
def __init__(self, status: int, code: str):
self.status_code, self.code = status, code
super().__init__(code)
class Admission:
sequence = 0
def call(self, method, path, value=None):
self.sequence += 1
number = self.sequence
save(f"admission-{number}-request.json", {"method": method, "path": path, "body": value})
result = transport.request(method, "http://127.0.0.1:18892" + path,
body=value, timeout=30)
save(f"admission-{number}-response.json", {"http_status": result.status, "body": result.body})
if result.status != 200:
raise AdmissionError(result.status, str(result.body.get("error") or "ADMISSION_HTTP_ERROR"))
return dict(result.body)
def create_envelope(self, value):
return self.call("POST", "/v2/task-admission/envelope", {**value, "expires_in_seconds": 900})
def authorize_and_admit(self, value):
return self.call("POST", "/v2/task-admission/authorize", value)
def reconcile_status(self, envelope_id, _authorization):
return self.call("GET", "/v2/task-admission/status?envelope_id=" + urllib.parse.quote(envelope_id, safe=""))
authority = owner.policy
policy = OwnerAuthorizationPolicy(authorization_id=authority["canonical_payload_digest"],
subject=AGENT, repository="aios/aios", allowed_branch_prefixes=(BRANCH,),
allowed_capabilities=CAPS, risk_ceiling="engineering", trust_domain=authority["trust_domain"],
revocation_epoch=0, not_before=authority["not_before"], expires_at=authority["expires_at"])
live_config = LiveProtocolConfig(control_token_file=activation["task_control_token_file"])
writer = None
def snapshot():
task = writer.agent.read_task(TASK).raw
execution = task.get("metadata", {}).get("execution")
if not isinstance(execution, dict):
raise ValueError("AUTHORITATIVE_TASK_EXECUTION_MISSING")
expected = {"repository": "aios/aios", "target_branch": BRANCH,
"generation": 1, "execution_digest": PLAN_SHA}
if task.get("task_id") != TASK or any(execution.get(k) != v for k, v in expected.items()):
raise ValueError("AUTHORITATIVE_TASK_BINDING_MISMATCH")
execution = dict(execution)
jobs = {execution[k] for k in ("job_id", "external_job_id") if execution.get(k)}
if len(jobs) != 1 or any(not isinstance(job, str) for job in jobs):
raise ValueError("AUTHORITATIVE_JOB_ID_MISSING_OR_CONFLICTING")
execution["external_job_id"] = jobs.pop()
return task, execution
def enroll(**kwargs):
task, execution = snapshot()
if execution.get("external_job_id") != kwargs["job_id"] or task.get("claimed_by") != AGENT:
raise ValueError("CLAIMED_TASK_JOB_BINDING_MISMATCH")
trust = json.loads(Path("/run/credentials/aios-task-principal-issuer.service/task_principal_trust_manifest").read_bytes())
receipt = C5ScopeEnrollmentIssuer(service, config).issue(task=task, execution=execution,
trust=trust, now=int(time.time()), ttl_seconds=3600)
save("principal-enrollment-receipt.json", receipt)
return receipt
writer = LiveGovernedWriterExecutor(config=live_config, owner_policy=policy,
authorization_ref=policy.authorization_id, agent_id=AGENT, scope_enrollment=enroll)
def binding(**_kwargs):
task, execution = snapshot()
job = execution.get("external_job_id")
if not isinstance(job, str) or not job:
raise ValueError("AUTHORITATIVE_JOB_ID_MISSING")
# The base is the original canonical plan, independently checked
# against authoritative main and the admitted receipt by the client.
return {"task_id": TASK, "authority_base_oid": plan["authority_base_oid"],
"target_branch": execution["target_branch"], "generation": execution["generation"],
"execution_digest": execution["execution_digest"], "job_id": job}
def readback(*, session, commit_result, **_kwargs):
response = dict(commit_result["commit_response"])
save("governed-commit-response.json", response)
receipt = response.get("receipt")
if not isinstance(receipt, dict) or response.get("state") != "COMPLETED":
raise ValueError("GOVERNED_COMPLETION_RECEIPT_REQUIRED")
oid = git("rev-parse", "refs/heads/" + BRANCH, authority=True).decode().strip()
tree = git("rev-parse", oid + "^{tree}", authority=True).decode().strip()
parent = git("rev-parse", oid + "^", authority=True).decode().strip()
if (response.get("job_id") != session.job_id or tree != TREE or parent != BASE
or any(receipt.get(k) != oid for k in ("commit_oid", "authority_oid", "git_remote_oid", "api_branch_oid"))
or receipt.get("remote_verified") is not True or receipt.get("commit_visible") is not True):
raise ValueError("GOVERNED_AUTHORITY_REMOTE_READBACK_MISMATCH")
current = binding()
principals = writer.principals.public_receipts
if {x["operation"] for x in principals} != {"acquireWriterLease", "commitFilesGoverned"}:
raise ValueError("PRINCIPAL_OPERATION_RECEIPTS_REQUIRED")
leases = dict(session.task_lease_receipt)
if leases.get("task_id") != TASK:
raise ValueError("SERVER_CLAIM_TASK_ID_REQUIRED")
return {**current, "authenticated": True, "source_oid": oid, "tree_oid": tree,
"parent": parent, "commit_oid": oid, "commit_receipt": receipt,
"task_lease": leases, "writer_lease": dict(session.writer_lease_receipt),
"task_principal": {"task_id": TASK, "operations": principals},
"commit_response": response, "readback_sha256": commit_result["readback_sha256"]}
client = GovernedPublicationClient(writer_executor=writer,
git_tree_file=lambda *, tree_oid, path: git("show", tree_oid + ":" + path),
admission_binding=binding, authenticated_readback=readback, subject=AGENT,
manifest_id="c5-source-" + PLAN_SHA, runtime_fingerprint=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
process_start_identity="pid:" + str(os.getpid()) + ":" + Path("/proc/self/stat").read_text().split()[21],
commit_message="V2R2C5 governed source publication")
runner = GovernedUnattendedReleaseRunner(admission=Admission(), publication_adapter=client,
environment_mode="AUTHORITY", deadline_seconds=90, max_attempts=2)
admitted = runner.admit(intent, {"authorization_kind": "delegation_epoch",
"delegation_epoch_id": owner.epoch["epoch_id"]})
save("admission-result.json", admitted)
if admitted.get("phase") not in {"ADMITTED", "RECONCILED"}:
return admitted
save("publication-started.json", {"task_id": TASK, "status": "EFFECT_NOT_YET_KNOWN"})
result = runner.publish(intent, admitted["receipt"])
save("publication-result.json", result)
return result
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--candidate", type=Path, required=True)
parser.add_argument("--installation", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
result = run(args.candidate, args.installation, args.output)
print(json.dumps(result, sort_keys=True))
raise SystemExit(0 if result.get("phase") == "PUBLISHED" else 2)
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env python3
"""Exercise committed C5 -> real signed admission -> non-authoritative RC material.
Linux/POSIX only, matching the production modules' secure-file/locking contracts.
No network transport, host service, production path or live credential is used.
The exported RC stays BUILDING until real trusted-provider acceptance exists.
"""
from __future__ import annotations
import argparse
import hashlib
import io
import json
import os
from pathlib import Path
import subprocess
import sys
import tarfile
import tempfile
import time
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "active-runtime"))
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from aios_agent_task_protocol import TaskStore, TaskProtocolError
from aios_task_admission_bootstrap import canonical_json_bytes, sha256_json
from aios_task_admission_live_adapters import OfficialTaskHTTPError
from silent_release_controller import ReleaseCandidate, ReleaseCandidateStore
CAPS = (
"agent-task:control", "governed.task.create", "governed.write.files",
"task.principal.issue", "writer.lease",
)
def git(repository: Path, *args: str) -> bytes:
return subprocess.run(
["git", "-C", str(repository), *args], check=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=90,
).stdout
def write_json(path: Path, value: dict) -> str:
data = canonical_json_bytes(value)
path.write_bytes(data)
return hashlib.sha256(data).hexdigest()
class IsolatedTaskTransport:
"""The real TaskStore API, confined to this simulation's temporary directory."""
def __init__(self, path: Path):
self.store = TaskStore(path)
self.creates = 0
def request(self, method, path, body=None):
if method == "GET" and path.startswith("/v1/tasks/"):
try:
return self.store.get_task(path.rsplit("/", 1)[-1])
except TaskProtocolError as exc:
raise OfficialTaskHTTPError(exc.http_status) from exc
if method == "POST" and path == "/v1/tasks":
self.creates += 1
return self.store.create_task(**dict(body))
raise ValueError("SIMULATION_ROUTE_DENIED")
def exercise_admission(tmp: Path, output: Path, source: dict, execution: dict) -> dict:
# The integration implementation is deliberately below the CLI: no signer
# object or private key is ever returned to the release runner or exported.
from c5_unattended_simulation_support import signed_admission
return signed_admission(tmp, output, source, execution, IsolatedTaskTransport)
def run(repository: Path, source_ref: str, base_ref: str, output: Path) -> dict:
if os.name != "posix":
raise RuntimeError("POSIX simulation environment required")
repository = repository.resolve(strict=True)
source_oid = git(repository, "rev-parse", "--verify", source_ref + "^{commit}").decode().strip()
base_oid = git(repository, "rev-parse", "--verify", base_ref + "^{commit}").decode().strip()
tree_oid = git(repository, "rev-parse", source_oid + "^{tree}").decode().strip()
git(repository, "merge-base", "--is-ancestor", base_oid, source_oid)
files = sorted(git(repository, "diff", "--name-only", "-z", base_oid, source_oid).decode().strip("\0").split("\0"))
if not files or files == [""]:
raise ValueError("CANDIDATE_DIFF_REQUIRED")
output = output.resolve()
output.mkdir(parents=True, exist_ok=False)
archive = git(repository, "archive", "--format=tar", source_oid)
artifact_sha = hashlib.sha256(archive).hexdigest()
(output / "source.tar").write_bytes(archive)
source = {
"source_commit": source_oid, "tree_oid": tree_oid,
"authority_base_oid": base_oid, "repository": "aios/aios",
"target_branch": "agent/v2r2c5-source-publication-20260926",
"generation": 1, "file_allowlist": files, "risk": "engineering",
"requested_capabilities": list(CAPS),
}
execution = {
"schema": "aios.source-publication-simulation-plan.v1",
"environment": "SIMULATION", "production_impact": "none",
"source": source, "artifact_sha256": artifact_sha,
}
execution_digest = write_json(output / "EXECUTION_PLAN.simulation.json", execution)
source["execution_digest"] = execution_digest
# Copy, not alias the plan's source before the resulting digest is attached.
execution = json.loads((output / "EXECUTION_PLAN.simulation.json").read_text())
with tempfile.TemporaryDirectory(prefix="aios-c5-unattended-simulation-") as temp:
tmp = Path(temp)
candidate_root = tmp / "candidate"
candidate_root.mkdir()
with tarfile.open(fileobj=io.BytesIO(archive), mode="r:") as tar:
tar.extractall(candidate_root, filter="data")
command = [sys.executable, "-m", "pytest", "-q", "tests/test_v2r2c5_capability_contract.py"]
started = time.monotonic()
test = subprocess.run(
command, cwd=candidate_root, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, timeout=120,
env={**os.environ, "PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1"},
)
(output / "candidate-tests.log").write_bytes(test.stdout)
test_evidence = {
"schema": "aios.simulation-test-evidence.v1", "environment": "SIMULATION",
"source_commit": source_oid, "tree_oid": tree_oid,
"artifact_sha256": artifact_sha, "command": command,
"returncode": test.returncode, "passed": test.returncode == 0,
"duration_seconds": round(time.monotonic() - started, 3),
"log_sha256": hashlib.sha256(test.stdout).hexdigest(),
"scope": "C5 capability contract N01/N02/N03, isolated committed archive",
}
test_sha = write_json(output / "TEST_EVIDENCE_C5.simulation.json", test_evidence)
if test.returncode:
raise RuntimeError("CANDIDATE_TESTS_FAILED")
admission = exercise_admission(tmp, output, source, execution)
manifest = {
"schema": "aios.c5-simulation-artifact-manifest.v1",
"environment": "SIMULATION", "official_release": False,
"source_commit": source_oid, "tree_oid": tree_oid,
"artifact_sha256": artifact_sha, "test_evidence_sha256": test_sha,
"execution_digest": execution_digest,
"admission_receipt_sha256": admission["receipt_sha256"],
"policy_receipt_sha256": admission["policy_receipt_sha256"],
"epoch_payload_sha256": admission["epoch_payload_sha256"],
}
manifest_sha = write_json(output / "BUILD_EVIDENCE_C5.simulation.json", manifest)
candidate = ReleaseCandidate(
candidate_id="rc-simulation-v2r2c5-" + source_oid[:12],
source_commit=source_oid, tree_oid=tree_oid,
artifact_sha256=artifact_sha, manifest_sha256=manifest_sha,
tests=test_evidence,
)
ReleaseCandidateStore(output / "candidates").save(candidate)
result = {
"schema": "aios.c5-unattended-release-simulation.v1",
"environment": "SIMULATION", "official_release": False,
"authority_source_commit": None, "production_mutations": 0,
"source_commit": source_oid, "tree_oid": tree_oid,
"candidate": candidate.to_dict(), "manifest_sha256": manifest_sha,
"admission": admission,
"acceptance": "PENDING_AUTHENTICATED_PRODUCTION_PROVIDER_EVIDENCE",
"approval": "NOT_REQUESTED", "deployment": "NOT_ATTEMPTED",
"completed_path": ["COMMITTED_SOURCE", "REAL_SIGNATURE_VERIFIED", "ISOLATED_TASK_ADMITTED", "RC_MATERIAL_WRITTEN"],
}
write_json(output / "V2R2C5_RC.simulation.json", result)
hashes = {str(path.relative_to(output)): hashlib.sha256(path.read_bytes()).hexdigest()
for path in sorted(output.rglob("*")) if path.is_file()}
write_json(output / "SHA256_MANIFEST.json", hashes)
return result
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repository", type=Path, required=True)
parser.add_argument("--source", default="536a2af0f07962495c88526061ce43fb27994c15")
parser.add_argument("--base", default="e5f8209f57b43fc39e55c4555776e6362ce326eb")
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
result = run(args.repository, args.source, args.base, args.output)
print(json.dumps({key: result[key] for key in (
"environment", "official_release", "source_commit", "authority_source_commit",
"acceptance", "deployment", "completed_path",
)}, indent=2))
+151
View File
@@ -0,0 +1,151 @@
"""Offline factory check against hash-pinned live overlays and ephemeral keys.
Run as root in an isolated Linux test directory. No live API or host credential
is accessed; the production factory is composed but no admission is submitted.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import sys
import tempfile
def run(overlay: Path, application: Path, existing_paths: list[str]) -> dict:
# Match the installed precedence: narrow overlay, existing live modules,
# then test-only source dependencies. Never shadow the live authority module.
sys.path[:0] = [str(overlay), *existing_paths, str(application)]
import importlib
authority_module = importlib.import_module(
"c5_source_authority" if (overlay / "c5_source_authority.py").is_file()
else "aios_single_host_authority")
(LocalOwnerAuthority, OwnerEpochRequest, SingleHostOwnerPolicy,
SourcePublicationPolicyRequest, bootstrap_key, canonical_json_bytes,
write_signed_epoch_directory, write_signer_registry) = (
getattr(authority_module, name) for name in (
"LocalOwnerAuthority", "OwnerEpochRequest", "SingleHostOwnerPolicy",
"SourcePublicationPolicyRequest", "bootstrap_key", "canonical_json_bytes",
"write_signed_epoch_directory", "write_signer_registry"))
import aios_task_admission_live_adapters as adapters
from source_publication_runtime import build_source_runtime_from_activation_file
if os.geteuid() != 0:
raise RuntimeError("ISOLATED_ROOT_TEST_REQUIRED")
if Path(adapters.__file__).parent.resolve() != overlay.resolve():
raise RuntimeError("LIVE_OVERLAY_NOT_LOADED")
caps = frozenset(("agent-task:control", "governed.task.create",
"governed.write.files", "task.principal.issue", "writer.lease"))
with tempfile.TemporaryDirectory(prefix="c5-overlay-factory-") as temp:
root = Path(temp)
def write(path, value):
path.write_bytes(canonical_json_bytes(value))
path.chmod(0o600)
policy = SingleHostOwnerPolicy(
repository="aios/aios", trust_domain="aios.owner",
allowed_branch_prefixes=("agent/c5-factory-test",),
allowed_capabilities=caps, allowed_file_prefixes=("test.py",),
risk_ceiling="engineering", production_scope="none",
destructive_policy="forbidden", maximum_epoch_ttl_seconds=3600,
maximum_machine_capability_ttl_seconds=300, allowed_caller_uids=(0,),
)
owner_path = root / "owner.pem"
owner_pub = bootstrap_key(owner_path)
machine_pub = bootstrap_key(root / "machine.pem")
registry = root / "registry.json"
write_signer_registry(registry, owner_public_key=owner_pub,
machine_public_key=machine_pub, authority_generation=1)
authority = LocalOwnerAuthority(owner_path, policy)
epoch = authority.issue_epoch(OwnerEpochRequest(
epoch_id="c5-factory-test-epoch", allowed_branch_prefixes=policy.allowed_branch_prefixes,
allowed_capabilities=caps, allowed_file_prefixes=("test.py",),
risk_ceiling="engineering", business_goal="Isolated factory test",
production_scope="none", destructive_policy="forbidden", ttl_seconds=600,
))
epoch_root = root / "epoch"
write_signed_epoch_directory(epoch_root, epoch)
plan = {"schema": "aios.task-admission-execution-plan.v3", "task_id": "C5-FACTORY-TEST",
"repository": "aios/aios", "authority_base_oid": "a" * 40,
"target_branch": "agent/c5-factory-test", "generation": 1,
"risk": "engineering", "capabilities": sorted(caps),
"file_allowlist": ["test.py"], "file_sha256": {"test.py": "c" * 64}}
digest = hashlib.sha256(canonical_json_bytes(plan)).hexdigest()
plan_dir = root / "plans"
plan_dir.mkdir(mode=0o700)
write(plan_dir / (digest + ".json"), plan)
artifact = authority.issue_source_publication_policy(SourcePublicationPolicyRequest(
policy_id="C5_SOURCE_PUBLICATION_ONLY", repository="aios/aios",
authority_base_oid="a" * 40, source_oid="b" * 40, source_tree_oid="c" * 40,
target_agent="agent/c5-factory-test", target_branch="agent/c5-factory-test",
generation=1, execution_digest=digest, exact_files=("test.py",),
nonce="isolated-factory-test-nonce-20260926", ttl_seconds=600,
))
write(root / "policy.json", {"payload": dict(artifact.payload),
"signature": artifact.signature, "key_id": artifact.key_id, "namespace": artifact.namespace})
write(root / "epoch-revocations.json", {"schema": "aios.owner-delegation-revocations.v1", "revoked_epoch_ids": []})
write(root / "policy-revocations.json", {"schema": "aios.source-publication-revocations.v1", "revoked_nonces": []})
verifier_path = root / "verifier.py"
verifier_path.write_text("from pathlib import Path\nfrom " + authority_module.__name__ + " import RegistryOwnerApprovalVerifier\n"
"class GrantRootOwnerApprovalVerifier(RegistryOwnerApprovalVerifier):\n"
f" def __init__(self): super().__init__(Path({str(registry)!r}))\n")
verifier_path.chmod(0o600)
(root / "test-token").write_text("ISOLATED-UNUSED-TEST-TOKEN")
(root / "test-token").chmod(0o600)
activation = {
"schema": "aios.task-admission-host-activation.v1", "epoch_root": str(epoch_root),
"epoch_id": epoch.payload["epoch_id"], "revocation_file": str(root / "epoch-revocations.json"),
"execution_plan_dir": str(plan_dir), "state_db": str(root / "admission.sqlite3"),
"task_api_url": "http://127.0.0.1:18940", "task_control_token_file": str(root / "test-token"),
"owner_verifier_module": str(verifier_path),
}
write(root / "admission.json", activation)
write(root / "ordinary.json", {**activation, "epoch_id": "absent-expired-old-epoch"})
write(root / "source.json", {
"schema": "aios.source-publication-host-activation.v1", "policy_artifact": str(root / "policy.json"),
"policy_sha256": hashlib.sha256((root / "policy.json").read_bytes()).hexdigest(),
"policy_revocations": str(root / "policy-revocations.json"),
"admission_activation": str(root / "admission.json"), "expected_agent_id": "agent/c5-factory-test",
})
class OfflineRuntime:
def project_admitted_task(self, *_args):
raise AssertionError("NO_ADMISSION_IN_FACTORY_TEST")
runtime = OfflineRuntime()
ordinary = adapters.build_task_admission_handlers_from_activation_file(
root / "ordinary.json", post_admission=runtime.project_admitted_task)
assert ordinary
assert adapters.task_admission_policy_from_epoch().max_risk == "medium"
source = build_source_runtime_from_activation_file(root / "source.json",
post_admission=runtime.project_admitted_task, ordinary_activation_file=root / "ordinary.json")
assert source.service.bootstrap.policy.max_risk == "engineering"
assert source.service.expected_task_id == plan["task_id"]
assert source.service.expected_file_sha256 == plan["file_sha256"]
assert source.service.bootstrap.policy.normalized_policy_digest() == policy.digest
assert adapters.task_admission_policy_from_epoch().max_risk == "medium"
# A source configuration may not fork the existing admission store.
write(root / "admission.json", {**activation, "state_db": str(root / "other.sqlite3")})
try:
build_source_runtime_from_activation_file(root / "source.json",
post_admission=runtime.project_admitted_task, ordinary_activation_file=root / "ordinary.json")
except ValueError as exc:
assert str(exc) == "SOURCE_ACTIVATION_CONTROL_PLANE_BINDING_MISMATCH"
else:
raise AssertionError("CONTROL_PLANE_FORK_ACCEPTED")
return {"environment": "ISOLATED_FACTORY_TEST", "result": "PASS",
"ordinary_risk": "medium", "source_risk": "engineering",
"live_admission": "NOT_ATTEMPTED", "host_mutation": "NONE"}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--overlay", required=True, type=Path)
parser.add_argument("--application", required=True, type=Path)
parser.add_argument("--identities", required=True, type=Path)
args = parser.parse_args()
paths = json.loads(args.identities.read_text())["pythonpaths"]
print(json.dumps(run(args.overlay, args.application, paths), indent=2))
+46
View File
@@ -66,6 +66,52 @@ def test_real_local_task_goes_through_claim_job_receipt_and_lineage(tmp_path):
assert service.claim_next(WORKER) is None
def _completed_task_evidence(tmp_path):
import json
from task_plane.models import EvidenceEnvelope
service=fixture_service(tmp_path)
service.submit_goal(USER,REQUEST,idempotency_key='evidence-proof')
service.register_worker(WORKER)
claim=service.claim_next(WORKER)
service.execute_claim(WORKER,claim['task_id'],claim['claim_id'])
row=service.store.list_evidence(claim['task_id'])[0]
return service,EvidenceEnvelope(**json.loads(row['envelope_json']))
def test_closed_task_evidence_has_verified_machine_proof(tmp_path):
service,evidence=_completed_task_evidence(tmp_path)
assert evidence.signature
assert evidence.attestation_ref=='baseline-machine:baseline-machine'
assert service._verify_task_evidence(evidence)
assert service.store.verify_evidence_integrity(evidence.task_id)
@pytest.mark.parametrize('field,value',[
('signature',None),('signature','invalid-base64!'),
('attestation_ref','baseline-machine:untrusted'),
('task_id','task-foreign'),('claim_id','claim-foreign'),
('worker_identity','worker-foreign'),('previous_evidence_hash','0'*64),
('kind','invented'),
])
def test_task_evidence_rejects_missing_or_tampered_proof(tmp_path,field,value):
from dataclasses import replace
service,evidence=_completed_task_evidence(tmp_path)
assert not service._verify_task_evidence(replace(evidence,**{field:value}))
def test_signed_wrapper_does_not_bless_invalid_operation_receipt(tmp_path):
import base64
from dataclasses import replace
from aios_baseline.evidence import canonical
service,evidence=_completed_task_evidence(tmp_path)
payload={**evidence.payload,'signature':'invalid-base64!'}
evidence=replace(evidence,payload=payload)
proof=canonical({'domain':'aios.baseline.task-evidence.v1',
'key_id':service.signer.key_id,'evidence':evidence.digest_payload()})
evidence=replace(evidence,signature=base64.b64encode(service.signer.private_key.sign(proof)).decode())
assert not service._verify_task_evidence(evidence)
def test_spoofed_tenant_cross_subject_and_worker_results_are_denied(tmp_path):
service=fixture_service(tmp_path)
with pytest.raises(ServiceError): service.submit_goal(USER,{**REQUEST,'tenant_id':'tenant-b'},idempotency_key='a')
@@ -14,7 +14,7 @@ from app.task_plane.adapters.openclaw import OpenClawAdapter
from app.task_plane.api import TaskPlaneRequestHandler, TaskPlaneService
from app.task_plane.errors import ConflictError, LockedError, ValidationError
from app.task_plane.events import DurableOutboxDispatcher, WeChatGatewayAdapter
from app.task_plane.models import ProbeEvidence, ProbeOutcome, TaskPlan
from app.task_plane.models import EvidenceEnvelope, ProbeEvidence, ProbeOutcome, TaskPlan
from app.task_plane.policy import ALL_ACTION_CAPABILITIES, PolicyEngine
from app.task_plane.store import TaskPlaneStore
@@ -81,9 +81,19 @@ class DurableStoreTests(unittest.TestCase):
store = TaskPlaneStore(":memory:")
service = TaskPlaneService(store)
task = service.create_task({"task_id": "task-tamper"})
evidence_id = store.add_evidence(
task["task_id"], "test-report", {"passed": True}, IDENTITY
store.evidence_attestation_verifier = lambda envelope: envelope.attestation_ref == (
"trusted://test-report"
)
envelope = EvidenceEnvelope.create(
task_id=task["task_id"],
claim_id="claim-unbound-shadow",
kind="test-report",
worker_identity=IDENTITY,
payload={"passed": True},
previous_evidence_hash="",
attestation_ref="trusted://test-report",
)
evidence_id = store.add_evidence_envelope(envelope)
store.conn.execute(
"UPDATE events SET event_data=? WHERE task_id=?",
(json.dumps({"changed": True}), task["task_id"]),
@@ -98,7 +108,7 @@ class DurableStoreTests(unittest.TestCase):
def test_secrets_are_redacted_at_rest_and_in_outbox(self):
store = TaskPlaneStore(":memory:")
service = TaskPlaneService(store)
secret = "never-store-this-value"
secret = ('never-store' + '-this-value')
task = service.create_task(
{
"task_id": "task-redaction",
@@ -137,6 +137,8 @@ class EvidenceEnvelopeContractTests(unittest.TestCase):
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
attestation_verifier=lambda envelope: envelope.attestation_ref
== "trusted://shadow/test-report",
)
)
mutations = {
@@ -155,11 +157,77 @@ class EvidenceEnvelopeContractTests(unittest.TestCase):
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
attestation_verifier=lambda envelope: envelope.attestation_ref
== "trusted://shadow/test-report",
)
)
def test_unsigned_and_unverifiable_signed_evidence_are_rejected(self):
unsigned = EvidenceEnvelope.create(
task_id="task-evidence",
claim_id="claim-evidence",
kind="test-report",
worker_identity=WORKER_IDENTITY,
payload={"passed": True},
previous_evidence_hash="",
)
self.assertFalse(
unsigned.verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
)
)
signed = dataclasses.replace(unsigned, signature="signed-proof")
self.assertFalse(
signed.verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
)
)
reference_only = dataclasses.replace(
unsigned, attestation_ref="trusted://reference-only"
)
self.assertFalse(
reference_only.verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
)
)
self.assertFalse(
reference_only.verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
attestation_verifier=lambda _envelope: False,
)
)
self.assertTrue(
signed.verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
attestation_verifier=lambda envelope: envelope.signature == "signed-proof",
)
)
self.assertFalse(
signed.verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
attestation_verifier=lambda _envelope: (_ for _ in ()).throw(
RuntimeError("verifier unavailable")
),
)
)
def test_store_rejects_cross_task_required_evidence_and_chain_tamper(self):
store = TaskPlaneStore(":memory:")
store.evidence_attestation_verifier = lambda envelope: envelope.attestation_ref == (
"trusted://shadow/test-report"
)
store.create_task_with_event(
TaskPlan.from_dict({"task_id": "task-evidence"})
)
@@ -218,7 +286,10 @@ class AtomicMutationContractTests(unittest.TestCase):
self.assertEqual([], store.list_children(parent.task_id))
def test_complete_fail_and_recover_roll_back_wholly(self):
service = _service()
service = _service(
evidence_attestation_verifier=lambda envelope: envelope.attestation_ref
== "trusted://shadow/test"
)
task, claim = _claim_for_service(service, task_id="task-complete-atomic")
service.progress(
task["task_id"],
@@ -252,6 +323,46 @@ class AtomicMutationContractTests(unittest.TestCase):
self.assertEqual([], service.store.list_evidence(task["task_id"]))
self.assertEqual("RUNNING", service.store.get_task(task["task_id"]).status)
def test_unverifiable_evidence_cannot_complete_claim_or_leave_partial_state(self):
def raising_verifier(_envelope):
raise RuntimeError("verifier unavailable")
cases = (
{},
{"evidence_attestation_verifier": lambda _envelope: False},
{"evidence_attestation_verifier": raising_verifier},
)
for index, policy_kwargs in enumerate(cases):
with self.subTest(case=index):
service = _service(**policy_kwargs)
task, claim = _claim_for_service(
service, task_id=f"task-unverifiable-{index}"
)
envelope = EvidenceEnvelope.create(
task_id=task["task_id"],
claim_id=claim["claim_id"],
kind="test-report",
worker_identity=WORKER_IDENTITY,
payload={"passed": True},
previous_evidence_hash="",
attestation_ref="trusted://unverifiable",
)
with self.assertRaises(ConflictError):
service.store.complete_claim_with_evidence_and_event(
task["task_id"],
claim["claim_id"],
"COMPLETED",
{"status": "COMPLETED"},
[envelope],
)
self.assertEqual("CLAIMED", service.store.get_task(task["task_id"]).status)
self.assertEqual("ACTIVE", service.store.get_claim(claim["claim_id"]).status)
self.assertEqual([], service.store.list_evidence(task["task_id"]))
self.assertNotIn(
"TASK_COMPLETED",
{event["type"] for event in service.store.list_events(task["task_id"])},
)
def test_fail_rolls_back_claim_state_and_event(self):
service = _service()
task, claim = _claim_for_service(service, task_id="task-fail-atomic")
@@ -867,6 +978,16 @@ class ExplicitSecurityMatrixTests(unittest.TestCase):
with self.assertRaises(ForbiddenError):
service.get_task("task-rbac", "spiffe://reader/none")
def test_unknown_action_is_denied_even_for_trusted_identity(self):
policy = PolicyEngine(
identity_capabilities={
"spiffe://trusted/worker": set(ALL_ACTION_CAPABILITIES)
}
)
decision = policy.authorize("future.unregistered-action", "spiffe://trusted/worker")
self.assertFalse(decision.allowed)
self.assertEqual("unknown action future.unregistered-action", decision.reason)
def test_default_service_policy_denies_authenticated_read(self):
service = TaskPlaneService(TaskPlaneStore(":memory:"))
service.create_task({"task_id": "task-default-deny"})
@@ -25,6 +25,7 @@ class RecordingTransport:
def __init__(self):
self.calls=[]
self.task_reads=0
self.submitted_result=None
def request(self,method,url,*,body=None,headers=None,timeout=20.0):
self.calls.append({
"method":method,"url":url,"body":body or {},
@@ -62,16 +63,30 @@ class RecordingTransport:
})
if method=="POST" and path.endswith("/heartbeat"):
return HttpResult(200,{"status":"RUNNING"})
if method=="POST" and path.endswith("/result"):
self.submitted_result=dict((body or {})["result"])
return HttpResult(200,{
"task_id":"task-live-1", "status":"VERIFYING",
"claimed_by":"agent-live", "revision":8,
"result":dict(self.submitted_result),
})
if method=="POST" and path.endswith(":18946/v1/task-principal/assertions"):
operation=(body or {}).get("operation")
return HttpResult(201,{"assertion":f"assertion-for-{operation}"})
if method=="POST" and path.endswith(":18892/v2/writer-lease"):
return HttpResult(201,{
return HttpResult(200,{
"schema":"aios.task-writer-lease.v2",
"task_id":"task-live-1",
"lease_id":"writer-lease-live-1",
"repository":(body or {}).get("repository"),
"branch_mode":"CONTINUE_EXISTING",
"base_oid":(body or {}).get("base_oid"),
"expected_branch_oid":(body or {}).get("expected_branch_oid"),
"expires_at":time.time()+900,
"target_branch":(body or {}).get("target_branch"),
"generation":(body or {}).get("generation"),
"lease_seconds":(body or {}).get("lease_seconds"),
"create_target_branch":False,
"worktree_mode":"detached_at_expected_head",
"cas_ref_update_required":True,
})
if method=="POST" and path.endswith(":18892/v2/commit-files"):
return HttpResult(201,{
@@ -161,6 +176,7 @@ class LiveAgentTaskContractTests(unittest.TestCase):
)
self.assertEqual(claim.claim_ref,"claim-live-1")
self.assertEqual(claim.fencing_generation,9)
self.assertNotIn("lease_token", claim.public_receipt)
running=client.heartbeat(
claim=claim,agent_token_handle=agent_handle,phase="test"
)
@@ -185,6 +201,9 @@ class LiveAgentTaskContractTests(unittest.TestCase):
request_id="req-1",
)
self.assertEqual(assertion,"assertion-for-acquireWriterLease")
self.assertEqual(principal.public_receipts[0]["assertion_sha256"],
hashlib.sha256(assertion.encode()).hexdigest())
self.assertNotIn(assertion, str(principal.public_receipts))
call=self.transport.calls[-1]
self.assertEqual(call["body"]["schema"],"aios.task-principal-issue.v1")
self.assertEqual(call["body"]["operation"],"acquireWriterLease")
@@ -234,6 +253,9 @@ class LiveBridgeContinuationTests(unittest.TestCase):
writer_lease_id=wl.writer_lease_id,
continuation_key=wl.continuation_key,
principal_issue_schema=self.cfg.issue_schema,
agent_token_handle=self.secrets.put(
"agent-token", "agent-token-secret-12345"
),
lease_handle=self.lease_handle,
)
self.bridge.commit_files(
@@ -254,6 +276,43 @@ class LiveBridgeContinuationTests(unittest.TestCase):
]
self.assertEqual(issue_ops,["acquireWriterLease","commitFilesGoverned"])
def test_binding_projection_is_not_presented_as_durable_writer_lease(self):
writer = self.bridge.acquire_writer_lease(
task_id="task-live-1",
job_id="job-live-1",
lease_handle=self.lease_handle,
repository="aios/aios",
target_branch="agent/autonomy-live-protocol",
generation=1,
base_oid="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
expected_branch_oid="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
branch_mode="CREATE_NEW",
continuation_key="gw-cont-projection",
)
self.assertIsNone(writer.writer_lease_id)
self.assertIsNone(writer.expires_at)
self.assertEqual(writer.branch_mode, "CONTINUE_EXISTING")
from app.task_plane.live_governed_writer_adapters import LiveWriterSession
session = LiveWriterSession(
authorization_id="azn-owner-gw-v1",
task_id="task-live-1",
agent_id="agent-live",
claim_ref="claim-live-1",
job_id="job-live-1",
branch_mode=writer.branch_mode,
expected_branch_oid=writer.expected_branch_oid,
writer_lease_id=writer.writer_lease_id,
continuation_key=writer.continuation_key,
principal_issue_schema=self.cfg.issue_schema,
agent_token_handle=self.secrets.put("agent-token", "agent-token-secret-12345"),
lease_handle=self.lease_handle,
writer_lease_binding=writer.raw,
)
public = session.public_dict()
self.assertNotIn("writer_lease_id", public)
self.assertEqual(public["writer_lease_binding"]["generation"], 1)
class LiveExecutorTests(unittest.TestCase):
def setUp(self):
@@ -274,12 +333,50 @@ class LiveExecutorTests(unittest.TestCase):
)
ex.git=FakeGit(head=None)
session=ex.acquire(request=request(),job_id="job-live-1")
self.assertEqual(session.branch_mode,"CREATE_NEW")
self.assertEqual(session.branch_mode,"CONTINUE_EXISTING")
self.assertEqual(
session.expected_branch_oid,
"5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
)
self.assertEqual(session.writer_lease_id,"writer-lease-live-1")
self.assertIsNone(session.writer_lease_id)
self.assertNotIn("writer_lease_id", session.public_dict())
self.assertEqual(
session.public_dict()["writer_lease_binding"]["target_branch"],
request().target_branch,
)
def test_scope_enrollment_occurs_after_claim_before_assertion(self):
calls = []
def enroll(**kwargs):
calls.append(kwargs)
self.assertTrue(any(c["url"].endswith("/claim") for c in self.transport.calls))
self.assertFalse(any(c["url"].endswith("/assertions") for c in self.transport.calls))
return {"status":"ENROLLED", "task_id":"task-live-1",
"agent_id":"c5-fixed-agent", "job_id":"job-live-1"}
ex=LiveGovernedWriterExecutor(config=self.cfg, owner_policy=policy(),
authorization_ref="azn-owner-gw-v1", transport=self.transport,
agent_id="c5-fixed-agent", scope_enrollment=enroll)
ex.git=FakeGit()
session=ex.acquire(request=request(),job_id="job-live-1")
self.assertEqual(session.agent_id,"c5-fixed-agent")
self.assertEqual(len(calls),1)
def test_scope_enrollment_failure_stops_before_assertion(self):
ex=LiveGovernedWriterExecutor(config=self.cfg, owner_policy=policy(),
authorization_ref="azn-owner-gw-v1", transport=self.transport,
agent_id="c5-fixed-agent", scope_enrollment=lambda **_: {"status":"DENIED"})
ex.git=FakeGit()
with self.assertRaises(LiveProtocolConflict):
ex.acquire(request=request(),job_id="job-live-1")
self.assertFalse(any(c["url"].endswith("/assertions") for c in self.transport.calls))
def test_engineering_risk_remains_above_medium_ceiling(self):
from dataclasses import replace
from app.task_plane.governed_writer_automation import AuthorizationDenied
engineering = replace(request(), risk="engineering")
with self.assertRaises(AuthorizationDenied):
policy().validate(engineering)
replace(policy(), risk_ceiling="engineering").validate(engineering)
def test_nonqueued_task_fails_before_agent_registration(self):
class ExpiredTransport(RecordingTransport):
@@ -349,6 +446,32 @@ class LiveExecutorTests(unittest.TestCase):
]
self.assertEqual(commit_calls,[])
def test_result_uses_same_session_without_reregister(self):
ex=LiveGovernedWriterExecutor(
config=self.cfg,owner_policy=policy(),
authorization_ref="azn-owner-gw-v1",transport=self.transport,
agent_id="agent-live",
)
ex.git=FakeGit(head=None)
session=ex.acquire(request=request(),job_id="job-live-1")
result_value={"schema":"aios.governed-source-publication-result.v1",
"task_id":"task-live-1", "published_source_oid":"a"*40}
submission=ex.submit_task_result(
session=session,result=result_value,
idempotency_key="governed-source-result-"+"b"*64,
)
self.assertEqual(submission["status"],"VERIFYING")
result_call=[c for c in self.transport.calls if c["url"].endswith("/result")][0]
self.assertEqual(result_call["body"]["lease_token"],"lease-token-secret-12345")
self.assertEqual(
result_call["headers"]["Authorization"],
"Bearer agent-token-secret-12345",
)
self.assertEqual(
len([c for c in self.transport.calls if c["url"].endswith("/agents/register")]),
1,
)
if __name__=="__main__":
unittest.main()
@@ -192,6 +192,8 @@ def _service(
store,
policy=PolicyEngine(
execution_enabled=True,
evidence_attestation_verifier=lambda envelope: envelope.attestation_ref
== "test-report:root-action",
identity_capabilities={
OWNER: {"task.creator", "task.read", "task.delegate"},
WORKER: {"task.claim", "worker.self_register"},
@@ -296,7 +298,7 @@ def _finish(service: TaskPlaneService, task_id: str):
{
"claim_id": claim["claim_id"],
"actor_identity": WORKER,
"evidence": [{"kind": "test-report", "status": "passed"}],
"evidence": [{"kind": "test-report", "status": "passed", "attestation_ref": "test-report:root-action"}],
},
)
@@ -105,6 +105,8 @@ def _service(db_path: Path, evaluator=_complete_when_proven) -> TaskPlaneService
TaskPlaneStore(str(db_path)),
policy=PolicyEngine(
execution_enabled=True,
evidence_attestation_verifier=lambda envelope: envelope.attestation_ref
== "test-report:lineage",
identity_capabilities=grants,
),
lineage_policy=_lineage_policy(evaluator),
@@ -181,7 +183,7 @@ def _finish_task(service: TaskPlaneService, task_id: str) -> dict:
"claim_id": claim["claim_id"],
"actor_identity": WORKER_IDENTITY,
"outputs": [{"kind": "summary", "status": "passed"}],
"evidence": [{"kind": "test-report", "status": "passed"}],
"evidence": [{"kind": "test-report", "status": "passed", "attestation_ref": "test-report:lineage"}],
},
)
@@ -135,6 +135,8 @@ def _fixture(tmp_path):
policy = PolicyEngine(
identity_verifier=IdentityVerifier({OWNER, OTHER, WORKER}),
execution_enabled=True,
evidence_attestation_verifier=lambda envelope: envelope.attestation_ref
== "bridge-receipt:test",
identity_capabilities={
OWNER: {"task.creator", "task.read", "task.delegate"},
OTHER: {"task.creator", "task.read", "task.delegate"},
+573
View File
@@ -0,0 +1,573 @@
from __future__ import annotations
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import sqlite3
import sys
from types import SimpleNamespace
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))
import c5_governed_principal_scope as scope # noqa: E402
NOW = 2_000_000_000
OWNER_KEY = "SHA256:" + "A" * 43
TRUST = {
"schema": "aios.task-principal-assertion.v1",
"issuer_id": "issuer",
"key_id": "key",
"public_key": "public",
"purpose": "TASK_PRINCIPAL",
"bridge_id": "bridge",
"scope_manifest_sha256": "f" * 64,
}
class StubSignatureVerifier:
key_id = OWNER_KEY
identity = "aios-task-grant-signer"
def __init__(self):
self.calls = 0
def verify(self, payload, signature):
self.calls += 1
return signature == b"valid-signature"
class StubAdapter:
def resolve_row(self, *, task, execution, trust, now):
if task["status"] not in {"CLAIMED", "RUNNING"}:
raise RuntimeError("TASK_NOT_CLAIMED")
return {
"scope_id": "source-scope",
"task_id": scope.TASK_ID,
"external_job_id": execution["external_job_id"],
"repository": scope.REPOSITORY,
"target_branch": scope.TARGET_BRANCH,
"generation": scope.GENERATION,
"execution_digest": scope.EXECUTION_DIGEST,
"capabilities_json": scope.canonical_json(list(scope.SOURCE_CAPABILITIES)).decode(),
"audiences_json": scope.canonical_json([
"aios.gitops.bridge.v2/bridge/acquireWriterLease",
"aios.gitops.bridge.v2/bridge/commitFilesGoverned",
]).decode(),
"trust_manifest_sha256": scope.sha256_json(dict(trust)),
"grant_id": "grant-c5",
"grant_expires_at": NOW + 3000,
}
def _canonical_payload(value):
result = dict(value)
result["canonical_payload_digest"] = scope.sha256_json(result)
return result
def _write(path: Path, value: bytes):
path.write_bytes(value)
if os.name == "posix":
path.chmod(0o600)
def _owner_material(tmp_path: Path, signatures: StubSignatureVerifier):
policy = _canonical_payload({
"schema": scope.POLICY_SCHEMA,
"policy_id": "C5_SOURCE_PUBLICATION_ONLY",
"owner_identity": "aios-task-grant-signer",
"repository": scope.REPOSITORY,
"trust_domain": "aios.owner",
"policy_digest": "1" * 64,
"authority_base_oid": "2" * 40,
"source_oid": "3" * 40,
"source_tree_oid": "4" * 40,
"target_agent": "agent/codex-c5-release",
"target_branch": scope.TARGET_BRANCH,
"generation": scope.GENERATION,
"execution_digest": scope.EXECUTION_DIGEST,
"exact_files": ["active-runtime/app/example.py"],
"capabilities": list(scope.SOURCE_CAPABILITIES),
"risk": "engineering",
"production_impact": "none",
"renewal": "none",
"nonce": "nonce-c5",
"issued_at": NOW - 10,
"not_before": NOW - 10,
"expires_at": NOW + 1000,
"status": "ACTIVE",
"signature_namespace": scope.OWNER_NAMESPACE,
"signature_key_id": OWNER_KEY,
})
epoch = _canonical_payload({
"epoch_id": "epoch-c5",
"owner_identity": "aios-task-grant-signer",
"trust_domain": "aios.owner",
"repository": scope.REPOSITORY,
"allowed_branch_prefixes": [scope.TARGET_BRANCH],
"allowed_capabilities": list(scope.SOURCE_CAPABILITIES),
"allowed_file_prefixes": ["active-runtime/app/"],
"risk_ceiling": "engineering",
"issued_at": NOW - 10,
"not_before": NOW - 10,
"expires_at": NOW + 1000,
"revocation_epoch": 0,
"policy_digest": "1" * 64,
"schema": scope.EPOCH_SCHEMA,
"business_goal": "publish C5 source",
"production_scope": "none",
"destructive_policy": "forbidden",
"signature_namespace": scope.OWNER_NAMESPACE,
"signature_key_id": OWNER_KEY,
"status": "ACTIVE",
})
epoch_revocations = {
"schema": "aios.owner-delegation-revocations.v1",
"revoked_epoch_ids": [],
}
policy_revocations = {
"schema": "aios.source-publication-revocations.v1",
"revoked_nonces": [],
}
epoch_root = tmp_path / "epoch"
epoch_root.mkdir()
paths = {
"policy": tmp_path / "policy.json",
"policy_revocations": tmp_path / "policy-revocations.json",
"epoch_root": epoch_root,
"epoch_revocations": tmp_path / "epoch-revocations.json",
}
_write(paths["policy"], scope.canonical_json({
"payload": policy, "signature": "valid-signature",
"key_id": OWNER_KEY, "namespace": scope.OWNER_NAMESPACE,
}))
_write(
epoch_root / "owner-delegation-epoch.canonical.json",
scope.canonical_json(epoch),
)
_write(epoch_root / "owner-delegation-epoch.sig", b"valid-signature")
_write(epoch_root / "owner-delegation-epoch.key-id", (OWNER_KEY + "\n").encode())
_write(paths["epoch_revocations"], scope.canonical_json(epoch_revocations))
_write(paths["policy_revocations"], scope.canonical_json(policy_revocations))
verifier = scope.FreshOwnerMaterialVerifier(
signature_verifier=signatures,
policy_artifact=paths["policy"],
policy_revocation_file=paths["policy_revocations"],
epoch_root=paths["epoch_root"],
epoch_revocation_file=paths["epoch_revocations"],
)
return verifier, paths
def _service(tmp_path: Path):
signatures = StubSignatureVerifier()
owner, paths = _owner_material(tmp_path, signatures)
store = scope.AppendOnlyScopeEnrollmentStore(tmp_path / "enrollments.sqlite3")
store.initialize()
service = scope.C5ScopeEnrollmentService(
deriver=scope.GovernedScopeDeriver(StubAdapter()),
owner=owner,
store=store,
enrollment_signature_verifier=signatures,
)
return service, signatures, paths, store
def _task(claimed_by="agent/codex-c5-release"):
return {"task_id": scope.TASK_ID, "status": "CLAIMED", "claimed_by": claimed_by}
def _execution():
return {
"external_job_id": "job-c5-real",
"repository": scope.REPOSITORY,
"target_branch": scope.TARGET_BRANCH,
"generation": scope.GENERATION,
"execution_digest": scope.EXECUTION_DIGEST,
}
def test_claim_then_signed_enrollment_then_runtime_resolution(tmp_path):
service, signatures, _paths, store = _service(tmp_path)
contract = service.prepare(
task=_task(), execution=_execution(), trust=TRUST, now=NOW, ttl_seconds=600,
)
assert contract["claimed_by"] == "agent/codex-c5-release"
assert contract["capability_derivation"] == {
"governed.write.files": "governed.write.file",
"writer.lease": "governed.write.branch",
}
assert contract["audiences"] == [
"aios.gitops.bridge.v2/bridge/acquireWriterLease",
"aios.gitops.bridge.v2/bridge/commitFilesGoverned",
]
assert service.enroll(
contract=contract, signature=b"valid-signature", task=_task(),
execution=_execution(), trust=TRUST, now=NOW,
) is False
assert service.enroll(
contract=contract, signature=b"valid-signature", task=_task(),
execution=_execution(), trust=TRUST, now=NOW,
) is True
resolved = service.resolve(
task=_task(), execution=_execution(), trust=TRUST, now=NOW,
)
assert json.loads(resolved["capabilities_json"]) == list(scope.PRINCIPAL_CAPABILITIES)
assert resolved["scope_id"] == contract["scope_id"]
assert signatures.calls >= 9 # policy + epoch + contract are rechecked per use
assert store.get(scope.TASK_ID)[0] == contract
def test_enrollment_requires_claimed_agent_bound_by_owner_policy(tmp_path):
service, _signatures, _paths, _store = _service(tmp_path)
with pytest.raises(scope.C5PrincipalScopeError, match="C5_TASK_CLAIM_IDENTITY_MISMATCH"):
service.prepare(
task=_task("governed-writer-random"), execution=_execution(),
trust=TRUST, now=NOW, ttl_seconds=600,
)
def test_execution_job_alias_is_canonical_and_conflicts_are_denied(tmp_path):
service, _signatures, _paths, _store = _service(tmp_path)
aliased = dict(_execution())
aliased["job_id"] = aliased.pop("external_job_id")
contract = service.prepare(
task=_task(), execution=aliased, trust=TRUST, now=NOW, ttl_seconds=600,
)
assert contract["external_job_id"] == "job-c5-real"
conflicted = dict(aliased, external_job_id="job-other")
with pytest.raises(scope.C5PrincipalScopeError, match="C5_EXECUTION_JOB_ID_INVALID"):
service.prepare(
task=_task(), execution=conflicted, trust=TRUST,
now=NOW, ttl_seconds=600,
)
def test_revocation_is_rechecked_after_append(tmp_path):
service, _signatures, paths, _store = _service(tmp_path)
contract = service.prepare(
task=_task(), execution=_execution(), trust=TRUST, now=NOW, ttl_seconds=600,
)
service.enroll(
contract=contract, signature=b"valid-signature", task=_task(),
execution=_execution(), trust=TRUST, now=NOW,
)
revoked = {
"schema": "aios.owner-delegation-revocations.v1",
"revoked_epoch_ids": ["epoch-c5"],
}
_write(paths["epoch_revocations"], scope.canonical_json(revoked))
with pytest.raises(scope.C5PrincipalScopeError, match="C5_OWNER_EPOCH_REVOKED"):
service.resolve(task=_task(), execution=_execution(), trust=TRUST, now=NOW)
def test_trust_or_execution_drift_cannot_reuse_enrollment(tmp_path):
service, _signatures, _paths, _store = _service(tmp_path)
contract = service.prepare(
task=_task(), execution=_execution(), trust=TRUST, now=NOW, ttl_seconds=600,
)
service.enroll(
contract=contract, signature=b"valid-signature", task=_task(),
execution=_execution(), trust=TRUST, now=NOW,
)
drifted = dict(TRUST, key_id="other")
with pytest.raises(scope.C5PrincipalScopeError, match="C5_ENROLLMENT_SCOPE_INVALID"):
service.resolve(task=_task(), execution=_execution(), trust=drifted, now=NOW)
bad_execution = dict(_execution(), external_job_id="job-other")
with pytest.raises(scope.C5PrincipalScopeError, match="C5_ENROLLMENT_BINDING_MISMATCH"):
service.resolve(task=_task(), execution=bad_execution, trust=TRUST, now=NOW)
def test_append_only_store_rejects_update_and_conflict(tmp_path):
service, _signatures, _paths, store = _service(tmp_path)
contract = service.prepare(
task=_task(), execution=_execution(), trust=TRUST, now=NOW, ttl_seconds=600,
)
service.enroll(
contract=contract, signature=b"valid-signature", task=_task(),
execution=_execution(), trust=TRUST, now=NOW,
)
with sqlite3.connect(store.path) as con:
with pytest.raises(sqlite3.IntegrityError, match="C5_PRINCIPAL_SCOPE_APPEND_ONLY"):
con.execute(
"UPDATE c5_principal_scope_enrollments SET scope_id='other' WHERE task_id=?",
(scope.TASK_ID,),
)
changed = dict(contract, external_job_id="job-other")
changed_without_digest = dict(changed)
changed_without_digest.pop("canonical_payload_digest")
changed["canonical_payload_digest"] = scope.sha256_json(changed_without_digest)
with pytest.raises(scope.C5PrincipalScopeError, match="C5_ENROLLMENT_CONFLICT"):
store.append(changed, b"valid-signature", NOW)
def test_not_yet_enrolled_does_not_break_base_readiness(monkeypatch):
class Base:
trust = TRUST
def readiness(self):
return {"ready": True, "schema_ready": True}
def activation_scope_task_ids(self):
return ("task-c4",)
class Runtime:
def readiness(self, trust, now):
return {"ready": False, "c5_scope_enrolled": False, "error": "C5_ENROLLMENT_REQUIRED"}
monkeypatch.setattr(
scope.RuntimeConfig, "from_environment",
classmethod(lambda cls: SimpleNamespace()),
)
monkeypatch.setattr(scope, "_runtime_service", lambda: Runtime())
composite = scope.wrap_principal_runtime_from_environment(Base(), role="verifier")
readiness = composite.readiness()
assert readiness["ready"] is True
assert readiness["c5_scope_enrolled"] is False
assert readiness["c5_scope_error"] == "C5_ENROLLMENT_REQUIRED"
# The upstream Bridge storage readiness contract is the original static
# C4 singleton. C5 is admitted later through the exact signed enrollment.
assert composite.activation_scope_task_ids() == ("task-c4",)
def test_issuer_readiness_reports_actual_loaded_server_source(monkeypatch, tmp_path):
source = tmp_path / "aios_task_principal_issuer_server.py"
source.write_bytes(b"# exact issuer fixture\n")
class Base:
def readiness(self):
return {"ready": True}
monkeypatch.setitem(
sys.modules, "aios_task_principal_issuer_server",
SimpleNamespace(__file__=str(source)),
)
monkeypatch.setattr(
scope.RuntimeConfig, "from_environment", classmethod(lambda cls: None),
)
status = scope.wrap_principal_runtime_from_environment(
Base(), role="issuer",
).readiness()
assert status["ready"] is True
assert status["source_module_path"] == str(source.resolve())
assert status["source_module_sha256"] == hashlib.sha256(
source.read_bytes()
).hexdigest()
def test_issuer_readiness_accepts_only_exact_python_m_main_module(monkeypatch, tmp_path):
source = tmp_path / "aios_task_principal_issuer_server.py"
source.write_bytes(b"# python -m issuer fixture\n")
class Base:
def readiness(self):
return {"ready": True}
monkeypatch.delitem(sys.modules, "aios_task_principal_issuer_server", raising=False)
main = SimpleNamespace(
__file__=str(source),
__spec__=SimpleNamespace(name="aios_task_principal_issuer_server"),
)
monkeypatch.setitem(sys.modules, "__main__", main)
monkeypatch.setattr(
scope.RuntimeConfig, "from_environment", classmethod(lambda cls: None),
)
composite = scope.wrap_principal_runtime_from_environment(Base(), role="issuer")
status = composite.readiness()
assert status["ready"] is True
assert status["source_module_path"] == str(source.resolve())
main.__spec__ = SimpleNamespace(name="untrusted_other_module")
status = composite.readiness()
assert status["ready"] is False
assert status["error"] == "C5_ISSUER_SOURCE_MODULE_MISSING"
def test_single_activation_config_is_exact_and_old_env_does_not_enable(monkeypatch, tmp_path):
monkeypatch.delenv("AIOS_C5_PRINCIPAL_SCOPE_CONFIG", raising=False)
monkeypatch.setenv("AIOS_C5_PRINCIPAL_SCOPE_ENABLED", "true")
assert scope.RuntimeConfig.from_environment() is None
config = {
"schema": "aios.c5-task-principal-activation.v1",
"task_id": scope.TASK_ID,
"agent_id": scope.AGENT_ID,
"execution_digest": scope.EXECUTION_DIGEST,
"store_path": "/var/lib/aios-task-principal-c5/scope-enrollment.sqlite3",
"grant_db": "/var/lib/aios-gitops/bridge-v2-staging/task-admission.sqlite3",
"plan_dir": "/var/lib/aios-gitops/bridge-v2-staging/releases/c5/plans",
"producer_state_root": "/var/lib/aios-gitops/bridge-v2-staging",
"policy_artifact": "/var/lib/aios-gitops/bridge-v2-staging/releases/c5/policy.json",
"policy_revocations": "/var/lib/aios-gitops/bridge-v2-staging/releases/c5/policy-revocations.json",
"epoch_root": "/var/lib/aios-gitops/bridge-v2-staging/releases/c5/epoch",
"epoch_revocations": "/var/lib/aios-gitops/bridge-v2-staging/releases/c5/epoch-revocations.json",
"owner_registry": "/etc/aios/single-host-signer-registry.json",
"owner_signer_id": "local-owner-authority",
"registered_signer_command": "/usr/local/libexec/aios-grant-signer-adapter",
"require_root_plan_owner": True,
"grant_db_allowed_group_gid": None,
}
path = tmp_path / "principal-activation.json"
_write(path, scope.canonical_json(config))
monkeypatch.setenv("AIOS_C5_PRINCIPAL_SCOPE_CONFIG", str(path.resolve()))
parsed = scope.RuntimeConfig.from_environment()
assert parsed is not None
assert str(parsed.owner_registry).replace("\\", "/") == "/etc/aios/single-host-signer-registry.json"
config["unexpected"] = True
_write(path, scope.canonical_json(config))
with pytest.raises(scope.C5PrincipalScopeError, match="C5_CONFIG_FIELDS_INVALID"):
scope.RuntimeConfig.from_environment()
def test_typed_issuer_signs_enrolls_and_replays_without_raw_artifact(monkeypatch, tmp_path):
service, _signatures, _paths, _store = _service(tmp_path)
calls = []
class RegistryVerifier:
def __init__(self, *args, **kwargs):
calls.append(("verifier", args, kwargs))
class Signer:
def __init__(self, *args, **kwargs):
calls.append(("signer", args, kwargs))
def _sign_canonical_payload(self, namespace, payload):
calls.append(("sign", namespace, payload))
return "valid-signature"
monkeypatch.setitem(
sys.modules, "aios_single_host_authority",
SimpleNamespace(RegistryOwnerApprovalVerifier=RegistryVerifier),
)
monkeypatch.setitem(
sys.modules, "registered_owner_signer_adapter",
SimpleNamespace(RegisteredOwnerSignerAdapter=Signer),
)
config = scope.RuntimeConfig(
store_path=tmp_path / "enrollments.sqlite3",
grant_db=tmp_path / "grant.sqlite3", plan_dir=tmp_path / "plans",
producer_state_root=tmp_path, policy_artifact=tmp_path / "policy.json",
policy_revocation_file=tmp_path / "policy-revocations.json",
epoch_root=tmp_path / "epoch",
epoch_revocation_file=tmp_path / "epoch-revocations.json",
owner_registry=Path("/etc/aios/single-host-signer-registry.json"),
owner_signer_id="local-owner-authority",
registered_signer_command=Path("/usr/local/libexec/aios-grant-signer-adapter"),
require_root_plan_owner=True, allowed_group_gid=None,
)
issuer = scope.C5ScopeEnrollmentIssuer(service, config)
receipt = issuer.issue(
task=_task(), execution=_execution(), trust=TRUST, now=NOW,
ttl_seconds=600,
)
assert receipt["status"] == "ENROLLED"
assert receipt["task_id"] == scope.TASK_ID
assert receipt["agent_id"] == scope.AGENT_ID
assert receipt["job_id"] == "job-c5-real"
assert "signature" not in receipt and "contract" not in receipt
replay = issuer.issue(
task=_task(), execution=_execution(), trust=TRUST, now=NOW,
ttl_seconds=600,
)
assert replay["replayed"] is True
assert len([item for item in calls if item[0] == "sign"]) == 1
def test_overlay_builder_patches_one_shared_admission_module(tmp_path, monkeypatch):
builder_path = ROOT / "scripts" / "build_c5_live_principal_overlay.py"
spec = importlib.util.spec_from_file_location("c5_overlay_builder_test", builder_path)
assert spec is not None and spec.loader is not None
builder = importlib.util.module_from_spec(spec)
spec.loader.exec_module(builder)
principal = tmp_path / "principal"
adapter = tmp_path / "adapter"
principal.mkdir()
adapter.mkdir()
admission_payload = b"# effective governed admission fixture\n"
(principal / "aios_task_principal_admission.py").write_bytes(admission_payload)
issuer_payload = (
b"class Base:\n"
b" _read_public_manifest = staticmethod(lambda path: path)\n"
b"_BASE = Base()\n"
b"MAX_BODY = 4 * 1024 * 1024\n"
b"def main():\n"
b" return None\n"
+ builder.ISSUER_MAIN_ANCHOR.encode()
)
(principal / "aios_task_principal_issuer_server.py").write_bytes(issuer_payload)
adapter_payloads = {
"aios_task_principal_governed_adapter.py": b"# governed adapter fixture\n",
"governed_runtime_scope.py": b"# governed scope fixture\n",
}
for name, payload in adapter_payloads.items():
(adapter / name).write_bytes(payload)
builder.PINNED = {
"aios_task_principal_admission.py": hashlib.sha256(
admission_payload
).hexdigest(),
"aios_task_principal_issuer_server.py": hashlib.sha256(
issuer_payload
).hexdigest(),
**{
name: hashlib.sha256(payload).hexdigest()
for name, payload in adapter_payloads.items()
},
}
output = tmp_path / "overlay"
monkeypatch.setattr(sys, "argv", [
str(builder_path), "--principal-source-root", str(principal),
"--adapter-source-root", str(adapter), "--output-root", str(output),
])
assert builder.main() == 0
manifest = json.loads((output / "overlay-manifest.json").read_text())
assert manifest["activation"]["same_admission_overlay_for_both_units"] is True
assert manifest["entrypoints"]["issuer"] == "python3 -B -m aios_task_principal_issuer_server"
issuer_output = output / "app" / "aios_task_principal_issuer_server.py"
issuer_result = issuer_output.read_text()
assert issuer_result.startswith(issuer_payload.decode().split(
builder.ISSUER_MAIN_ANCHOR, 1
)[0])
assert "_read_public_manifest = _BASE._read_public_manifest" in issuer_result
assert issuer_result.count(builder.ISSUER_MAIN_ANCHOR) == 1
namespace = {"__name__": "issuer_fixture_import"}
exec(compile(issuer_result, str(issuer_output), "exec"), namespace)
assert namespace["_read_public_manifest"]("manifest.json") == "manifest.json"
assert namespace["MAX_BODY"] == 4 * 1024 * 1024
assert manifest["files"][issuer_output.name]["base_sha256"] == hashlib.sha256(
issuer_payload
).hexdigest()
assert manifest["files"][issuer_output.name]["result_sha256"] == hashlib.sha256(
issuer_output.read_bytes()
).hexdigest()
assert manifest["files"][issuer_output.name]["bridge_private_helper"] == (
"_read_public_manifest"
)
patched = (output / "app" / "aios_task_principal_admission.py").read_text()
assert builder.PINNED["aios_task_principal_admission.py"] == hashlib.sha256(
admission_payload
).hexdigest()
assert "_ORIGINAL_SCOPE_AND_TRUST = _legacy._scope_and_trust" in patched
assert 'if str(task["task_id"]) != C5_TASK_ID:' in patched
assert "return _ORIGINAL_SCOPE_AND_TRUST(" in patched
assert "active_digest, active_scope = _legacy._active_scope_chain(con)" in patched
assert "if active_digest != digest:" in patched
assert 'event["scope_id"] != active_scope["scope_id"]' in patched
assert "_legacy._check_revocations(con, task, active_scope, trust, None)" in patched
assert "wrap_principal_runtime_from_environment" in patched
assert manifest["files"]["aios_task_principal_admission.py"][
"runtime_base_source"
] == builder.EFFECTIVE_ADMISSION_SOURCE
assert hashlib.sha256(
(output / "app" / "aios_task_principal_governed_adapter.py").read_bytes()
).hexdigest() == scope.sha256_bytes((adapter / "aios_task_principal_governed_adapter.py").read_bytes())
assert "sys.dont_write_bytecode = True" in (
output / "bin" / "aios-c5-principal-scope"
).read_text()
+127
View File
@@ -0,0 +1,127 @@
from __future__ import annotations
import hashlib
import io
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tarfile
import pytest
from scripts.build_c5_staging_artifact import BuildError, build_artifact
def _sha(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _fixture(tmp_path: Path) -> tuple[Path, str, Path, Path, Path]:
repo = Path(__file__).parents[1]
support = subprocess.check_output(
["git", "-C", str(repo), "rev-parse", "HEAD"], text=True
).strip()
live = repo.parent / "live-release"
overlay = tmp_path / "staging-overlay" / "app"
snapshot = overlay.parent.parent / "staging-source-snapshot"
overlay.mkdir(parents=True)
snapshot.mkdir(parents=True)
for name in (
"aios_chatgpt_bridge_v2_server.py",
"aios_task_admission_bootstrap.py",
"aios_task_admission_live_adapters.py",
):
shutil.copy2(live / "staging-overlay" / "app" / name, overlay / name)
shutil.copy2(live / "staging-source-snapshot" / name, snapshot / name)
shutil.copy2(
live / "staging-overlay" / "c5-epoch-v2-overlay.result.json",
overlay.parent / "c5-epoch-v2-overlay.result.json",
)
authority = tmp_path / "authority"
authority.mkdir()
root = f"/var/lib/aios-gitops/bridge-v2-staging/releases/c5-source-{support}"
policy = {"payload": {"source_oid": support}}
policy_bytes = json.dumps(policy).encode()
(authority / "policy.json").write_bytes(policy_bytes)
source_activation = {
"schema": "aios.source-publication-host-activation.v1",
"policy_artifact": f"{root}/policy.json",
"policy_sha256": _sha(policy_bytes),
"policy_revocations": f"{root}/policy-revocations.json",
"admission_activation": f"{root}/admission-activation.json",
"expected_agent_id": "agent/codex-c5-release",
}
(authority / "source-activation.json").write_bytes(json.dumps(source_activation).encode())
(authority / "policy-revocations.json").write_bytes(b"{}")
(authority / "admission-activation.json").write_bytes(b"{}")
(authority / "AUTHORITY_PREPARATION_RESULT.json").write_bytes(
json.dumps({"source_commit": support, "support_commit": support}).encode()
)
old_module = "/opt/aios/current/app/aios_chatgpt_bridge_v2_server.py"
snapshot_payload = {
"unit_active": True,
"activation_file": None,
"principal_scope_config": None,
"effective_config_sha256": "1" * 64,
"dropin": {"exists": True, "sha256": "2" * 64},
"pythonpath": ["/opt/aios/current", "/opt/aios/current/app"],
"source_files": [{
"path": old_module, "sha256": "3" * 64, "size": 12,
"mode": 0o644, "kind": "existing_source",
}],
}
snapshot = tmp_path / "snapshot.json"
snapshot.write_bytes(json.dumps(snapshot_payload).encode())
return repo, support, overlay, authority, snapshot
def test_builds_regular_only_archive_and_typed_request(tmp_path: Path) -> None:
repo, support, overlay, authority, snapshot = _fixture(tmp_path)
result = build_artifact(
repo, support, overlay, authority, snapshot, tmp_path / "out",
nonce="c5-test-nonce-20260926", issued_at=1000, ttl_seconds=900,
)
artifact = Path(result["artifact"])
request = json.loads(Path(result["request"]).read_text())
assert result["status"] == "BUILT_UNSIGNED"
assert result["signed"] is False and result["installed"] is False
assert request["schema"] == "aios.c5-staging-installation-request.v2"
assert request["target"] == "bridge-18892"
paths = {record["path"] for record in request["files"]}
assert any(path.endswith("/app/c5_source_authority.py") for path in paths)
assert any(path.endswith("/app/aios_task_admission_live_adapters.py") for path in paths)
with tarfile.open(artifact, "r:") as bundle:
members = bundle.getmembers()
assert members
assert all(member.isfile() for member in members)
assert "dropin.conf" in {member.name for member in members}
assert "__pycache__" not in " ".join(member.name for member in members)
def test_rejects_bytecode_in_overlay(tmp_path: Path) -> None:
repo, support, overlay, authority, snapshot = _fixture(tmp_path)
(overlay / "bad.pyc").write_bytes(b"bytecode")
with pytest.raises(BuildError, match="OVERLAY_BYTECODE_FORBIDDEN"):
build_artifact(repo, support, overlay, authority, snapshot, tmp_path / "out")
def test_generated_request_and_archive_use_installer_producer_validators(tmp_path: Path) -> None:
"""Exercise the real typed request and artifact producer checks when crypto is available."""
repo, support, overlay, authority, snapshot = _fixture(tmp_path)
result = build_artifact(
repo, support, overlay, authority, snapshot, tmp_path / "out",
nonce="c5-validator-nonce-20260926", issued_at=1000, ttl_seconds=900,
)
sys.path.insert(0, str((repo / "active-runtime" / "app").resolve()))
c5 = pytest.importorskip("c5_staging_installation")
request = c5.load_installation_request(Path(result["request"]), require_root_owner=False)
layout = c5._Layout(test_only=True)
payload = c5._request_payload(
request, owner_key_id="SHA256:ephemeral-validator", issued_at=1000, layout=layout,
)
c5._validate_payload(payload, 1000, layout=layout)
c5._verified_artifact(
Path(result["artifact"]), payload, layout=layout, require_root_owner=False,
)
+994
View File
@@ -0,0 +1,994 @@
from __future__ import annotations
import hashlib
import io
import json
import os
from pathlib import Path
import sys
import tarfile
from types import SimpleNamespace
import pytest
sys.path.insert(0, str(Path(__file__).parents[1] / "active-runtime" / "app"))
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from aios_single_host_authority import (
OWNER_NAMESPACE,
sign_sshsig,
ssh_fingerprint,
ssh_public_line,
)
import c5_staging_installation as c5
from registered_owner_signer_adapter import RegisteredOwnerSignerAdapter
NOW = 1050
SOURCE = "536a2af0f07962495c88526061ce43fb27994c15"
SUPPORT = "04a5e3cbfecf8156074f3a68cd72b678c06ace11"
def sha(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def file_record(path: str, data: bytes, kind: str, mode: int = 0o644) -> dict:
return {
"path": path,
"sha256": sha(data),
"size": len(data),
"mode": mode,
"kind": kind,
}
def principal_material(root: Path) -> tuple[bytes, dict[str, bytes]]:
supporting = {
"plans/task.json": b'{"plan":"c5"}',
"policy.json": b'{"policy":"c5"}',
"policy-revocations.json": b'{"revocations":[]}',
"epoch/epoch.json": b'{"epoch":"c5"}',
"epoch-revocations.json": b'{"revocations":[]}',
}
config = {
"schema": "aios.c5-task-principal-activation.v1",
"task_id": "TASK-V2R2C5-SOURCE-PUBLICATION-20260926-R1",
"agent_id": "agent/codex-c5-release",
"execution_digest": "1ba534d551991e8c99014285eeea36afcbc1500686035cb8bcd7f9f4efd6215e",
"store_path": "/var/lib/aios-gitops/c5-source-principal/enrollment.sqlite3",
"grant_db": "/var/lib/aios-gitops/bridge-v2-staging/task-admission.sqlite3",
"plan_dir": (root / "plans").as_posix(),
"producer_state_root": "/var/lib/aios-gitops/bridge-v2-staging",
"policy_artifact": (root / "policy.json").as_posix(),
"policy_revocations": (root / "policy-revocations.json").as_posix(),
"epoch_root": (root / "epoch").as_posix(),
"epoch_revocations": (root / "epoch-revocations.json").as_posix(),
"owner_registry": "/etc/aios/single-host-signer-registry.json",
"owner_signer_id": "local-owner-authority",
"registered_signer_command": "/usr/local/libexec/aios-grant-signer-adapter",
"require_root_plan_owner": True,
"grant_db_allowed_group_gid": None,
}
return c5.canonical_bytes(config), supporting
def write_registry(path: Path, key: Ed25519PrivateKey) -> None:
public = key.public_key()
value = {
"schema": "aios.single-host-signer-registry.v1",
"authority_generation": 1,
"signers": [
{
"signer_id": "local-owner-authority",
"role": "OWNER_AUTHORITY",
"provider": "local_unix_service",
"fingerprint": ssh_fingerprint(public),
"public_key": ssh_public_line(public, "local-owner-authority"),
"socket": "/run/aios/owner-authority.sock",
"secret_ref": "aios-secret://single-host/local-owner-authority",
"status": "ACTIVE",
"generation": 1,
"parent_signer_id": "",
"parent_fingerprint": "",
}
],
}
path.write_text(json.dumps(value), encoding="utf-8")
def write_tar(path: Path, members: list[tuple[str, bytes, int, str]]) -> None:
with tarfile.open(path, "w") as bundle:
for name, data, mode, kind in members:
info = tarfile.TarInfo(name)
info.mode = mode
if kind == "file":
info.size = len(data)
bundle.addfile(info, io.BytesIO(data))
elif kind == "symlink":
info.type = tarfile.SYMTYPE
info.linkname = "target"
bundle.addfile(info)
else:
raise AssertionError(kind)
class FakeBackend:
def __init__(self, case: "Case") -> None:
self.case = case
self.host = c5.HOST
self.reloads = 0
self.restarts = 0
self.fail_new = False
self.fail_old = False
self.runtime_results = []
self.runtime_calls = 0
def hostname(self) -> str:
return self.host
def is_new(self) -> bool:
dropin = self.case.profile.dropin(self.case.layout)
return (
dropin.exists()
and dropin.read_bytes() == self.case.new_dropin
)
def unit_snapshot(self, profile) -> c5.UnitSnapshot:
assert profile == self.case.profile
if self.is_new():
return c5.UnitSnapshot(
True,
sha(b"new-effective-config"),
self.case.new_pythonpath,
self.case.activation_path,
self.case.principal_config_path,
)
return c5.UnitSnapshot(
True,
self.case.old_config_hash,
self.case.old_pythonpath,
None,
None,
)
def daemon_reload(self) -> None:
self.reloads += 1
def restart(self, profile) -> None:
self.restarts += 1
def runtime(self, *, url: str, timeout_seconds: int):
assert url == self.case.runtime_url
assert timeout_seconds == 3
self.runtime_calls += 1
if self.runtime_results:
result = self.runtime_results.pop(0)
if isinstance(result, Exception):
raise result
return result
if self.is_new():
return {"status": "bad"} if self.fail_new else dict(self.case.new_runtime)
return {"status": "bad"} if self.fail_old else dict(self.case.old_runtime)
class Case:
pass
def make_case(tmp_path: Path) -> Case:
case = Case()
case.profile = c5._BRIDGE_PROFILE
case.layout = c5._Layout(
release_base=tmp_path / "var" / "releases",
dropin=tmp_path / "etc" / f"{c5.UNIT}.d" / c5.DROPIN.name,
issuer_dropin=tmp_path / "etc" / f"{c5.ISSUER_UNIT}.d" / c5.ISSUER_DROPIN.name,
state_root=tmp_path / "var" / "state",
test_only=True,
)
case.layout.release_base.mkdir(parents=True)
case.layout.dropin.parent.mkdir(parents=True)
case.layout.issuer_dropin.parent.mkdir(parents=True)
case.old_dropin = b"[Service]\nEnvironment=EXISTING_G0=preserved\n"
case.layout.dropin.write_bytes(case.old_dropin)
old_root = tmp_path / "old-runtime"
old_app = old_root / "app"
old_agent = old_root / "runtime-agent-os"
old_app.mkdir(parents=True)
old_agent.mkdir()
case.old_module = old_app / "aios_chatgpt_bridge_v2_server.py"
case.old_module_bytes = b"old bridge source\n"
case.old_module.write_bytes(case.old_module_bytes)
case.old_pythonpath = (
old_root.as_posix(),
old_app.as_posix(),
old_agent.as_posix(),
)
case.old_config_hash = sha(b"old-effective-config")
case.root = case.layout.release_base / f"c5-source-{SUPPORT}"
case.new_module = case.root / "app" / "aios_chatgpt_bridge_v2_server.py"
case.new_module_bytes = b"new bridge source with runtime proof\n"
case.activation = case.root / "source-activation.json"
case.activation_bytes = b'{"schema":"aios.source-activation.v1"}\n'
case.activation_path = case.activation.as_posix()
case.principal_config_path = None
case.runtime_url = c5.RUNTIME_URL
case.new_pythonpath = ((case.root / "app").as_posix(), *case.old_pythonpath)
case.new_dropin = c5._dropin_bytes(case.root, case.old_pythonpath)
case.old_runtime = {
"status": "active",
"health": 200,
"deployment_state": "STAGING",
"repository": c5.REPOSITORY,
"control_plane_target_oid": "1" * 40,
}
case.new_runtime = {
"status": "active",
"health": 200,
"deployment_state": "STAGING",
"repository": c5.REPOSITORY,
"governed_writes_enabled": True,
"source_module_path": case.new_module.as_posix(),
"source_module_sha256": sha(case.new_module_bytes),
}
case.files = [
file_record(case.new_module.as_posix(), case.new_module_bytes, "release_file"),
file_record(case.activation.as_posix(), case.activation_bytes, "release_file"),
file_record(case.layout.dropin.as_posix(), case.new_dropin, "systemd_dropin"),
]
case.payload = {
"schema": c5.SCHEMA,
"target": c5.BRIDGE_TARGET,
"contract_id": "c5-installation-01",
"owner_signer_id": "local-owner-authority",
"owner_key_id": "",
"owner_namespace": OWNER_NAMESPACE,
"host": c5.HOST,
"repository": c5.REPOSITORY,
"source_commit": SOURCE,
"support_commit": SUPPORT,
"release_root": case.root.as_posix(),
"files": case.files,
"unit": c5.UNIT,
"listen": {"host": "127.0.0.1", "port": c5.PORT},
"deployment_state": "STAGING",
"effects": sorted(c5.EFFECTS),
"forbidden_effects": sorted(c5.FORBIDDEN),
"existing": {
"unit_active": True,
"effective_config_sha256": case.old_config_hash,
"dropin": {"exists": True, "sha256": sha(case.old_dropin)},
"pythonpath": list(case.old_pythonpath),
"source_files": [
file_record(
case.old_module.as_posix(),
case.old_module_bytes,
"existing_source",
)
],
"activation_file": None,
"principal_scope_config": None,
},
"readiness": {
"url": c5.RUNTIME_URL,
"timeout_seconds": 3,
"unit_active": True,
"pythonpath": list(case.new_pythonpath),
"activation_file": case.activation_path,
"principal_scope_config": None,
"module_path": case.new_module.as_posix(),
"module_sha256": sha(case.new_module_bytes),
"runtime_fields": case.new_runtime,
},
"rollback": {
"strategy": "restore-exact-dropin-and-restart",
"dropin_restore": "exact",
"readiness": {
"url": c5.RUNTIME_URL,
"timeout_seconds": 3,
"unit_active": True,
"pythonpath": list(case.old_pythonpath),
"activation_file": None,
"principal_scope_config": None,
"module_path": case.old_module.as_posix(),
"module_sha256": sha(case.old_module_bytes),
"runtime_fields": case.old_runtime,
},
},
"issued_at": 1000,
"expires_at": 1100,
"nonce": "nonce-c5-installation-0001",
}
case.key = Ed25519PrivateKey.generate()
case.payload["owner_key_id"] = ssh_fingerprint(case.key.public_key())
case.registry = tmp_path / "registry.json"
write_registry(case.registry, case.key)
case.contract = tmp_path / "contract.json"
case.artifact = tmp_path / "artifact.tar"
resign(case)
write_tar(
case.artifact,
[
("app/aios_chatgpt_bridge_v2_server.py", case.new_module_bytes, 0o644, "file"),
("source-activation.json", case.activation_bytes, 0o644, "file"),
("dropin.conf", case.new_dropin, 0o644, "file"),
],
)
case.backend = FakeBackend(case)
case.installer = c5.Installer(
case.registry,
backend=case.backend,
clock=lambda: NOW,
sleeper=lambda _seconds: None,
require_root_owner=False,
_test_layout=case.layout,
)
return case
def make_issuer_case(tmp_path: Path) -> Case:
case = make_case(tmp_path)
case.profile = c5._ISSUER_PROFILE
case.runtime_url = c5.ISSUER_RUNTIME_URL
case.layout.issuer_dropin.write_bytes(case.old_dropin)
old_issuer = case.old_module.parent / "aios_task_principal_issuer_server.py"
old_issuer_bytes = b"old issuer source\n"
old_issuer.write_bytes(old_issuer_bytes)
case.old_module = old_issuer
case.old_module_bytes = old_issuer_bytes
app = case.root / "app"
app.mkdir(parents=True)
case.new_module = app / "aios_task_principal_issuer_server.py"
case.new_module_bytes = b"new issuer source with C5 health\n"
case.new_module.write_bytes(case.new_module_bytes)
bridge_module = app / "aios_chatgpt_bridge_v2_server.py"
bridge_bytes = b"shared bridge module\n"
bridge_module.write_bytes(bridge_bytes)
case.activation.write_bytes(case.activation_bytes)
principal_config = case.root / "principal-activation.json"
principal_bytes, principal_supporting = principal_material(case.root)
principal_config.write_bytes(principal_bytes)
for relative, data in principal_supporting.items():
path = case.root / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
case.activation_path = None
case.principal_config_path = principal_config.as_posix()
case.new_pythonpath = ((case.root / "app").as_posix(), *case.old_pythonpath)
case.new_dropin = c5._dropin_bytes(
case.root, case.old_pythonpath,
target=c5.ISSUER_TARGET, principal_config=True,
)
case.old_runtime = {
"ready": True,
"active_key_registered": True,
"private_key_matches_trust": True,
}
case.new_runtime = {
"ready": True,
"c5_scope_enabled": True,
"c5_scope_enrolled": False,
"c5_scope_role": "ISSUER",
"source_module_path": case.new_module.as_posix(),
"source_module_sha256": sha(case.new_module_bytes),
}
case.files = [
file_record(bridge_module.as_posix(), bridge_bytes, "release_file"),
file_record(case.new_module.as_posix(), case.new_module_bytes, "release_file"),
file_record(case.activation.as_posix(), case.activation_bytes, "release_file"),
file_record(principal_config.as_posix(), principal_bytes, "release_file"),
*[
file_record((case.root / relative).as_posix(), data, "release_file")
for relative, data in principal_supporting.items()
],
file_record(case.layout.issuer_dropin.as_posix(), case.new_dropin, "systemd_dropin"),
]
case.payload.update(
target=c5.ISSUER_TARGET,
contract_id="c5-issuer-installation-01",
files=case.files,
unit=c5.ISSUER_UNIT,
listen={"host": "127.0.0.1", "port": c5.ISSUER_PORT},
effects=sorted(c5.ISSUER_EFFECTS),
nonce="nonce-c5-issuer-installation-0001",
)
case.payload["existing"] = {
"unit_active": True,
"effective_config_sha256": case.old_config_hash,
"dropin": {"exists": True, "sha256": sha(case.old_dropin)},
"pythonpath": list(case.old_pythonpath),
"source_files": [
file_record(old_issuer.as_posix(), old_issuer_bytes, "existing_source")
],
"activation_file": None,
"principal_scope_config": None,
}
case.payload["readiness"] = {
"url": c5.ISSUER_RUNTIME_URL,
"timeout_seconds": 3,
"unit_active": True,
"pythonpath": list(case.new_pythonpath),
"activation_file": None,
"principal_scope_config": case.principal_config_path,
"module_path": case.new_module.as_posix(),
"module_sha256": sha(case.new_module_bytes),
"runtime_fields": case.new_runtime,
}
case.payload["rollback"]["readiness"] = {
"url": c5.ISSUER_RUNTIME_URL,
"timeout_seconds": 3,
"unit_active": True,
"pythonpath": list(case.old_pythonpath),
"activation_file": None,
"principal_scope_config": None,
"module_path": old_issuer.as_posix(),
"module_sha256": sha(old_issuer_bytes),
"runtime_fields": case.old_runtime,
}
resign(case)
write_tar(
case.artifact,
[
("app/aios_chatgpt_bridge_v2_server.py", bridge_bytes, 0o644, "file"),
("app/aios_task_principal_issuer_server.py", case.new_module_bytes, 0o644, "file"),
("source-activation.json", case.activation_bytes, 0o644, "file"),
("principal-activation.json", principal_bytes, 0o644, "file"),
*[
(relative, data, 0o644, "file")
for relative, data in principal_supporting.items()
],
("dropin.conf", case.new_dropin, 0o644, "file"),
],
)
case.backend = FakeBackend(case)
case.installer = c5.Installer(
case.registry,
backend=case.backend,
clock=lambda: NOW,
sleeper=lambda _seconds: None,
require_root_owner=False,
_test_layout=case.layout,
)
return case
def resign(case: Case) -> None:
signature = sign_sshsig(case.key, c5.canonical_bytes(case.payload), namespace=OWNER_NAMESPACE)
case.contract.write_text(
json.dumps({"payload": case.payload, "signature": signature}),
encoding="utf-8",
)
def test_signature_registry_and_concrete_snapshot_preflight(tmp_path: Path) -> None:
case = make_case(tmp_path)
result = case.installer.preflight(case.contract, case.artifact)
assert result["status"] == "PREFLIGHT_PASS"
assert result["release_root"] == case.root.as_posix()
assert not case.root.exists()
def test_tamper_is_rejected_by_independent_consumer(tmp_path: Path) -> None:
case = make_case(tmp_path)
outer = json.loads(case.contract.read_text())
outer["payload"]["source_commit"] = "2" * 40
case.contract.write_text(json.dumps(outer))
with pytest.raises(c5.InstallationError, match="INSTALL_SIGNATURE_INVALID"):
case.installer.preflight(case.contract, case.artifact)
@pytest.mark.parametrize(
("field", "value", "error"),
[
("host", "other-host", "INSTALL_SCOPE_INVALID"),
("unit", "other.service", "INSTALL_SCOPE_INVALID"),
("listen", {"host": "0.0.0.0", "port": 18892}, "INSTALL_LISTEN_INVALID"),
("expires_at", 1050, "INSTALL_TTL_OR_NONCE_INVALID"),
],
)
def test_scope_expiration_and_unit_are_rejected(
tmp_path: Path, field: str, value, error: str
) -> None:
case = make_case(tmp_path)
case.payload[field] = value
resign(case)
with pytest.raises(c5.InstallationError, match=error):
case.installer.preflight(case.contract, case.artifact)
def test_actual_hostname_is_required(tmp_path: Path) -> None:
case = make_case(tmp_path)
case.backend.host = "wrong-host"
with pytest.raises(c5.InstallationError, match="INSTALL_HOST_INVALID"):
case.installer.preflight(case.contract, case.artifact)
@pytest.mark.parametrize("mode", [0o444, 0o640])
def test_live_safe_existing_source_modes_are_accepted(
tmp_path: Path, mode: int,
) -> None:
case = make_case(tmp_path)
os.chmod(case.old_module, mode)
case.payload["existing"]["source_files"][0]["mode"] = mode
resign(case)
assert case.installer.preflight(case.contract, case.artifact)["status"] == "PREFLIGHT_PASS"
def test_dropin_execstart_or_extra_environment_is_rejected(tmp_path: Path) -> None:
case = make_case(tmp_path)
injected = case.new_dropin + b"ExecStart=/bin/sh -c evil\nEnvironment=AIOS_GITOPS_WRITES_ENABLED=true\n"
for record in case.payload["files"]:
if record["path"] == case.layout.dropin.as_posix():
record.update(sha256=sha(injected), size=len(injected))
resign(case)
with pytest.raises(c5.InstallationError, match="DROPIN_CONTRACT_INVALID"):
case.installer.preflight(case.contract, case.artifact)
def test_bridge_principal_config_is_byte_pinned_and_adds_only_exact_env(
tmp_path: Path,
) -> None:
case = make_case(tmp_path)
config = case.root / "principal-activation.json"
config_bytes, principal_supporting = principal_material(case.root)
case.files.insert(-1, file_record(config.as_posix(), config_bytes, "release_file"))
for relative, data in principal_supporting.items():
case.files.insert(
-1,
file_record((case.root / relative).as_posix(), data, "release_file"),
)
case.new_dropin = c5._dropin_bytes(
case.root, case.old_pythonpath,
target=c5.BRIDGE_TARGET, principal_config=True,
)
case.principal_config_path = config.as_posix()
for record in case.files:
if record["path"] == case.layout.dropin.as_posix():
record.update(sha256=sha(case.new_dropin), size=len(case.new_dropin))
case.payload["files"] = case.files
case.payload["readiness"]["principal_scope_config"] = config.as_posix()
resign(case)
write_tar(
case.artifact,
[
("app/aios_chatgpt_bridge_v2_server.py", case.new_module_bytes, 0o644, "file"),
("source-activation.json", case.activation_bytes, 0o644, "file"),
("principal-activation.json", config_bytes, 0o644, "file"),
*[(relative, data, 0o644, "file") for relative, data in principal_supporting.items()],
("dropin.conf", case.new_dropin, 0o644, "file"),
],
)
assert case.installer.preflight(case.contract, case.artifact)["status"] == "PREFLIGHT_PASS"
assert case.new_dropin.decode().splitlines() == [
"[Service]",
f"Environment=PYTHONPATH={':'.join(case.new_pythonpath)}",
f"Environment=AIOS_SOURCE_PUBLICATION_ACTIVATION_FILE={case.activation_path}",
f"Environment=AIOS_C5_PRINCIPAL_SCOPE_CONFIG={config.as_posix()}",
]
write_tar(
case.artifact,
[
("app/aios_chatgpt_bridge_v2_server.py", case.new_module_bytes, 0o644, "file"),
("source-activation.json", case.activation_bytes, 0o644, "file"),
("principal-activation.json", b"X" * len(config_bytes), 0o644, "file"),
*[(relative, data, 0o644, "file") for relative, data in principal_supporting.items()],
("dropin.conf", case.new_dropin, 0o644, "file"),
],
)
with pytest.raises(c5.InstallationError, match="ARTIFACT_HASH_MISMATCH"):
case.installer.preflight(case.contract, case.artifact)
def test_issuer_contract_verifies_existing_tree_and_changes_only_issuer_unit(
tmp_path: Path,
) -> None:
case = make_issuer_case(tmp_path)
before = {path.relative_to(case.root): path.read_bytes() for path in case.root.rglob("*") if path.is_file()}
result = case.installer.preflight(case.contract, case.artifact)
assert result["unit"] == c5.ISSUER_UNIT
assert result["port"] == c5.ISSUER_PORT
assert case.installer.apply(case.contract, case.artifact)["status"] == "APPLIED"
after = {path.relative_to(case.root): path.read_bytes() for path in case.root.rglob("*") if path.is_file()}
assert after == before
assert case.layout.issuer_dropin.read_bytes() == case.new_dropin
assert case.layout.dropin.read_bytes() == case.old_dropin
assert case.backend.reloads == 1
assert case.backend.restarts == 1
def test_issuer_rejects_nonexact_existing_release_tree(tmp_path: Path) -> None:
case = make_issuer_case(tmp_path)
(case.root / "unsigned-extra.py").write_text("extra", encoding="utf-8")
with pytest.raises(c5.InstallationError, match="IMMUTABLE_RELEASE_TREE_MISMATCH"):
case.installer.preflight(case.contract, case.artifact)
def test_issuer_readiness_failure_restores_dropin_without_deleting_shared_tree(
tmp_path: Path,
) -> None:
case = make_issuer_case(tmp_path)
case.backend.fail_new = True
with pytest.raises(c5.InstallationError, match="ROLLBACK_VERIFIED"):
case.installer.apply(case.contract, case.artifact)
assert case.layout.issuer_dropin.read_bytes() == case.old_dropin
assert case.root.is_dir()
assert case.new_module.read_bytes() == case.new_module_bytes
assert case.backend.reloads == 2
assert case.backend.restarts == 2
@pytest.mark.parametrize("attack", ["duplicate", "extra", "traversal", "symlink"])
def test_tar_rejects_duplicate_extra_traversal_and_links(
tmp_path: Path, attack: str
) -> None:
case = make_case(tmp_path)
members = [
("app/aios_chatgpt_bridge_v2_server.py", case.new_module_bytes, 0o644, "file"),
("source-activation.json", case.activation_bytes, 0o644, "file"),
("dropin.conf", case.new_dropin, 0o644, "file"),
]
if attack == "duplicate":
members.append(members[0])
elif attack == "extra":
members.append(("extra.py", b"extra", 0o644, "file"))
elif attack == "traversal":
members[0] = ("../escape.py", case.new_module_bytes, 0o644, "file")
else:
members[0] = (members[0][0], b"", 0o644, "symlink")
write_tar(case.artifact, members)
with pytest.raises(c5.InstallationError):
case.installer.preflight(case.contract, case.artifact)
def test_apply_and_same_contract_replay_verify_bytes_and_runtime(tmp_path: Path) -> None:
case = make_case(tmp_path)
assert case.installer.apply(case.contract, case.artifact)["status"] == "APPLIED"
assert case.layout.dropin.read_bytes() == case.new_dropin
assert case.backend.reloads == 1
assert case.backend.restarts == 1
assert case.installer.apply(case.contract, case.artifact)["status"] == "IDEMPOTENT_REPLAY"
case.new_module.write_bytes(b"tampered")
with pytest.raises(c5.InstallationError, match="INSTALLED_FILE_MISMATCH"):
case.installer.apply(case.contract, case.artifact)
def test_future_bridge_contract_requires_governed_writes_readiness(
tmp_path: Path,
) -> None:
case = make_case(tmp_path)
case.payload["readiness"]["runtime_fields"].pop("governed_writes_enabled")
resign(case)
with pytest.raises(
c5.InstallationError,
match="BRIDGE_GOVERNED_WRITES_READINESS_REQUIRED",
):
case.installer.preflight(case.contract, case.artifact)
def test_applied_issuer_rollback_restores_old_runtime_and_keeps_shared_tree(
tmp_path: Path,
) -> None:
case = make_issuer_case(tmp_path)
case.layout.issuer_dropin.unlink()
case.payload["existing"]["dropin"] = {"exists": False, "sha256": None}
resign(case)
assert case.installer.apply(case.contract, case.artifact)["status"] == "APPLIED"
result = case.installer.rollback_applied(case.contract, case.artifact)
assert result["status"] == "ROLLED_BACK"
assert not case.layout.issuer_dropin.exists()
assert case.root.is_dir()
assert case.new_module.read_bytes() == case.new_module_bytes
state = json.loads(case.installer._state_path(case.payload).read_text())
nonce = json.loads(case.installer._nonce_path(case.payload).read_text())
assert state == nonce
assert state["status"] == "ROLLED_BACK"
assert state["detail"] == "applied_rollback_verified"
assert case.installer.rollback_applied(case.contract, case.artifact)["status"] == (
"IDEMPOTENT_ROLLBACK"
)
def test_applied_bridge_rollback_requires_restored_issuer_proof(
tmp_path: Path,
) -> None:
case = make_case(tmp_path)
case.layout.dropin.unlink()
case.payload["existing"]["dropin"] = {"exists": False, "sha256": None}
resign(case)
assert case.installer.apply(case.contract, case.artifact)["status"] == "APPLIED"
with pytest.raises(
c5.InstallationError, match="ISSUER_ROLLBACK_DEPENDENCY_REQUIRED"
):
case.installer.rollback_applied(case.contract, case.artifact)
state = json.loads(case.installer._state_path(case.payload).read_text())
assert state["status"] == "APPLIED"
def test_different_nonce_or_digest_conflicts_after_install(tmp_path: Path) -> None:
case = make_case(tmp_path)
case.installer.apply(case.contract, case.artifact)
case.payload["nonce"] = "nonce-c5-installation-0002"
resign(case)
with pytest.raises(c5.InstallationError, match="INSTALL_REPLAY_CONFLICT"):
case.installer.apply(case.contract, case.artifact)
def test_existing_release_directory_is_never_overwritten(tmp_path: Path) -> None:
case = make_case(tmp_path)
case.root.mkdir()
sentinel = case.root / "sentinel"
sentinel.write_text("keep")
with pytest.raises(c5.InstallationError, match="IMMUTABLE_RELEASE_ROOT_EXISTS"):
case.installer.apply(case.contract, case.artifact)
assert sentinel.read_text() == "keep"
def test_failed_new_readiness_restores_restarts_and_verifies_old_runtime(
tmp_path: Path,
) -> None:
case = make_case(tmp_path)
case.backend.fail_new = True
with pytest.raises(c5.InstallationError, match="ROLLBACK_VERIFIED"):
case.installer.apply(case.contract, case.artifact)
assert case.layout.dropin.read_bytes() == case.old_dropin
assert case.backend.reloads == 2
assert case.backend.restarts == 2
assert not case.root.exists()
state = json.loads(case.installer._state_path(case.payload).read_text())
assert state["status"] == "ROLLED_BACK"
with pytest.raises(c5.InstallationError, match="INSTALL_PRIOR_ATTEMPT_NOT_APPLIED"):
case.installer.apply(case.contract, case.artifact)
def test_unverifiable_rollback_is_durable_unknown(tmp_path: Path) -> None:
case = make_case(tmp_path)
case.backend.fail_new = True
case.backend.fail_old = True
with pytest.raises(c5.InstallationOutcomeUnknown, match="INSTALLATION_OUTCOME_UNKNOWN"):
case.installer.apply(case.contract, case.artifact)
state = json.loads(case.installer._state_path(case.payload).read_text())
assert state["status"] == "UNKNOWN"
assert case.layout.dropin.read_bytes() == case.old_dropin
def test_unknown_reconciliation_proves_old_runtime_and_removes_only_verified_tree(
tmp_path: Path,
) -> None:
case = make_case(tmp_path)
case.backend.fail_new = True
case.backend.fail_old = True
with pytest.raises(c5.InstallationOutcomeUnknown):
case.installer.apply(case.contract, case.artifact)
assert case.root.exists()
reloads = case.backend.reloads
restarts = case.backend.restarts
case.backend.fail_old = False
result = case.installer.reconcile_unknown(case.contract, case.artifact)
assert result["status"] == "ROLLED_BACK"
assert not case.root.exists()
assert case.backend.reloads == reloads
assert case.backend.restarts == restarts
state = json.loads(case.installer._state_path(case.payload).read_text())
nonce = json.loads(case.installer._nonce_path(case.payload).read_text())
assert state == nonce
assert state["status"] == "ROLLED_BACK"
assert state["detail"] == "rollback_runtime_and_bytes_reconciled"
assert case.installer.reconcile_unknown(case.contract, case.artifact)["status"] == (
"IDEMPOTENT_RECONCILIATION"
)
def test_unknown_reconciliation_refuses_tampered_failed_tree(tmp_path: Path) -> None:
case = make_case(tmp_path)
case.backend.fail_new = True
case.backend.fail_old = True
with pytest.raises(c5.InstallationOutcomeUnknown):
case.installer.apply(case.contract, case.artifact)
case.backend.fail_old = False
case.new_module.write_bytes(b"tampered")
with pytest.raises(c5.InstallationError, match="INSTALLED_FILE_MISMATCH"):
case.installer.reconcile_unknown(case.contract, case.artifact)
assert case.root.exists()
state = json.loads(case.installer._state_path(case.payload).read_text())
assert state["status"] == "UNKNOWN"
def test_readiness_retries_transport_only_and_never_contract_mismatch(
tmp_path: Path,
) -> None:
case = make_case(tmp_path)
transport = c5.InstallationError("STAGING_READINESS_HTTP_FAILED")
case.backend.runtime_results = [transport, transport, dict(case.new_runtime)]
assert case.installer.apply(case.contract, case.artifact)["status"] == "APPLIED"
assert case.backend.runtime_calls == 3
before = case.backend.runtime_calls
case.backend.runtime_results = [{"status": "bad"}, dict(case.new_runtime)]
with pytest.raises(c5.InstallationError, match="STAGING_RUNTIME_CONTRACT_MISMATCH"):
case.installer._verify_readiness(case.payload["readiness"], case.profile)
assert case.backend.runtime_calls == before + 1
def test_interruption_before_mutation_leaves_durable_unknown(tmp_path: Path) -> None:
case = make_case(tmp_path)
original = case.installer._write_state
calls = 0
def interrupt_second_state_write(path, value, *, create):
nonlocal calls
calls += 1
if calls == 2:
raise RuntimeError("simulated process interruption")
return original(path, value, create=create)
case.installer._write_state = interrupt_second_state_write
with pytest.raises(RuntimeError, match="simulated process interruption"):
case.installer.apply(case.contract, case.artifact)
state = json.loads(case.installer._state_path(case.payload).read_text())
assert state["status"] == "UNKNOWN"
assert state["detail"] == "mutation_may_start"
assert not case.root.exists()
class SigningRunner:
def __init__(self, key: Ed25519PrivateKey) -> None:
self.key = key
self.calls = 0
def __call__(self, command, **kwargs):
self.calls += 1
namespace = command[command.index("--namespace") + 1]
payload_path = Path(command[command.index("--input") + 1])
signature_path = Path(command[command.index("--signature-out") + 1])
key_id_path = Path(command[command.index("--key-id-out") + 1])
signature_path.write_bytes(
sign_sshsig(
self.key, payload_path.read_bytes(), namespace=namespace
).encode("utf-8")
)
key_id_path.write_bytes(
(ssh_fingerprint(self.key.public_key()) + "\n").encode("utf-8")
)
return SimpleNamespace(returncode=0)
def producer_case(tmp_path: Path, ttl: int = 100):
key = Ed25519PrivateKey.generate()
runner = SigningRunner(key)
registry = tmp_path / "producer-registry.json"
write_registry(registry, key)
verifier = c5.RegistryOwnerVerifier(
registry,
owner_signer_id="local-owner-authority",
require_root_owner=False,
)
signer = RegisteredOwnerSignerAdapter(
"test-registered-owner-signer",
key_id=ssh_fingerprint(key.public_key()),
owner_verifier=verifier,
require_root=False,
test_mode=True,
runner=runner,
)
root = c5.RELEASE_BASE / f"c5-source-{SUPPORT}"
module = root / "app" / "aios_chatgpt_bridge_v2_server.py"
activation = root / "source-activation.json"
old_module = Path("/opt/aios/current/app/aios_chatgpt_bridge_v2_server.py")
module_bytes = b"new\n"
activation_bytes = b"{}\n"
old_bytes = b"old\n"
old_paths = ("/opt/aios/current", "/opt/aios/current/app")
dropin = c5._dropin_bytes(root, old_paths)
new_runtime = {
"status": "active", "health": 200, "deployment_state": "STAGING",
"repository": c5.REPOSITORY, "source_module_path": module.as_posix(),
"source_module_sha256": sha(module_bytes),
}
old_runtime = {
"status": "active", "health": 200, "deployment_state": "STAGING",
"repository": c5.REPOSITORY,
}
request = c5.InstallationRequest(
target=c5.BRIDGE_TARGET,
contract_id="c5-producer-01",
owner_signer_id="local-owner-authority",
source_commit=SOURCE,
support_commit=SUPPORT,
files=(
c5.FileRecord(**file_record(module.as_posix(), module_bytes, "release_file")),
c5.FileRecord(**file_record(activation.as_posix(), activation_bytes, "release_file")),
c5.FileRecord(**file_record(c5.DROPIN.as_posix(), dropin, "systemd_dropin")),
),
existing={
"unit_active": True,
"effective_config_sha256": sha(b"effective"),
"dropin": {"exists": True, "sha256": sha(b"old-dropin")},
"pythonpath": list(old_paths),
"source_files": [file_record(old_module.as_posix(), old_bytes, "existing_source")],
"activation_file": None,
"principal_scope_config": None,
},
readiness={
"url": c5.RUNTIME_URL, "timeout_seconds": 3, "unit_active": True,
"pythonpath": [(root / "app").as_posix(), *old_paths],
"activation_file": (root / "source-activation.json").as_posix(),
"principal_scope_config": None,
"module_path": module.as_posix(), "module_sha256": sha(module_bytes),
"runtime_fields": new_runtime,
},
rollback_readiness={
"url": c5.RUNTIME_URL, "timeout_seconds": 3, "unit_active": True,
"pythonpath": list(old_paths), "activation_file": None,
"principal_scope_config": None,
"module_path": old_module.as_posix(), "module_sha256": sha(old_bytes),
"runtime_fields": old_runtime,
},
nonce="nonce-producer-contract-0001",
ttl_seconds=ttl,
)
artifact = tmp_path / "producer.tar"
write_tar(
artifact,
[
("app/aios_chatgpt_bridge_v2_server.py", module_bytes, 0o644, "file"),
("source-activation.json", activation_bytes, 0o644, "file"),
("dropin.conf", dropin, 0o644, "file"),
],
)
return request, artifact, registry, signer, runner
def test_typed_producer_validates_then_signs_and_consumer_verifies(tmp_path: Path) -> None:
request, artifact, registry, signer, runner = producer_case(tmp_path)
outer = c5.issue_signed_contract(
request,
artifact,
signer,
registry,
clock=lambda: NOW,
require_root_owner=False,
)
assert runner.calls == 1
contract = tmp_path / "issued.json"
contract.write_text(json.dumps(outer))
payload, _ = c5.verify_signed_contract(
contract,
registry,
now=NOW,
require_root_owner=False,
)
assert payload["owner_key_id"] == signer.fingerprint
def test_typed_producer_rejects_before_private_signer_call(tmp_path: Path) -> None:
request, artifact, registry, signer, runner = producer_case(tmp_path, ttl=901)
with pytest.raises(c5.InstallationError, match="INSTALL_TTL_OR_NONCE_INVALID"):
c5.issue_signed_contract(
request,
artifact,
signer,
registry,
clock=lambda: NOW,
require_root_owner=False,
)
assert runner.calls == 0
def test_production_cli_has_no_test_backend_or_arbitrary_root() -> None:
source = (
Path(__file__).parents[1] / "scripts" / "c5_staging_installation.py"
).read_text(encoding="utf-8")
assert "--test-backend" not in source
assert "--release-root" not in source
assert c5.DROPIN == Path("/etc/systemd/system") / f"{c5.UNIT}.d" / (
"~" * 32 + "-c5-source.conf"
)
@@ -0,0 +1,79 @@
"""Use the real signed builder, durable release service and approval verifier."""
import json
import pytest
from governed_unattended_release import GovernedUnattendedReleaseRunner
from silent_release_hardening import CapabilityAuthorizationError
from test_silent_release_execution_readiness import (
COMMIT, trusted_service, signed_shadow_approval,
)
def bind_simulated_source(service):
service.source_admission_resolver = lambda source: {
"admission_id": "simulated-source-admission", "source_commit": source,
"tree_oid": "3" * 40, "version": "V2R2C5",
}
def test_real_release_service_and_signed_owner_broker_are_composable(tmp_path):
service, signer = trusted_service(tmp_path)
bind_simulated_source(service)
runner = GovernedUnattendedReleaseRunner(
admission=object(), environment_mode="AUTHORITY", release_service=service,
owner_approval_broker=lambda envelope: signed_shadow_approval(
service, signer, envelope["candidate_id"]
).to_dict(),
)
result = runner.postpublication_rc(
event_id="isolated-integration", source_commit=COMMIT,
source_readback={"authenticated": True, "source_commit": COMMIT, "tree_oid": "3" * 40},
)
assert result["phase"] == "APPROVED", result
assert service.get_release_saga(result["candidate_id"])["state"] == "APPROVED"
assert service.execution_enabled is False
assert runner.deploy(result["candidate_id"])["phase"] == "BLOCKED"
def test_real_verifier_rejects_broker_source_substitution(tmp_path):
service, signer = trusted_service(tmp_path)
bind_simulated_source(service)
broker_calls = []
def broker(envelope):
broker_calls.append(envelope["candidate_id"])
approval = signed_shadow_approval(service, signer, envelope["candidate_id"]).to_dict()
approval["source_commit"] = "f" * 40
return approval
runner = GovernedUnattendedReleaseRunner(
admission=object(), environment_mode="AUTHORITY", release_service=service,
owner_approval_broker=broker,
)
result = runner.postpublication_rc(
event_id="isolated-substitution", source_commit=COMMIT,
source_readback={"authenticated": True, "source_commit": COMMIT, "tree_oid": "3" * 40},
)
assert result["phase"] == "BLOCKED"
assert len(broker_calls) == 1
assert not list(service.approvals_root.glob("*.json"))
@pytest.mark.parametrize("damage", ["missing_verifier", "tampered_attestation"])
def test_durable_acceptance_reverification_fails_closed(tmp_path, damage):
service, _ = trusted_service(tmp_path)
bind_simulated_source(service)
created = service.create_release_intent({"event_id": "acceptance-recheck", "source_commit": COMMIT})
candidate_id = created["candidate_id"]
assert service.verify_release_acceptance(candidate_id)["status"] == "VERIFIED"
if damage == "missing_verifier":
service.builder_attestation_verifier = None
else:
path = service.acceptance_root / (candidate_id + ".json")
record = json.loads(path.read_text())
record["attestation"]["artifact_sha256"] = "f" * 64
path.write_text(json.dumps(record))
with pytest.raises(CapabilityAuthorizationError):
service.verify_release_acceptance(candidate_id)
+255
View File
@@ -0,0 +1,255 @@
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from governed_publication_client import ( # noqa: E402
CommittedTreeSourceReader,
GovernedPublicationClient,
GovernedPublicationError,
)
from governed_unattended_release import SourcePublicationIntent # noqa: E402
COMMIT = "a" * 40
TREE = "b" * 40
BASE = "c" * 40
DIGEST = "d" * 64
def intent() -> SourcePublicationIntent:
return SourcePublicationIntent(
repository="aios/aios", source_commit=COMMIT, source_tree_oid=TREE,
authority_base_oid=BASE, target_branch="agent/c5-publication",
file_allowlist=("active-runtime/app/example.py", "tests/test_example.py"),
requested_capabilities=("governed.write.files", "writer.lease"),
risk="medium", execution_digest=DIGEST, generation=2,
)
def admission(i: SourcePublicationIntent) -> dict[str, object]:
return {
"task_id": i.task_id, "authority_base_oid": BASE,
"target_branch": i.target_branch, "generation": i.generation,
"execution_digest": DIGEST,
}
class Writer:
def __init__(self, *, fail: BaseException | None = None):
self.calls: list[tuple[str, object]] = []
self.fail = fail
self.result = None
def acquire(self, *, request, job_id):
self.calls.append(("acquire", request))
if self.fail:
raise self.fail
return {"session": "opaque"}
def commit_and_readback(self, *, session, request, files, expected_sha256, message):
self.calls.append(("commit", (session, dict(files), dict(expected_sha256), message)))
if self.fail:
raise self.fail
return {"branch_head": "e" * 40}
def submit_task_result(self, *, session, result, idempotency_key):
self.calls.append(("result", (session, dict(result), idempotency_key)))
if self.fail:
raise self.fail
self.result = dict(result)
return {
"task_id": result["task_id"], "status": "VERIFYING",
"claimed_by": "agent/c5", "revision": 8,
"result": dict(result),
}
def client(writer: Writer, files=None, readback=None, *, resolver=None, git=False) -> GovernedPublicationClient:
i = intent()
source = files or {p: p.encode() for p in i.file_allowlist}
evidence = readback or {
"authenticated": True, "task_id": i.task_id,
"authority_base_oid": BASE, "target_branch": i.target_branch,
"generation": i.generation, "execution_digest": DIGEST,
"task_lease": {"task_id": i.task_id, "ref": "tasklease:1"},
"task_principal": {"task_id": i.task_id, "ref": "principal:1"},
"writer_lease": {"task_id": i.task_id, "ref": "writerlease:1"},
"source_oid": COMMIT, "tree_oid": TREE, "commit_oid": COMMIT,
"commit_receipt": {"commit_oid": COMMIT, "tree_oid": TREE},
}
kwargs = {"git_tree_file": (lambda *, tree_oid, path: source[path])}
return GovernedPublicationClient(
writer_executor=writer, **kwargs,
admission_binding=resolver or (lambda **_kwargs: {"task_id": i.task_id, "authority_base_oid": BASE, "target_branch": i.target_branch, "generation": i.generation, "execution_digest": DIGEST, "job_id": "job-c5-1"}),
authenticated_readback=lambda **_kwargs: evidence,
subject="aios-autonomous-admin", manifest_id="manifest:c5",
runtime_fingerprint="runtime:c5", process_start_identity="proc:c5",
)
def test_contract_uses_job_binding_and_immutable_bytes():
writer = Writer()
i = intent()
result = client(writer).publish(i, admission(i))
assert result["job_id"] == "job-c5-1"
assert result["source_oid"] == COMMIT
commit = writer.calls[1][1]
assert commit[1]["active-runtime/app/example.py"] == b"active-runtime/app/example.py"
assert all(isinstance(value, str) for value in commit[2].values())
assert [call[0] for call in writer.calls] == ["acquire", "commit", "result"]
assert result["task_result_submission"]["status"] == "VERIFYING"
task_result = result["task_result"]
assert task_result["candidate_source_oid"] == COMMIT
assert task_result["published_source_oid"] == COMMIT
assert task_result["source_tree_oid"] == TREE
assert result["task_result_idempotency_key"].startswith(
"governed-source-result-"
)
def test_live_commit_receipt_preserved_without_invented_tree_field():
i = intent()
evidence = client(Writer()).publish(i, admission(i))
evidence["commit_receipt"] = {"commit_oid": COMMIT, "authority_oid": COMMIT}
result = client(Writer(), readback=evidence).publish(i, admission(i))
assert result["commit_receipt"] == evidence["commit_receipt"]
assert "tree_oid" not in result["commit_receipt"]
evidence["commit_receipt"]["tree_oid"] = "f" * 40
with pytest.raises(GovernedPublicationError, match="PUBLICATION_COMMIT_BINDING_MISMATCH"):
client(Writer(), readback=evidence).publish(i, admission(i))
def test_file_set_mismatch_fails_before_any_mutation():
writer = Writer()
i = intent()
bad = {i.file_allowlist[0]: b"one"}
with pytest.raises(GovernedPublicationError, match="PUBLICATION_FILE_SET_MISMATCH"):
client(writer, files=bad).publish(i, admission(i))
assert writer.calls == []
def test_workspace_source_reader_is_rejected():
with pytest.raises(ValueError, match="workspace_source_reader_forbidden"):
GovernedPublicationClient(
writer_executor=Writer(), source_files=lambda _intent: {},
admission_binding=lambda **_kwargs: {},
authenticated_readback=lambda **_kwargs: {}, subject="s",
manifest_id="m", runtime_fingerprint="r", process_start_identity="p",
)
def test_committed_tree_source_reader_binds_the_intent_tree():
seen = []
reader = CommittedTreeSourceReader(
lambda *, tree_oid, path: seen.append((tree_oid, path)) or path.encode()
)
values = reader(intent())
assert set(values) == set(intent().file_allowlist)
assert {tree for tree, _path in seen} == {TREE}
def test_transport_failure_is_unknown_and_never_retried():
writer = Writer(fail=TimeoutError("secret transport detail"))
i = intent()
with pytest.raises(GovernedPublicationError, match="PUBLICATION_EFFECT_UNKNOWN"):
client(writer).publish(i, admission(i))
assert [call[0] for call in writer.calls] == ["acquire"]
def test_missing_authenticated_readback_fails_closed():
writer = Writer()
i = intent()
with pytest.raises(GovernedPublicationError, match="PUBLICATION_AUTHENTICATED_READBACK_REQUIRED"):
client(writer, readback={"authenticated": False}).publish(i, admission(i))
def test_real_admission_receipt_has_no_caller_job_field():
writer = Writer()
i = intent()
result = client(writer).publish(i, admission(i))
assert result["job_id"] == "job-c5-1"
def test_committed_tree_reader_is_used_and_commit_receipt_is_mandatory():
writer = Writer()
i = intent()
result = client(writer, git=True).publish(i, admission(i))
assert result["source_oid"] == COMMIT
incomplete = {
"authenticated": True, "task_id": i.task_id,
"authority_base_oid": BASE, "target_branch": i.target_branch,
"generation": i.generation, "execution_digest": DIGEST,
"task_lease": {"task_id": i.task_id},
"task_principal": {"task_id": i.task_id},
"writer_lease": {"task_id": i.task_id},
"source_oid": COMMIT, "tree_oid": TREE, "commit_oid": COMMIT,
}
with pytest.raises(GovernedPublicationError, match="PUBLICATION_COMMIT_RECEIPT_REQUIRED"):
client(writer, git=True, readback=incomplete).publish(i, admission(i))
def test_readback_source_tree_binding_is_required():
writer = Writer()
i = intent()
evidence = {
"authenticated": True, "task_id": i.task_id,
"authority_base_oid": BASE, "target_branch": i.target_branch,
"generation": i.generation, "execution_digest": DIGEST,
"task_lease": {"task_id": i.task_id},
"task_principal": {"task_id": i.task_id},
"writer_lease": {"task_id": i.task_id},
"source_oid": COMMIT, "tree_oid": "f" * 40, "commit_oid": COMMIT,
}
with pytest.raises(GovernedPublicationError, match="PUBLICATION_SOURCE_TREE_BINDING_MISMATCH"):
client(writer, readback=evidence).publish(i, admission(i))
def test_admission_binding_mismatch_precedes_writer_call():
writer = Writer()
i = intent()
bad = {**admission(i), "external_job_id": "job-other" , "task_id": "other"}
with pytest.raises(GovernedPublicationError, match="PUBLICATION_ADMISSION_BINDING_MISMATCH"):
client(writer).publish(i, bad)
assert writer.calls == []
def test_invalid_authenticated_readback_never_submits_task_result():
writer = Writer()
i = intent()
with pytest.raises(
GovernedPublicationError,
match="PUBLICATION_SOURCE_TREE_BINDING_MISMATCH",
):
client(
writer,
readback={
"authenticated": True, "task_id": i.task_id,
"authority_base_oid": BASE, "target_branch": i.target_branch,
"generation": i.generation, "execution_digest": DIGEST,
"task_lease": {"task_id": i.task_id},
"task_principal": {"task_id": i.task_id},
"writer_lease": {"task_id": i.task_id},
"source_oid": COMMIT, "tree_oid": "f" * 40,
"commit_oid": COMMIT,
},
).publish(i, admission(i))
assert [call[0] for call in writer.calls] == ["acquire", "commit"]
def test_result_transport_failure_is_unknown_and_not_retried():
class ResultFailureWriter(Writer):
def submit_task_result(self, **kwargs):
self.calls.append(("result", kwargs))
raise TimeoutError("lost response")
writer = ResultFailureWriter()
i = intent()
with pytest.raises(GovernedPublicationError, match="PUBLICATION_EFFECT_UNKNOWN"):
client(writer).publish(i, admission(i))
assert [call[0] for call in writer.calls] == ["acquire", "commit", "result"]
@@ -0,0 +1,271 @@
from __future__ import annotations
import json
import os
from pathlib import Path
import tempfile
import unittest
from types import SimpleNamespace
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from aios_single_host_authority import (
LocalOwnerAuthority,
RegistryOwnerApprovalVerifier,
SingleHostAuthorityError,
SingleHostOwnerPolicy,
SourcePublicationPolicyRequest,
ssh_fingerprint,
ssh_public_line,
)
from governed_source_publication_policy import (
ScopedSourcePublicationPolicyAdmission,
SourcePublicationPolicyVerifier,
require_source_publication_epoch_scope,
)
from aios_task_admission_bootstrap import TaskAdmissionPolicy
class SourcePublicationPolicyTests(unittest.TestCase):
now = 2_000_000_000
def setUp(self) -> None:
self.tmp = tempfile.TemporaryDirectory()
root = Path(self.tmp.name)
self.key = Ed25519PrivateKey.generate()
self.key_path = root / "owner.key"
self.key_path.write_bytes(
self.key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,
serialization.NoEncryption(),
)
)
os.chmod(self.key_path, 0o600)
self.registry = root / "registry.json"
self.registry.write_text(json.dumps({
"schema": "aios.single-host-signer-registry.v1",
"authority_generation": 1,
"signers": [{
"signer_id": "local-owner-authority", "role": "OWNER_AUTHORITY",
"provider": "local_unix_service", "fingerprint": ssh_fingerprint(self.key.public_key()),
"public_key": ssh_public_line(self.key.public_key(), "owner"),
"socket": "/run/aios/owner.sock", "secret_ref": "aios-secret://owner", "status": "ACTIVE",
"generation": 1,
"parent_signer_id": "", "parent_fingerprint": "",
}],
}), encoding="utf-8")
self.registry.chmod(0o600)
self.policy = SingleHostOwnerPolicy(
repository="aios/aios", trust_domain="owner",
allowed_branch_prefixes=("agent/",),
allowed_capabilities=frozenset({
"agent-task:control", "governed.task.create", "governed.write.files",
"task.principal.issue", "writer.lease",
}),
allowed_file_prefixes=("active-runtime/app/", "tests/"),
risk_ceiling="high", production_scope="none", destructive_policy="forbidden",
maximum_epoch_ttl_seconds=3600, maximum_machine_capability_ttl_seconds=3600,
allowed_caller_uids=(0,),
)
self.owner = LocalOwnerAuthority(self.key_path, self.policy, require_root_owner=False, clock=lambda: self.now)
self.request = SourcePublicationPolicyRequest(
policy_id="C5_SOURCE_PUBLICATION_ONLY", repository="aios/aios",
authority_base_oid="a" * 40, source_oid="b" * 40, source_tree_oid="c" * 40,
target_agent="agent/c5", target_branch="agent/c5", generation=1,
execution_digest="d" * 64, exact_files=("active-runtime/app/example.py",),
nonce="nonce-c5-1", ttl_seconds=600,
)
self.artifact = self.owner.issue_source_publication_policy(self.request)
self.verifier = SourcePublicationPolicyVerifier(
RegistryOwnerApprovalVerifier(self.registry, require_root_owner=False),
revocation_check=lambda _policy: True,
clock=lambda: self.now,
)
def tearDown(self) -> None:
self.tmp.cleanup()
def test_real_signature_and_exact_scope(self) -> None:
self.assertTrue(self.verifier.verify(self.artifact, self.request))
admission = ScopedSourcePublicationPolicyAdmission(self.verifier)
projected = admission.project_task_admission_policy(self.artifact, self.request)
self.assertEqual(projected.max_risk, "engineering")
self.assertEqual(projected.exact_files, self.request.exact_files)
def test_tamper_expiry_revoke_missing_and_scope_widening(self) -> None:
changed = dict(self.artifact.payload)
changed["target_branch"] = "agent/wider"
tampered = type(self.artifact)(changed, self.artifact.canonical_payload, self.artifact.signature, self.artifact.key_id, self.artifact.namespace)
self.assertFalse(self.verifier.verify(tampered, self.request))
expired = SourcePublicationPolicyVerifier(
RegistryOwnerApprovalVerifier(self.registry, require_root_owner=False),
revocation_check=lambda _policy: True, clock=lambda: self.now + 601,
)
self.assertFalse(expired.verify(self.artifact, self.request))
revoked = SourcePublicationPolicyVerifier(
RegistryOwnerApprovalVerifier(self.registry, require_root_owner=False),
revocation_check=lambda _policy: False, clock=lambda: self.now,
)
self.assertFalse(revoked.verify(self.artifact, self.request))
missing = SourcePublicationPolicyVerifier(
RegistryOwnerApprovalVerifier(self.registry, require_root_owner=False),
revocation_check=None, clock=lambda: self.now,
)
self.assertFalse(missing.verify(self.artifact, self.request))
widened = SourcePublicationPolicyRequest(**{**self.request.__dict__, "exact_files": ("outside/secret",)}) if hasattr(self.request, "__dict__") else SourcePublicationPolicyRequest(
**{field: getattr(self.request, field) for field in self.request.__dataclass_fields__ if field != "exact_files"}, exact_files=("outside/secret",)
)
self.assertFalse(self.verifier.verify(self.artifact, widened))
def test_unknown_fields_and_medium_ceiling_reject_engineering(self) -> None:
unknown = dict(self.artifact.payload)
unknown["unexpected"] = True
artifact = type(self.artifact)(unknown, self.artifact.canonical_payload, self.artifact.signature, self.artifact.key_id, self.artifact.namespace)
self.assertFalse(self.verifier.verify(artifact, self.request))
medium = SingleHostOwnerPolicy(
**{**self.policy.to_dict(), "allowed_capabilities": self.policy.allowed_capabilities, "risk_ceiling": "medium", "schema": self.policy.schema}
)
with self.assertRaises(SingleHostAuthorityError):
LocalOwnerAuthority(self.key_path, medium, require_root_owner=False, clock=lambda: self.now).issue_source_publication_policy(self.request)
narrow_caps = SingleHostOwnerPolicy(
**{**self.policy.to_dict(), "allowed_capabilities": frozenset({"governed.task.create"}), "schema": self.policy.schema}
)
with self.assertRaises(SingleHostAuthorityError):
LocalOwnerAuthority(self.key_path, narrow_caps, require_root_owner=False, clock=lambda: self.now).issue_source_publication_policy(self.request)
def test_owner_verifier_failure_is_fail_closed(self) -> None:
class BrokenVerifier:
def verify(self, **_kwargs):
raise RuntimeError("verifier unavailable")
verifier = SourcePublicationPolicyVerifier(
BrokenVerifier(), revocation_check=lambda _policy: True, clock=lambda: self.now,
)
self.assertFalse(verifier.verify(self.artifact, self.request))
def test_epoch_scope_requires_v2_and_exact_c5_provenance(self) -> None:
matching = SimpleNamespace(
schema="aios.owner-delegation-epoch.v2",
production_scope="none", destructive_policy="forbidden",
policy_digest=self.artifact.payload["policy_digest"],
repository=self.artifact.payload["repository"],
trust_domain=self.artifact.payload["trust_domain"],
)
require_source_publication_epoch_scope(matching, self.artifact.payload)
with self.assertRaisesRegex(SingleHostAuthorityError, "SCHEMA_REQUIRED"):
require_source_publication_epoch_scope(
SimpleNamespace(schema="aios.owner-delegation-epoch.v1"),
self.artifact.payload,
)
for field, value in (("production_scope", "staging"), ("destructive_policy", "rollback-only")):
changed = dict(matching.__dict__, **{field: value})
with self.assertRaisesRegex(SingleHostAuthorityError, "SCOPE_MISMATCH"):
require_source_publication_epoch_scope(
SimpleNamespace(**changed), self.artifact.payload,
)
def test_admission_and_status_reject_v1_epoch_provenance(self) -> None:
artifact = self.artifact
request = self.request
class Bootstrap:
def __init__(self):
self.policy = TaskAdmissionPolicy(
policy_digest=artifact.payload["policy_digest"],
trust_domain=artifact.payload["trust_domain"],
)
self.envelope = SimpleNamespace(as_dict=lambda: {
"repository": request.repository,
"authority_base_oid": request.authority_base_oid,
"target_branch": request.target_branch,
"generation": request.generation,
"execution_digest": request.execution_digest,
"risk": request.risk,
"requested_capabilities": list(artifact.payload["capabilities"]),
"file_allowlist": list(request.exact_files),
"policy_digest": artifact.payload["policy_digest"],
"trust_domain": artifact.payload["trust_domain"],
"task_id": "task-c5",
})
def _load_envelope(self, _envelope_id):
return self.envelope
def _verify_epoch(self, **_kwargs):
return SimpleNamespace(
schema="aios.owner-delegation-epoch.v1",
)
def authorize_and_admit(self, _raw):
return {"admitted": True}
def status(self, _envelope_id):
return {"status": "QUEUED"}
bootstrap = Bootstrap()
scoped = ScopedSourcePublicationPolicyAdmission(
self.verifier, bootstrap=bootstrap, artifact=artifact,
request=request, expected_agent_id=request.target_agent,
)
raw = {"envelope_id": "env-c5", "authorization_kind": "delegation_epoch",
"delegation_epoch_id": "epoch-c5"}
with self.assertRaisesRegex(SingleHostAuthorityError, "SCHEMA_REQUIRED"):
scoped.authorize_and_admit(raw)
with self.assertRaisesRegex(SingleHostAuthorityError, "SCHEMA_REQUIRED"):
scoped.status({"envelope_id": "env-c5", "delegation_epoch_id": "epoch-c5"})
def test_bootstrap_wrapper_rechecks_each_call_and_blocks_owner_bypass(self) -> None:
class Bootstrap:
def __init__(self) -> None:
self.created = 0
self.admitted = 0
self.policy = TaskAdmissionPolicy(policy_digest=self_artifact.payload["policy_digest"], trust_domain="owner")
self.envelope = SimpleNamespace(as_dict=lambda: {
"repository": "aios/aios", "authority_base_oid": "a" * 40,
"target_branch": "agent/c5", "generation": 1,
"execution_digest": "d" * 64, "risk": "engineering",
"requested_capabilities": list(self_artifact.payload["capabilities"]),
"file_allowlist": list(self_artifact.payload["exact_files"]),
})
def create_envelope(self, raw):
self.created += 1
return {"ok": True}
def _load_envelope(self, _envelope_id):
return self.envelope
def authorize_and_admit(self, raw):
self.admitted += 1
return {"admitted": True}
self_artifact = self.artifact
bootstrap = Bootstrap()
scoped = ScopedSourcePublicationPolicyAdmission(
self.verifier, bootstrap=bootstrap, artifact=self.artifact, request=self.request,
expected_agent_id="agent/c5",
)
raw = {
"idempotency_key": "idem-c5", "task_id": "task-c5", "repository": "aios/aios",
"authority_base_oid": "a" * 40, "target_branch": "agent/c5", "generation": 1,
"execution_digest": "d" * 64, "risk": "engineering",
"requested_capabilities": list(self.artifact.payload["capabilities"]),
"file_allowlist": list(self.artifact.payload["exact_files"]),
}
self.assertEqual(scoped.create_envelope(raw), {"ok": True})
self.assertEqual(bootstrap.created, 1)
self.assertTrue(scoped.authorize(self.artifact, self.request))
agent_mismatch = ScopedSourcePublicationPolicyAdmission(
self.verifier, bootstrap=bootstrap, artifact=self.artifact,
request=self.request, expected_agent_id="agent/other",
)
self.assertFalse(agent_mismatch.authorize(self.artifact, self.request))
widened = dict(raw, target_branch="agent/other")
with self.assertRaises(SingleHostAuthorityError):
scoped.create_envelope(widened)
with self.assertRaises(SingleHostAuthorityError):
scoped.authorize_and_admit({"envelope_id": "e", "authorization_kind": "owner_signature"})
if __name__ == "__main__":
unittest.main()
+268
View File
@@ -0,0 +1,268 @@
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from governed_unattended_release import ( # noqa: E402
GovernedUnattendedReleaseRunner,
SourcePublicationIntent,
UnattendedReleaseError,
)
COMMIT = "a" * 40
TREE = "b" * 40
BASE = "c" * 40
DIGEST = "d" * 64
def receipt():
request = intent()
return {"schema": "aios.task-admission-receipt.v1", "task_id": request.task_id,
"authority_base_oid": BASE, "target_branch": request.target_branch,
"generation": 1, "execution_digest": DIGEST, "task_state": "QUEUED",
"envelope_id": "env-1", "grant_id": "grant-1"}
def intent(**changes):
value = dict(
repository="aios/aios",
source_commit=COMMIT,
source_tree_oid=TREE,
authority_base_oid=BASE,
target_branch="agent/c5-unattended-runner-20260926",
file_allowlist=("active-runtime/app/foo.py", "tests/test_foo.py"),
requested_capabilities=("release.candidate", "source.publish"),
risk="low",
execution_digest=DIGEST,
event_id="release-event-1",
version="v2r2c5",
)
value.update(changes)
return SourcePublicationIntent(**value)
def test_existing_plan_task_and_idempotency_identity_are_preserved():
value = intent(admission_task_id="TASK-V2R2C5-SOURCE-PUBLICATION-20260926-R1",
admission_idempotency_key="c5-source-publication-20260926-r1-1ba534d551991e8c9901")
assert value.admission_request()["task_id"] == value.admission_task_id
assert value.admission_request()["idempotency_key"] == value.admission_idempotency_key
assert SourcePublicationIntent(**value.as_dict()).admission_request() == value.admission_request()
with pytest.raises(UnattendedReleaseError, match="INCOMPLETE_ADMISSION_IDENTITY"):
intent(admission_task_id=value.admission_task_id)
class Admission:
def __init__(self, create=None, authorize=None, status=None):
self.create_calls = self.authorize_calls = self.status_calls = 0
self.create_error = create
self.authorize_error = authorize
self.status_value = status or {"status": "ADMITTED", "task": receipt()}
def create_envelope(self, raw):
self.create_calls += 1
if self.create_error:
raise self.create_error
return {"envelope": {"envelope_id": "env-1"}, "request": raw}
def authorize_and_admit(self, raw):
self.authorize_calls += 1
if self.authorize_error:
raise self.authorize_error
return receipt()
def status(self, envelope_id):
self.status_calls += 1
return self.status_value
class Failure(RuntimeError):
def __init__(self, code, status_code=None):
super().__init__(code)
self.code = code
self.status_code = status_code
def runner(admission, **kwargs):
return GovernedUnattendedReleaseRunner(
admission=admission,
environment_mode=kwargs.pop("environment_mode", "AUTHORITY"),
max_attempts=3,
deadline_seconds=10,
sleep=lambda _seconds: None,
**kwargs,
)
def test_intake_is_immutable_and_deterministic():
first = intent(file_allowlist=("tests/test_foo.py", "active-runtime/app/foo.py"), requested_capabilities=("source.publish", "release.candidate"))
second = intent()
assert first.task_id == second.task_id
assert first.idempotency_key == second.idempotency_key
with pytest.raises(Exception):
first.file_allowlist += ("x",)
def test_admission_reconciles_server_error_without_duplicate_mutation():
admission = Admission(authorize=Failure("TEMPORARY_FAILURE", 503))
result = runner(admission).admit(intent(), {"authorization_kind": "delegation_epoch", "delegation_epoch_id": "epoch-1"})
assert result["phase"] == "RECONCILED"
assert admission.create_calls == 1
assert admission.authorize_calls == 1
assert admission.status_calls == 1
def test_admission_does_not_retry_forbidden():
admission = Admission(create=Failure("FORBIDDEN", 403))
result = runner(admission).admit(intent(), {"authorization_kind": "delegation_epoch", "delegation_epoch_id": "epoch-1"})
assert result["phase"] == "BLOCKED"
assert admission.create_calls == 1
def test_unknown_mutation_outcome_reconciles_before_resubmit():
admission = Admission(authorize=Failure("CONNECTION_LOST"))
result = runner(admission).admit(intent(), {"authorization_kind": "delegation_epoch", "delegation_epoch_id": "epoch-1"})
assert result["phase"] == "RECONCILED"
assert admission.authorize_calls == 1
assert admission.status_calls == 1
def test_publication_requires_full_lease_chain_and_exact_rc_binding():
class Adapter:
def publish(self, _intent, _receipt):
return {"task_lease": {"task_id": _receipt_task}, "task_principal": {"task_id": _receipt_task}, "writer_lease": {"task_id": _receipt_task},
"commit_receipt": {"task_id": _receipt_task, "commit_oid": "e" * 40, "tree_oid": TREE,
"base_oid": BASE, "target_branch": _receipt_branch, "execution_digest": DIGEST},
"source_oid": "e" * 40, "tree_oid": TREE, "authenticated": True, "task_id": _receipt_task,
"authority_base_oid": BASE, "target_branch": _receipt_branch, "execution_digest": DIGEST, "generation": 1}
_receipt_task = intent().task_id
_receipt_branch = intent().target_branch
admitted = receipt()
result = runner(Admission(), publication_adapter=Adapter()).publish(intent(), admitted)
assert result["phase"] == "PUBLISHED"
class LiveReceipt(Adapter):
def publish(self, _intent, _receipt):
value = super().publish(_intent, _receipt)
value["commit_receipt"] = {"commit_oid": "e" * 40, "authority_oid": "e" * 40,
"git_remote_oid": "e" * 40, "api_branch_oid": "e" * 40,
"remote_verified": True, "commit_visible": True}
return value
result = runner(Admission(), publication_adapter=LiveReceipt()).publish(intent(), admitted)
assert result["phase"] == "PUBLISHED"
assert "tree_oid" not in result["readback"]["commit_receipt"]
class WrongTree(LiveReceipt):
def publish(self, _intent, _receipt):
value = super().publish(_intent, _receipt)
value["tree_oid"] = "f" * 40
return value
assert runner(Admission(), publication_adapter=WrongTree()).publish(intent(), admitted)["phase"] == "BLOCKED"
class Missing:
def publish(self, _intent, _receipt):
return {"source_oid": COMMIT, "tree_oid": TREE}
assert runner(Admission(), publication_adapter=Missing()).publish(intent(), admitted)["error"] == "PUBLICATION_LEASE_CHAIN_MISSING"
def test_unknown_pending_is_not_success_and_does_not_retry():
admission = Admission(authorize=TimeoutError(), status={"status": "PENDING_AUTHORIZATION", "task": None})
result = runner(admission).admit(intent(), {"authorization_kind": "delegation_epoch", "delegation_epoch_id": "epoch-1"})
assert result["phase"] == "RECONCILIATION_REQUIRED"
assert admission.authorize_calls == 1
def test_engineering_and_default_simulation_are_explicit():
assert intent(risk="engineering").admission_request()["risk"] == "engineering"
admission = Admission()
r = runner(admission, environment_mode="SIMULATION")
assert r.admit(intent(), {})["phase"] == "BLOCKED"
admission.simulation_only = True
assert r.admit(intent(), {})["mode"] == "SIMULATION"
assert r.publish(intent(), receipt())["phase"] == "BLOCKED"
def test_deadline_prevents_call_and_exception_message_is_not_exported():
ticks = iter([0, 11])
admission = Admission()
assert runner(admission, clock=lambda: next(ticks)).admit(intent(), {})["phase"] == "BLOCKED"
assert admission.create_calls == 0
error = runner(Admission(create=RuntimeError("Bearer private-test-material"))).admit(intent(), {})
assert "private-test-material" not in str(error)
def test_pre_envelope_transient_failure_retries_same_identity():
class Flaky(Admission):
def create_envelope(self, raw):
if self.create_calls == 0:
self.create_calls += 1
self.first_raw = raw
raise Failure("UNAVAILABLE", 503)
assert raw == self.first_raw
return super().create_envelope(raw)
admission = Flaky()
assert runner(admission).admit(intent(), {})["phase"] == "ADMITTED"
assert admission.create_calls == 2
assert admission.authorize_calls == 1
def test_rc_missing_approval_backend_is_blocked_and_preparation_cannot_deploy():
class Release:
def create_release_intent(self, payload):
return {"candidate_id": "rc-1", "payload": payload}
def get_approval_envelope(self, candidate_id):
return {"candidate_id": candidate_id, "status": "READY_FOR_OWNER_SIGNATURE"}
def get_release_candidate(self, candidate_id):
return {"candidate_id": candidate_id, "source_commit": COMMIT}
def get_release_plan(self, candidate_id):
return {"candidate_id": candidate_id, "deployment": {"source_commit": COMMIT, "tree_oid": TREE}}
def get_acceptance(self, candidate_id):
return {"candidate_id": candidate_id, "status": "ACCEPTED"}
service = Release()
r = runner(Admission(), release_service=service)
assert r.postpublication_rc(event_id="release-event-1", source_commit=COMMIT, source_readback={"authenticated": True, "source_commit": COMMIT, "tree_oid": TREE})["error"] == "AUTHENTICATED_SOURCE_ADMISSION_REQUIRED"
assert r.deploy("rc-1")["error"] == "DEPLOYMENT_NOT_ENABLED"
def test_rc_cannot_replace_authenticated_source_with_caller_flag():
class Release:
def __init__(self):
self.approval = None
def create_release_intent(self, payload):
return {"candidate_id": "rc-1", "payload": payload}
def get_approval_envelope(self, candidate_id):
return {"candidate_id": candidate_id, "candidate_fingerprint": "fp", "status": "READY_FOR_OWNER_SIGNATURE"}
def get_release_candidate(self, candidate_id):
return {"candidate_id": candidate_id, "source_commit": COMMIT}
def get_release_plan(self, candidate_id):
return {"candidate_id": candidate_id, "deployment": {"source_commit": COMMIT, "tree_oid": TREE}}
def get_acceptance(self, candidate_id):
return {"candidate_id": candidate_id, "status": "ACCEPTED"}
def submit_owner_approval(self, payload):
self.approval = payload
return {"status": "VERIFIED_AND_STORED"}
service = Release()
result = runner(Admission(), release_service=service, owner_approval_broker=lambda envelope: {"candidate_id": envelope["candidate_id"], "signature": "signed-by-owner"}).postpublication_rc(event_id="release-event-1", source_commit=COMMIT, source_readback={"authenticated": True, "source_commit": COMMIT, "tree_oid": TREE})
assert result["phase"] == "BLOCKED"
assert service.approval is None
@@ -0,0 +1,134 @@
from __future__ import annotations
from pathlib import Path
import tempfile
import unittest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from aios_single_host_authority import (
LocalOwnerAuthority,
OwnerEpochRequest,
SingleHostOwnerPolicy,
sign_sshsig,
ssh_fingerprint,
verify_sshsig,
)
from registered_owner_signer_adapter import (
OWNER_NAMESPACE,
RegisteredOwnerSignerAdapter,
RegisteredOwnerSignerError,
)
class RegisteredOwnerSignerTests(unittest.TestCase):
def setUp(self) -> None:
self.key = Ed25519PrivateKey.generate()
self.key_id = ssh_fingerprint(self.key.public_key())
self.payloads: list[bytes] = []
class Verifier:
def verify(inner, *, namespace, payload, signature, key_id):
return key_id == self.key_id and verify_sshsig(
self.key.public_key(), payload, signature,
namespace=namespace, expected_key_id=key_id,
)
self.owner_verifier = Verifier()
def runner(argv, **_kwargs):
input_path = Path(argv[argv.index("--input") + 1])
signature_path = Path(argv[argv.index("--signature-out") + 1])
key_id_path = Path(argv[argv.index("--key-id-out") + 1])
payload = input_path.read_bytes()
self.payloads.append(payload)
signature_path.write_bytes(sign_sshsig(self.key, payload, namespace=OWNER_NAMESPACE).encode())
key_id_path.write_bytes((self.key_id + "\n").encode())
return type("Result", (), {"returncode": 0})()
self.adapter = RegisteredOwnerSignerAdapter(
command="registered-signer-test", key_id=self.key_id,
owner_verifier=self.owner_verifier, require_root=False,
test_mode=True, runner=runner,
)
def policy(self) -> SingleHostOwnerPolicy:
return SingleHostOwnerPolicy(
repository="aios/aios", trust_domain="owner",
allowed_branch_prefixes=("agent/",),
allowed_capabilities=frozenset({"agent-task:control"}),
allowed_file_prefixes=("active-runtime/app/",), risk_ceiling="high",
production_scope="none", destructive_policy="forbidden",
maximum_epoch_ttl_seconds=3600,
maximum_machine_capability_ttl_seconds=3600,
allowed_caller_uids=(0,),
)
def test_typed_authority_uses_registered_signer_and_preserves_payload(self) -> None:
authority = LocalOwnerAuthority(
None, self.policy(), require_root_owner=False,
clock=lambda: 2_000_000_000, registered_signer=self.adapter,
)
request = OwnerEpochRequest(
epoch_id="epoch-registered", allowed_branch_prefixes=("agent/c5/",),
allowed_capabilities=frozenset({"agent-task:control"}),
allowed_file_prefixes=("active-runtime/app/example.py",),
risk_ceiling="high", business_goal="typed test",
production_scope="none", destructive_policy="forbidden", ttl_seconds=600,
)
artifact = authority.issue_epoch(request)
self.assertEqual(artifact.key_id, self.key_id)
self.assertEqual(artifact.canonical_payload, self.payloads[0])
self.assertEqual(artifact.signature.splitlines()[0], "-----BEGIN SSH SIGNATURE-----")
def test_adapter_has_no_public_raw_sign_method_and_rejects_wrong_outputs(self) -> None:
self.assertFalse(hasattr(self.adapter, "sign"))
with self.assertRaises(RegisteredOwnerSignerError):
self.adapter._sign_canonical_payload("wrong-namespace", b"{}")
def bad_runner(argv, **_kwargs):
Path(argv[argv.index("--signature-out") + 1]).write_bytes(b"-----BEGIN SSH SIGNATURE-----\n")
Path(argv[argv.index("--key-id-out") + 1]).write_bytes(b"wrong\n")
return type("Result", (), {"returncode": 0})()
bad = RegisteredOwnerSignerAdapter(
command="registered-signer-test", key_id=self.key_id,
owner_verifier=self.owner_verifier, require_root=False,
test_mode=True, runner=bad_runner,
)
with self.assertRaises(RegisteredOwnerSignerError):
bad._sign_canonical_payload(OWNER_NAMESPACE, b"{}")
with self.assertRaises(RegisteredOwnerSignerError):
RegisteredOwnerSignerAdapter(
command="relative-signer", key_id=self.key_id,
owner_verifier=self.owner_verifier, require_root=False,
)
def test_typed_policy_rejection_does_not_call_runner(self) -> None:
calls = 0
def counting_runner(*_args, **_kwargs):
nonlocal calls
calls += 1
raise AssertionError("runner must not be called")
adapter = RegisteredOwnerSignerAdapter(
command="registered-signer-test", key_id=self.key_id,
owner_verifier=self.owner_verifier, require_root=False,
test_mode=True, runner=counting_runner,
)
authority = LocalOwnerAuthority(
None, self.policy(), require_root_owner=False,
clock=lambda: 2_000_000_000, registered_signer=adapter,
)
request = OwnerEpochRequest(
epoch_id="epoch-widened", allowed_branch_prefixes=("agent/evil",),
allowed_capabilities=frozenset({"writer.lease"}),
allowed_file_prefixes=("active-runtime/app/",), risk_ceiling="high",
business_goal="typed test", production_scope="none",
destructive_policy="forbidden", ttl_seconds=600,
)
with self.assertRaises(Exception):
authority.issue_epoch(request)
self.assertEqual(calls, 0)
if __name__ == "__main__":
unittest.main()
+117
View File
@@ -0,0 +1,117 @@
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
from aios_task_admission_bootstrap import TaskAdmissionError
from aios_task_admission_live_adapters import TaskAdmissionRuntimeBundle
from source_publication_runtime import AdmissionRuntimeRouter, ScopedPrincipalProvider, expired_admission_runtime, ExistingPrincipalPolicyFence
def router_fixture():
names = ("getTaskAdmissionEnvelope", "submitTaskAdmissionAuthorization", "getTaskAdmissionStatus")
store = Mock()
store.get_envelope.return_value = None
store.get_grant_by_task_id.return_value = None
bootstrap = SimpleNamespace(store=store, _load_envelope=Mock())
guard = SimpleNamespace(bootstrap=bootstrap, artifact=SimpleNamespace(payload={"target_branch": "agent/c5"}))
ordinary = TaskAdmissionRuntimeBundle({op: Mock(return_value="ordinary") for op in names}, Mock())
source = TaskAdmissionRuntimeBundle({op: Mock(return_value="source") for op in names}, Mock(), service=guard)
return AdmissionRuntimeRouter(ordinary, source), ordinary, source, bootstrap
def test_exact_branch_is_routed_without_changing_ordinary_policy():
router, ordinary, source, _ = router_fixture()
route = router.handlers()["getTaskAdmissionEnvelope"]
assert route({"target_branch": "agent/ordinary", "risk": "engineering"}) == "ordinary"
assert route({"target_branch": "agent/c5", "risk": "engineering"}) == "source"
ordinary.handlers["getTaskAdmissionEnvelope"].assert_called_once()
source.handlers["getTaskAdmissionEnvelope"].assert_called_once()
def test_source_denial_does_not_fall_through_to_ordinary():
router, ordinary, source, _ = router_fixture()
source.handlers["getTaskAdmissionEnvelope"].side_effect = TaskAdmissionError("REVOKED", 403)
with pytest.raises(TaskAdmissionError, match="REVOKED"):
router.handlers()["getTaskAdmissionEnvelope"]({"target_branch": "agent/c5"})
ordinary.handlers["getTaskAdmissionEnvelope"].assert_not_called()
def test_authorization_routes_by_durable_envelope_not_caller_epoch():
router, ordinary, source, bootstrap = router_fixture()
bootstrap.store.get_envelope.return_value = {"present": True}
bootstrap._load_envelope.return_value = SimpleNamespace(target_branch="agent/c5")
request = {"envelope_id": "env-1", "delegation_epoch_id": "attacker-epoch"}
assert router.handlers()["submitTaskAdmissionAuthorization"](request) == "source"
ordinary.handlers["submitTaskAdmissionAuthorization"].assert_not_called()
def test_revoked_source_policy_fences_already_admitted_principal():
guarded = Mock()
guarded.status.side_effect = TaskAdmissionError("POLICY_REVOKED", 403)
delegate = Mock(epoch_id="epoch-1")
delegate.store.get_grant_by_task_id.return_value = SimpleNamespace(
parent_epoch_id="epoch-1", envelope_id="env-1")
provider = ScopedPrincipalProvider(guarded, delegate)
with pytest.raises(TaskAdmissionError, match="POLICY_REVOKED"):
provider({"operation": "commitFilesGoverned"}, {"task_id": "task-1"})
delegate.assert_not_called()
def test_principal_router_uses_durable_task_scope():
router, ordinary, source, bootstrap = router_fixture()
bootstrap.store.get_grant_by_task_id.return_value = SimpleNamespace(target_branch="agent/c5")
transport = {"operation": "acquireWriterLease"}
request = {"task_id": "task-1", "target_branch": "agent/ordinary"}
router(transport, request)
source.principal_verifier.assert_called_once_with(transport, request)
ordinary.principal_verifier.assert_not_called()
def test_expired_ordinary_epoch_remains_closed():
runtime = expired_admission_runtime()
for handler in runtime.handlers.values():
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_EXPIRED"):
handler({})
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_EXPIRED"):
runtime.principal_verifier({}, {})
assert runtime.principal_verifier.readiness()["ready"] is False
def test_existing_signed_principal_verifier_is_preserved_and_policy_fenced():
_, _, source, bootstrap = router_fixture()
source.service.status = Mock()
bootstrap.store.get_grant_by_task_id.return_value = SimpleNamespace(
target_branch="agent/c5", envelope_id="env", parent_epoch_id="epoch")
original = Mock(side_effect=TaskAdmissionError("SIGNED_PRINCIPAL_REQUIRED", 403))
fenced = ExistingPrincipalPolicyFence(original, source)
with pytest.raises(TaskAdmissionError, match="SIGNED_PRINCIPAL_REQUIRED"):
fenced({}, {"task_id": "c5-task"})
source.service.status.assert_called_once()
original.assert_called_once()
original.reset_mock()
source.service.status.side_effect = TaskAdmissionError("SOURCE_POLICY_REVOKED", 403)
with pytest.raises(TaskAdmissionError, match="SOURCE_POLICY_REVOKED"):
fenced({}, {"task_id": "c5-task"})
original.assert_not_called()
@pytest.mark.parametrize("files", [
{"a.py": "dGFtcGVyZWQ="},
{"a.py": "eA==", "extra.py": "eA=="},
[{"path": "a.py", "content_base64": "eA=="}, {"path": "a.py", "content_base64": "eA=="}],
])
def test_source_commit_rejects_content_drift_before_existing_verifier(files):
import hashlib
_, _, source, bootstrap = router_fixture()
source.service.status = Mock()
source.service.expected_file_sha256 = {"a.py": hashlib.sha256(b"x").hexdigest()}
bootstrap.store.get_grant_by_task_id.return_value = SimpleNamespace(
target_branch="agent/c5", envelope_id="env", parent_epoch_id="epoch")
original = Mock()
fenced = ExistingPrincipalPolicyFence(original, source)
with pytest.raises(TaskAdmissionError, match="SOURCE_COMMIT"):
fenced({"operation": "commitFilesGoverned"}, {"task_id": "c5-task", "files": files})
original.assert_not_called()
fenced({"operation": "commitFilesGoverned"}, {"task_id": "c5-task", "files": {"a.py": "eA=="}})
original.assert_called_once()
+174
View File
@@ -0,0 +1,174 @@
from __future__ import annotations
import json
import pytest
from aios_task_admission_bootstrap import (
SCHEMA_EPOCH_V2,
TaskAdmissionError,
canonical_json_bytes,
sha256_json,
)
from aios_task_admission_live_adapters import SignedEpochDirectoryProvider
class _Verifier:
def __init__(self) -> None:
self.calls: list[dict] = []
def verify(self, **kwargs):
self.calls.append(kwargs)
return True
def _value(now: int = 1_700_000_000) -> dict:
value = {
"epoch_id": "owner-epoch-v2-test-20260926",
"owner_identity": "aios-task-grant-signer",
"trust_domain": "aios.owner",
"repository": "aios/aios",
"allowed_branch_prefixes": ["agent/p0-test"],
"allowed_capabilities": ["git.read", "governed.write.file"],
"allowed_file_prefixes": ["active-runtime/app/example.py"],
"risk_ceiling": "engineering",
"issued_at": now - 1,
"not_before": now - 1,
"expires_at": now + 600,
"revocation_epoch": 0,
"policy_digest": "a" * 64,
"business_goal": "test",
"production_scope": "staging",
"destructive_policy": "forbidden",
"signature_namespace": "aios-owner-approval-v1",
"signature_key_id": "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/",
"status": "ACTIVE",
"schema": SCHEMA_EPOCH_V2,
}
value["canonical_payload_digest"] = sha256_json(value)
return value
def _provider(tmp_path, value: dict, now: int = 1_700_000_000):
root = tmp_path / "epoch"
root.mkdir(parents=True, mode=0o700)
payload = canonical_json_bytes(value)
(root / "owner-delegation-epoch.canonical.json").write_bytes(payload)
(root / "owner-delegation-epoch.sig").write_text("sig", encoding="utf-8")
(root / "owner-delegation-epoch.key-id").write_text(
value.get("signature_key_id", "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/"), encoding="utf-8"
)
revocations = tmp_path / "revocations.json"
revocations.write_bytes(canonical_json_bytes({
"schema": "aios.owner-delegation-revocations.v1",
"revoked_epoch_ids": [],
}))
for path in [*root.iterdir(), revocations]:
path.chmod(0o600)
verifier = _Verifier()
provider = SignedEpochDirectoryProvider(
root=root,
verifier=verifier,
revocation_file=revocations,
expected_caller={"service": "test"},
require_root_owner=False,
clock=lambda: now,
)
return provider, verifier, root
def test_v2_preserves_schema_digest_and_exact_signed_bytes(tmp_path):
value = _value()
provider, verifier, root = _provider(tmp_path, value)
epoch = provider(epoch_id=value["epoch_id"], caller={"service": "test"})
payload = (root / "owner-delegation-epoch.canonical.json").read_bytes()
assert epoch.schema == SCHEMA_EPOCH_V2
assert epoch.canonical_payload == payload
assert epoch.canonical_payload_digest == value["canonical_payload_digest"]
assert epoch.policy_digest == value["policy_digest"]
assert verifier.calls[0]["payload"] == payload
@pytest.mark.parametrize(
("field", "replacement", "error"),
[
("status", "REVOKED", "TASK_ADMISSION_EPOCH_V2_BINDING_INVALID"),
("signature_namespace", "wrong", "TASK_ADMISSION_EPOCH_V2_BINDING_INVALID"),
("canonical_payload_digest", "0" * 64, "TASK_ADMISSION_EPOCH_PAYLOAD_DIGEST_INVALID"),
("signature_key_id", "SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB+", "TASK_ADMISSION_EPOCH_KEY_BINDING_INVALID"),
],
)
def test_v2_binding_and_digest_fail_closed(tmp_path, field, replacement, error):
value = _value()
value[field] = replacement
if field != "canonical_payload_digest":
value["canonical_payload_digest"] = sha256_json(
{k: v for k, v in value.items() if k != "canonical_payload_digest"}
)
provider, _, _ = _provider(tmp_path, value)
if field == "signature_key_id":
(provider.root / "owner-delegation-epoch.key-id").write_text(
"SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/", encoding="utf-8"
)
with pytest.raises(TaskAdmissionError, match=error):
provider(epoch_id=value["epoch_id"], caller={"service": "test"})
def test_v2_rejects_missing_field_and_noncanonical_signed_bytes(tmp_path):
value = _value()
value.pop("business_goal")
provider, _, _ = _provider(tmp_path, value)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_FIELDS_INVALID"):
provider(epoch_id=value["epoch_id"], caller={"service": "test"})
value = _value()
provider, _, root = _provider(tmp_path / "noncanonical", value)
raw = json.dumps(value, indent=2).encode("utf-8")
(root / "owner-delegation-epoch.canonical.json").write_bytes(raw)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_CANONICAL_MISMATCH"):
provider(epoch_id=value["epoch_id"], caller={"service": "test"})
def test_v2_rejects_expiry_and_revocation(tmp_path):
value = _value(now=1_700_000_000)
value["expires_at"] = 1_699_999_999
value["canonical_payload_digest"] = sha256_json(
{k: v for k, v in value.items() if k != "canonical_payload_digest"}
)
provider, _, _ = _provider(tmp_path, value)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_EXPIRED"):
provider(epoch_id=value["epoch_id"], caller={"service": "test"})
value = _value()
provider, _, _ = _provider(tmp_path / "revoked", value)
(provider.revocation_file).write_bytes(canonical_json_bytes({
"schema": "aios.owner-delegation-revocations.v1",
"revoked_epoch_ids": [value["epoch_id"]],
}))
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_REVOKED"):
provider(epoch_id=value["epoch_id"], caller={"service": "test"})
@pytest.mark.parametrize("field", ["allowed_branch_prefixes", "allowed_capabilities", "allowed_file_prefixes"])
def test_v2_rejects_empty_scope(tmp_path, field):
value = _value()
value[field] = []
value["canonical_payload_digest"] = sha256_json(
{k: v for k, v in value.items() if k != "canonical_payload_digest"}
)
provider, _, _ = _provider(tmp_path, value)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_SCOPE_INVALID"):
provider(epoch_id=value["epoch_id"], caller={"service": "test"})
def test_v2_rejects_unknown_destructive_policy(tmp_path):
value = _value()
value["destructive_policy"] = "deny"
value["canonical_payload_digest"] = sha256_json(
{k: v for k, v in value.items() if k != "canonical_payload_digest"}
)
provider, _, _ = _provider(tmp_path, value)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_V2_BINDING_INVALID"):
provider(epoch_id=value["epoch_id"], caller={"service": "test"})
+149
View File
@@ -0,0 +1,149 @@
import pytest
from aios_capability_contract import (
has_capabilities,
normalize_required_capabilities,
normalize_worker_capabilities,
)
from aios_agent_task_protocol import TaskProtocolError, TaskStore
def test_n01_unknown_required_token_is_explicit_and_denied():
assert normalize_required_capabilities(["unknown-token"]) == frozenset({"exact:unknown-token"})
assert has_capabilities(["testing"], ["unknown-token"]) is False
def test_n02_unknown_worker_token_is_never_granted():
assert "exact:unknown-token" not in normalize_worker_capabilities(["unknown-token"])
assert has_capabilities(["unknown-token"], ["testing"]) is False
def test_n03_admitted_capability_and_claim_match_use_same_contract():
assert has_capabilities(["git.read"], ["repository_read"]) is True
assert has_capabilities(["git.read"], ["testing"]) is False
def test_n01_task_store_rejects_unknown_before_queue_insert(tmp_path):
store = TaskStore(tmp_path / "tasks.sqlite3")
with pytest.raises(TaskProtocolError) as exc:
store.create_task(
task_id="task-unknown",
goal="unknown",
instructions="unknown",
success_criteria=[],
required_capabilities=["unknown-token"],
idempotency_key="idempotency-unknown",
)
assert exc.value.code == "TASK_CAPABILITY_UNKNOWN"
with store._connect() as connection:
assert connection.execute("SELECT COUNT(*) FROM tasks").fetchone()[0] == 0
def test_n02_existing_unknown_requirement_is_denied_by_poll_and_claim(tmp_path):
store = TaskStore(tmp_path / "tasks.sqlite3")
store.register_agent(
agent_id="agent-old-row",
agent_type="test",
agent_version="1",
capabilities=["testing"],
)
task = store.create_task(
task_id="task-old-row",
goal="old",
instructions="old",
success_criteria=[],
required_capabilities=["testing"],
idempotency_key="idempotency-old-row",
)
with store._connect() as connection:
connection.execute(
"UPDATE tasks SET required_capabilities_json=? WHERE task_id=?",
('["unknown-token"]', task["task_id"]),
)
assert store.poll_tasks(agent_id="agent-old-row") == []
with pytest.raises(TaskProtocolError) as exc:
store.claim_task(task_id=task["task_id"], agent_id="agent-old-row")
assert exc.value.code == "AGENT_CAPABILITY_MISMATCH"
def test_n03_atomic_claim_denies_admitted_requirement_worker_drift(tmp_path):
store = TaskStore(tmp_path / "tasks.sqlite3")
store.register_agent(
agent_id="agent-drift",
agent_type="test",
agent_version="1",
capabilities=["git.read"],
)
task = store.create_task(
task_id="task-drift",
goal="drift",
instructions="drift",
success_criteria=[],
required_capabilities=["testing"],
idempotency_key="idempotency-drift",
)
with pytest.raises(TaskProtocolError) as exc:
store.claim_task(task_id=task["task_id"], agent_id="agent-drift")
assert exc.value.code == "AGENT_CAPABILITY_MISMATCH"
assert store.get_task(task["task_id"])["status"] == "QUEUED"
# ---------------------------------------------------------------------------
# C5 production.* capability family (V2R2C5 / formal-lineage-canonical-v17)
# ---------------------------------------------------------------------------
# The full 17-cap set of the provisioned C5 promotion worker
# (agent/c5-promotion-exec-20260929).
C5_WORKER17 = [
"agent-task:control",
"code.read",
"code.write",
"git.read",
"git.test",
"governed.task.create",
"governed.write.branch",
"governed.write.file",
"governed.write.files",
"production.plan.apply",
"production.promote",
"production.rollback",
"task.principal.issue",
"test.execute",
"testing",
"verification",
"writer.lease",
]
def test_c5_01_production_required_normalization_recognized():
# production.* must NOT be tagged as unknown (exact:prefix) after patch.
required = normalize_required_capabilities(["production.promote"])
assert required == frozenset({"production.promote"})
unknown = [c for c in normalize_required_capabilities(C5_WORKER17) if c.startswith("exact:")]
assert unknown == []
def test_c5_02_production_worker_normalization_recognized():
effective = normalize_worker_capabilities(C5_WORKER17)
assert {"production.plan.apply", "production.promote", "production.rollback"}.issubset(effective)
assert len(effective) >= 17
def test_c5_03_has_capabilities_full17_is_true():
# Previously raw claim returned 403 AGENT_CAPABILITY_MISMATCH; patched contract must be True.
assert has_capabilities(C5_WORKER17, C5_WORKER17) is True
def test_c5_04_unknown_capability_still_fail_closed():
assert has_capabilities(C5_WORKER17, ["production.promote", "unknown-cap"]) is False
assert normalize_required_capabilities(["unknown-cap"]) == frozenset({"exact:unknown-cap"})
def test_c5_05_wildcard_still_rejected():
assert has_capabilities(["production.*"], ["production.promote"]) is False
assert normalize_required_capabilities(["production.*"]) == frozenset({"exact:production.*"})
def test_c5_06_missing_production_still_denied():
# A worker WITHOUT production.* must not be able to claim a production task.
worker_no_prod = [c for c in C5_WORKER17 if not c.startswith("production.")]
assert has_capabilities(worker_no_prod, ["production.promote"]) is False