git-diff-check git diff --check PASS on exact PR head
py-compile Python compileall PASS on active-runtime/app
reconciliation-contract Full source regression includes completed-replay/reconciliation contract PASS
secret-scan Diff-added lines secret scan PASS; no credential material
signer-validator-contract Signer contract suite PASS; live signer remains opt-in and no key was provisioned
campaign-approval-gate V2R2C2 trusted-controller source binding gate PASS; durable candidate creation remains post-merge
release-state-machine V2R2C2 release-controller source contract PASS; no production mutation enabled
autonomy-smoke-tests Source snapshot smoke/contract suite passed; production mutations disabled
autonomy-unit-tests 816 passed, 3 skipped, 154 subtests passed
507 lines
18 KiB
Python
507 lines
18 KiB
Python
from __future__ import annotations
|
|
|
|
import base64
|
|
import dataclasses
|
|
import hashlib
|
|
import importlib
|
|
import importlib.util
|
|
import json
|
|
import socket
|
|
import sys
|
|
import threading
|
|
import urllib.error
|
|
import urllib.request
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from cryptography.hazmat.primitives import serialization
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
|
|
|
|
APP = Path(__file__).resolve().parents[1] / "active-runtime" / "app"
|
|
AGENT_OS = Path(__file__).resolve().parents[1] / "runtime" / "agent-os"
|
|
for import_root in (APP, AGENT_OS):
|
|
if str(import_root) not in sys.path:
|
|
sys.path.insert(0, str(import_root))
|
|
|
|
from silent_release_hardening import ( # noqa: E402
|
|
BuilderAttestation,
|
|
BuilderAttestationKeyRing,
|
|
BuilderAttestationVerifier,
|
|
CapabilityAuthorizationError,
|
|
SilentReleaseActionService,
|
|
TrustedCandidateBuilderUnavailable,
|
|
)
|
|
import silent_release_hardening as hardening # noqa: E402
|
|
|
|
|
|
COMMIT = "a" * 40
|
|
TREE = "b" * 40
|
|
SHA = "c" * 64
|
|
GITEA = {"consistent": True}
|
|
|
|
|
|
def _af_inet_available() -> bool:
|
|
try:
|
|
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
except OSError:
|
|
return False
|
|
probe.close()
|
|
return True
|
|
|
|
|
|
def _canonical_digest(value: dict) -> str:
|
|
return hashlib.sha256(
|
|
json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
|
|
).hexdigest()
|
|
|
|
|
|
def _instant(delta: timedelta = timedelta()) -> str:
|
|
return (datetime.now(timezone.utc) + delta).isoformat().replace(
|
|
"+00:00", "Z"
|
|
)
|
|
|
|
|
|
def _keyring_payload(private: Ed25519PrivateKey, *, purpose: str) -> dict:
|
|
public = private.public_key().public_bytes(
|
|
serialization.Encoding.Raw, serialization.PublicFormat.Raw
|
|
)
|
|
record = {
|
|
"algorithm": "Ed25519",
|
|
"purpose": purpose,
|
|
"key_id": "builder-key-001",
|
|
"generation": 1,
|
|
"not_before": _instant(timedelta(minutes=-1)),
|
|
"expires_at": _instant(timedelta(days=1)),
|
|
"status": "ACTIVE",
|
|
"public_key": base64.b64encode(public).decode("ascii"),
|
|
"public_key_sha256": hashlib.sha256(public).hexdigest(),
|
|
}
|
|
payload = {
|
|
"algorithm": "Ed25519",
|
|
"purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
|
|
"keys": [record],
|
|
}
|
|
payload["keyring_sha256"] = hashlib.sha256(
|
|
json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
).hexdigest()
|
|
return payload
|
|
|
|
|
|
def _refresh_keyring_digest(payload: dict) -> dict:
|
|
canonical = {
|
|
"algorithm": payload["algorithm"],
|
|
"purpose": payload["purpose"],
|
|
"keys": payload["keys"],
|
|
}
|
|
payload["keyring_sha256"] = hashlib.sha256(
|
|
json.dumps(canonical, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
).hexdigest()
|
|
return payload
|
|
|
|
|
|
def _attestation(private: Ed25519PrivateKey) -> BuilderAttestation:
|
|
unsigned = BuilderAttestation(
|
|
builder_attestation_id="builder-attestation-001",
|
|
builder_key_id="builder-key-001",
|
|
source_commit=COMMIT,
|
|
tree_oid=TREE,
|
|
artifact_sha256=SHA,
|
|
manifest_sha256=SHA,
|
|
package_closure_sha256=SHA,
|
|
test_evidence_sha256=SHA,
|
|
canary_evidence_sha256=SHA,
|
|
issued_at=_instant(),
|
|
signature="",
|
|
builder_key_generation=1,
|
|
signature_algorithm="Ed25519",
|
|
signature_purpose="AIOS_TRUSTED_RELEASE_BUILDER",
|
|
gitea_evidence_sha256=_canonical_digest(GITEA),
|
|
rollback_evidence_sha256=_canonical_digest({
|
|
"ready": True,
|
|
"rollback_sha256": SHA,
|
|
"source_commit": COMMIT,
|
|
}),
|
|
policy_evidence_sha256=SHA,
|
|
secret_scan_evidence_sha256=SHA,
|
|
)
|
|
return BuilderAttestation(
|
|
**{
|
|
**unsigned.canonical_payload,
|
|
"signature": base64.b64encode(
|
|
private.sign(unsigned.canonical_bytes())
|
|
).decode("ascii"),
|
|
}
|
|
)
|
|
|
|
|
|
def test_builder_verifier_rejects_owner_key_record_cross_purpose(tmp_path):
|
|
private = Ed25519PrivateKey.generate()
|
|
payload = _keyring_payload(private, purpose="owner_approval")
|
|
path = tmp_path / "builder-keyring.json"
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
|
|
|
with pytest.raises(ValueError, match="purpose"):
|
|
BuilderAttestationKeyRing.from_file(path)
|
|
|
|
|
|
def test_builder_verifier_checks_domain_key_window_digest_and_signature(tmp_path):
|
|
private = Ed25519PrivateKey.generate()
|
|
path = tmp_path / "builder-keyring.json"
|
|
path.write_text(
|
|
json.dumps(_keyring_payload(private, purpose="AIOS_TRUSTED_RELEASE_BUILDER")),
|
|
encoding="utf-8",
|
|
)
|
|
verifier = BuilderAttestationVerifier(
|
|
BuilderAttestationKeyRing.from_file(path)
|
|
)
|
|
candidate = SimpleNamespace(
|
|
source_commit=COMMIT,
|
|
tree_oid=TREE,
|
|
artifact_sha256=SHA,
|
|
manifest_sha256=SHA,
|
|
gitea_consistency=GITEA,
|
|
rollback_ready=True,
|
|
)
|
|
plan = SimpleNamespace(
|
|
deployment=SimpleNamespace(
|
|
package_closure_sha256=SHA,
|
|
rollback_sha256=SHA,
|
|
),
|
|
policy_sha256=SHA,
|
|
)
|
|
assert verifier.verify(_attestation(private), candidate, plan).builder_key_id == "builder-key-001"
|
|
|
|
|
|
@pytest.mark.parametrize("state", ["REVOKED", "EXPIRED", "FUTURE"])
|
|
def test_builder_verifier_rejects_unavailable_key_lifecycle_states(tmp_path, state):
|
|
private = Ed25519PrivateKey.generate()
|
|
payload = _keyring_payload(private, purpose="AIOS_TRUSTED_RELEASE_BUILDER")
|
|
record = payload["keys"][0]
|
|
if state == "REVOKED":
|
|
record["status"] = "REVOKED"
|
|
elif state == "EXPIRED":
|
|
record["not_before"] = _instant(timedelta(days=-2))
|
|
record["expires_at"] = _instant(timedelta(days=-1))
|
|
else:
|
|
record["not_before"] = _instant(timedelta(days=1))
|
|
record["expires_at"] = _instant(timedelta(days=2))
|
|
path = tmp_path / "builder-keyring.json"
|
|
path.write_text(json.dumps(_refresh_keyring_digest(payload)), encoding="utf-8")
|
|
verifier = BuilderAttestationVerifier(BuilderAttestationKeyRing.from_file(path))
|
|
candidate = SimpleNamespace(
|
|
source_commit=COMMIT, tree_oid=TREE,
|
|
artifact_sha256=SHA, manifest_sha256=SHA,
|
|
gitea_consistency=GITEA, rollback_ready=True,
|
|
)
|
|
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
|
|
with pytest.raises(CapabilityAuthorizationError, match="validity|revoked|unavailable"):
|
|
verifier.verify(_attestation(private), candidate, plan)
|
|
|
|
|
|
def test_builder_verifier_rejects_unknown_key_invalid_signature_and_keyring_digest(tmp_path):
|
|
private = Ed25519PrivateKey.generate()
|
|
payload = _keyring_payload(private, purpose="AIOS_TRUSTED_RELEASE_BUILDER")
|
|
path = tmp_path / "builder-keyring.json"
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
|
verifier = BuilderAttestationVerifier(BuilderAttestationKeyRing.from_file(path))
|
|
candidate = SimpleNamespace(
|
|
source_commit=COMMIT, tree_oid=TREE,
|
|
artifact_sha256=SHA, manifest_sha256=SHA,
|
|
gitea_consistency=GITEA, rollback_ready=True,
|
|
)
|
|
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
|
|
with pytest.raises(CapabilityAuthorizationError, match="not trusted"):
|
|
verifier.verify(
|
|
dataclasses.replace(_attestation(private), builder_key_id="unknown-builder-key"),
|
|
candidate,
|
|
plan,
|
|
)
|
|
with pytest.raises(CapabilityAuthorizationError, match="signature"):
|
|
verifier.verify(
|
|
dataclasses.replace(_attestation(private), signature=base64.b64encode(b"x" * 64).decode()),
|
|
candidate,
|
|
plan,
|
|
)
|
|
|
|
payload["keyring_sha256"] = "0" * 64
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
|
with pytest.raises(ValueError, match="keyring digest"):
|
|
BuilderAttestationKeyRing.from_file(path)
|
|
|
|
|
|
def test_builder_attestation_rejects_tampered_digest_and_signing_metadata():
|
|
private = Ed25519PrivateKey.generate()
|
|
raw = _attestation(private).to_dict()
|
|
raw["fingerprint"] = "0" * 64
|
|
with pytest.raises(CapabilityAuthorizationError, match="digest is invalid"):
|
|
BuilderAttestation.from_dict(raw, require_fingerprint=True)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"field,value",
|
|
[
|
|
("builder_key_generation", 2),
|
|
("signature_algorithm", "RSA"),
|
|
("signature_purpose", "owner_approval"),
|
|
],
|
|
)
|
|
def test_builder_keyring_rejects_attestation_metadata_mismatch(tmp_path, field, value):
|
|
private = Ed25519PrivateKey.generate()
|
|
path = tmp_path / "builder-keyring.json"
|
|
path.write_text(
|
|
json.dumps(_keyring_payload(private, purpose="AIOS_TRUSTED_RELEASE_BUILDER")),
|
|
encoding="utf-8",
|
|
)
|
|
verifier = BuilderAttestationVerifier(
|
|
BuilderAttestationKeyRing.from_file(path)
|
|
)
|
|
candidate = SimpleNamespace(
|
|
source_commit=COMMIT, tree_oid=TREE,
|
|
artifact_sha256=SHA, manifest_sha256=SHA,
|
|
gitea_consistency=GITEA, rollback_ready=True,
|
|
)
|
|
plan = SimpleNamespace(deployment=SimpleNamespace(package_closure_sha256=SHA, rollback_sha256=SHA), policy_sha256=SHA)
|
|
with pytest.raises(CapabilityAuthorizationError, match="metadata"):
|
|
verifier.verify(
|
|
dataclasses.replace(_attestation(private), **{field: value}),
|
|
candidate,
|
|
plan,
|
|
)
|
|
raw = _attestation(private).to_dict()
|
|
raw["builder_key_generation"] = 2
|
|
with pytest.raises(CapabilityAuthorizationError, match="digest is invalid"):
|
|
BuilderAttestation.from_dict(raw, require_fingerprint=True)
|
|
|
|
|
|
def test_builder_unavailable_intent_is_bounded_retryable_and_recovers_without_secret(tmp_path, monkeypatch):
|
|
class UnavailableBuilder:
|
|
def build(self, intent):
|
|
raise TrustedCandidateBuilderUnavailable("socket token=should-not-leak")
|
|
|
|
monkeypatch.setattr(hardening, "_fsync_directory", lambda _path: None)
|
|
service = SilentReleaseActionService(
|
|
tmp_path,
|
|
candidate_builder=UnavailableBuilder(),
|
|
max_builder_attempts=2,
|
|
)
|
|
payload = {"event_id": "event-builder-001", "source_commit": COMMIT}
|
|
|
|
first = service.create_release_intent(payload)
|
|
second = service.create_release_intent(payload)
|
|
|
|
assert first["status"] == "BUILDER_RETRYABLE_FAILURE"
|
|
assert first["retryable"] is True
|
|
assert first["attempt_count"] == 1
|
|
assert "should-not-leak" not in json.dumps(first)
|
|
assert second["status"] == "BUILDER_TERMINAL_FAILURE"
|
|
assert second["retryable"] is False
|
|
assert second["attempt_count"] == 2
|
|
|
|
|
|
def test_stale_building_intent_recovers_after_restart_without_double_running_active_attempt(tmp_path, monkeypatch):
|
|
class CountingUnavailableBuilder:
|
|
calls = 0
|
|
|
|
def build(self, intent):
|
|
self.calls += 1
|
|
raise TrustedCandidateBuilderUnavailable("nonsecret")
|
|
|
|
monkeypatch.setattr(hardening, "_fsync_directory", lambda _path: None)
|
|
builder = CountingUnavailableBuilder()
|
|
service = SilentReleaseActionService(
|
|
tmp_path,
|
|
candidate_builder=builder,
|
|
max_builder_attempts=3,
|
|
builder_attempt_timeout_seconds=1,
|
|
)
|
|
payload = {"event_id": "event-builder-crash-001", "source_commit": COMMIT}
|
|
intent_id = "intent-" + hashlib.sha256(
|
|
f"{payload['event_id']}\0{COMMIT}".encode()
|
|
).hexdigest()[:24]
|
|
intent_path = tmp_path / "intents" / f"{intent_id}.json"
|
|
intent_path.parent.mkdir(parents=True)
|
|
active_record = {
|
|
"attempt_count": 1,
|
|
"attempt_started_at": _instant(),
|
|
"created_at": _instant(timedelta(minutes=-1)),
|
|
"event_id": payload["event_id"],
|
|
"intent_id": intent_id,
|
|
"source_commit": COMMIT,
|
|
"status": "BUILDING",
|
|
}
|
|
intent_path.write_text(json.dumps(active_record), encoding="utf-8")
|
|
|
|
active = service.create_release_intent(payload)
|
|
assert active["status"] == "BUILDING"
|
|
assert active["idempotent"] is True
|
|
assert builder.calls == 0
|
|
|
|
active_record["attempt_started_at"] = _instant(timedelta(minutes=-5))
|
|
intent_path.write_text(json.dumps(active_record), encoding="utf-8")
|
|
recovered = service.create_release_intent(payload)
|
|
assert recovered["status"] == "BUILDER_RETRYABLE_FAILURE"
|
|
assert recovered["attempt_count"] == 2
|
|
assert recovered["failure_code"] == "TRUSTED_BUILDER_UNAVAILABLE"
|
|
assert recovered["last_recovery_code"] == "TRUSTED_BUILDER_CRASH_RECOVERED"
|
|
assert recovered["recovery_count"] == 1
|
|
assert builder.calls == 1
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
|
|
@pytest.mark.skipif(
|
|
not _af_inet_available(),
|
|
reason="isolated executor permits AF_UNIX only; live HTTP probe runs at activation",
|
|
)
|
|
def test_model_api_keeps_process_liveness_separate_from_builder_readiness(monkeypatch):
|
|
api = importlib.import_module("aios_model_api_staging")
|
|
|
|
class FakeService:
|
|
ready = False
|
|
|
|
def get_builder_readiness(self):
|
|
return {"status": "READY" if self.ready else "UNAVAILABLE"}
|
|
|
|
def create_release_candidate(self, payload):
|
|
if not payload:
|
|
raise ValueError("event_id is required")
|
|
return {"status": "READY_FOR_APPROVAL"}
|
|
|
|
service = FakeService()
|
|
monkeypatch.setattr(api, "silent_release_service", lambda: service)
|
|
monkeypatch.setattr(
|
|
api,
|
|
"authenticate",
|
|
lambda token: (
|
|
{"account": "test", "role": "owner", "scopes": ["release:candidate"]}
|
|
if token == "test-token"
|
|
else None
|
|
),
|
|
)
|
|
server = api.ThreadingHTTPServer(("127.0.0.1", 0), api.H)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
base = f"http://127.0.0.1:{server.server_port}"
|
|
|
|
def request(path, body=None):
|
|
data = None if body is None else json.dumps(body).encode("utf-8")
|
|
value = urllib.request.Request(
|
|
base + path,
|
|
data=data,
|
|
headers={"Authorization": "Bearer test-token"},
|
|
method="POST" if body is not None else "GET",
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(value, timeout=5) as response:
|
|
return response.status, json.load(response)
|
|
except urllib.error.HTTPError as error:
|
|
return error.code, json.load(error)
|
|
|
|
try:
|
|
health_code, health = request("/health")
|
|
unavailable_code, unavailable = request("/createReleaseCandidate", {})
|
|
service.ready = True
|
|
invalid_code, invalid = request("/createReleaseCandidate", {})
|
|
finally:
|
|
server.shutdown()
|
|
server.server_close()
|
|
thread.join(timeout=5)
|
|
|
|
assert health_code == 200
|
|
assert health["process_liveness"] == "PASS"
|
|
assert health["builder_readiness"]["status"] == "UNAVAILABLE"
|
|
assert unavailable_code == 503
|
|
assert unavailable["error"] == "TRUSTED_BUILDER_UNAVAILABLE"
|
|
assert invalid_code == 400
|
|
assert invalid["error"] == "event_id is required"
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
|
|
def test_host_adapter_accepts_a_real_builder_bundle_response():
|
|
spec = importlib.util.spec_from_file_location(
|
|
"trusted_builder_public_seam",
|
|
APP.parents[1] / "tests" / "test_trusted_candidate_builder.py",
|
|
)
|
|
assert spec and spec.loader
|
|
backend_seam = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(backend_seam)
|
|
server, client, _ = backend_seam.make_seam()
|
|
candidate_id = "rc-v2r2c2-d2b5a8339a34"
|
|
source_commit = "d2b5a8339a34a8f958a177f8a3b9e8296a57a7a6"
|
|
response = server.handle_request(
|
|
client.create_request(
|
|
"V2R2C2", candidate_id, source_commit, nonce="z" * 32
|
|
),
|
|
connection=object(),
|
|
)
|
|
|
|
class ResponseClient:
|
|
def build(self, version, supplied_candidate_id, supplied_source, *, nonce):
|
|
assert version == "V2R2C2"
|
|
assert supplied_candidate_id == candidate_id
|
|
assert supplied_source == source_commit
|
|
assert len(nonce) >= 32
|
|
return response
|
|
|
|
api = importlib.import_module("aios_model_api_staging")
|
|
bundle = api._HostTrustedCandidateBuilderAdapter(ResponseClient()).build(
|
|
{
|
|
"candidate_id": candidate_id,
|
|
"source_commit": source_commit,
|
|
"version": "V2R2C2",
|
|
}
|
|
)
|
|
|
|
assert bundle.validate().candidate.source_commit == response["base_oid"]
|
|
assert bundle.attestation.builder_key_id == response["attestation"]["builder_key_id"]
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="model API runtime requires POSIX fcntl")
|
|
def test_host_adapter_forwards_v2r2c3_to_the_policy_and_fails_closed_when_unavailable():
|
|
from aios_trusted_candidate_builder import BuilderError
|
|
api = importlib.import_module("aios_model_api_staging")
|
|
calls = []
|
|
|
|
class DerivedOnlyClient:
|
|
def build(self, version, candidate_id, source_commit, *, nonce):
|
|
calls.append((version, candidate_id, source_commit, nonce))
|
|
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
|
|
|
|
with pytest.raises(TrustedCandidateBuilderUnavailable):
|
|
api._HostTrustedCandidateBuilderAdapter(DerivedOnlyClient()).build(
|
|
{
|
|
"candidate_id": "r42-release-candidate-v2r2c3-20260820",
|
|
"source_commit": COMMIT,
|
|
"version": "V2R2C3",
|
|
}
|
|
)
|
|
assert calls[0][:3] == (
|
|
"V2R2C3",
|
|
"r42-release-candidate-v2r2c3-20260820",
|
|
COMMIT,
|
|
)
|
|
|
|
|
|
def test_host_adapter_accepts_intent_only_without_caller_candidate_id():
|
|
api = importlib.import_module('aios_model_api_staging')
|
|
calls = []
|
|
|
|
class IntentClient:
|
|
def build(self, version, candidate_id, source_commit, *, nonce):
|
|
calls.append((version, candidate_id, source_commit, nonce))
|
|
raise RuntimeError('intent-only probe')
|
|
|
|
with pytest.raises(RuntimeError, match='intent-only probe'):
|
|
api._HostTrustedCandidateBuilderAdapter(IntentClient()).build({
|
|
'event_id': 'event-intent-only',
|
|
'intent_id': 'intent-intent-only',
|
|
'source_commit': COMMIT,
|
|
'version': 'V2R2C2',
|
|
})
|
|
assert calls[0][0] == 'V2R2C2'
|
|
assert calls[0][1] == ''
|
|
assert calls[0][2] == COMMIT
|
|
assert len(calls[0][3]) >= 32
|