Merge pull request 'gitops: Silent Release execution readiness' (#13) from patch/silent-release-execution-readiness-20260724 into main

This commit was merged in pull request #13.
This commit is contained in:
2026-07-24 03:12:55 +08:00
12 changed files with 3155 additions and 35 deletions
+27 -2
View File
@@ -100,10 +100,14 @@ READ_ONLY_GET_PATHS = frozenset({
'/admin/ai-evolution/api/data',
'/api/release-consistency', '/api/tag-publication-status',
'/api/release-acceptance',
'/getReleaseCandidate', '/getApprovalEnvelope', '/getReleaseSaga',
'/getReleaseCandidate', '/getReleasePlan', '/getReleaseAcceptance',
'/getApprovalEnvelope', '/getReleaseSaga', '/getCapabilityStatus',
'/getRecoveryStatus',
})
SILENT_RELEASE_POST_PATHS = frozenset({
'/createReleaseCandidate',
'/createReleaseIntent',
'/submitSignedApproval',
'/submitOwnerApproval',
'/startDeployment',
})
@@ -533,8 +537,11 @@ class H(BaseHTTPRequestHandler):
return self.sendj(publication_publisher().run_release_acceptance(request))
if path in (
'/getReleaseCandidate',
'/getReleasePlan',
'/getReleaseAcceptance',
'/getApprovalEnvelope',
'/getReleaseSaga',
'/getCapabilityStatus',
):
candidate_id=(q.get('candidate_id') or [''])[0]
if not candidate_id:
@@ -548,7 +555,19 @@ class H(BaseHTTPRequestHandler):
return self.sendj(
service.get_approval_envelope(candidate_id)
)
if path == '/getReleasePlan':
return self.sendj(service.get_release_plan(candidate_id))
if path == '/getReleaseAcceptance':
return self.sendj(service.get_acceptance(candidate_id))
if path == '/getCapabilityStatus':
return self.sendj(
service.get_capability_status(candidate_id)
)
return self.sendj(service.get_release_saga(candidate_id))
if path == '/getRecoveryStatus':
return self.sendj(
silent_release_service().get_recovery_status()
)
# Admin AI Evolution Dashboard
if path in ('/admin/ai-evolution','/admin/ai-evolution/'):
return self.ai_evolution_page()
@@ -582,7 +601,13 @@ class H(BaseHTTPRequestHandler):
return self.sendj(
silent_release_service().create_release_candidate(data)
)
if path == '/submitOwnerApproval':
if path == '/createReleaseIntent':
a=self.need('release:candidate')
if not a: return
return self.sendj(
silent_release_service().create_release_intent(data)
)
if path in ('/submitOwnerApproval','/submitSignedApproval'):
a=self.need('release:approve')
if not a: return
return self.sendj(
@@ -0,0 +1,564 @@
#!/usr/bin/env python3
"""Execution-readiness adapters and the fail-closed Shadow controller service."""
from __future__ import annotations
import argparse
import base64
import dataclasses
import hashlib
import json
import os
import subprocess
import threading
import time
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol
from urllib.parse import parse_qs, urlparse
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
from silent_release_controller import _sanitize, utc_now
from silent_release_hardening import (
BuilderAttestation,
CapabilityAuthorizationError,
ReleaseRecoveryWorker,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TrustedCandidateBundle,
_canonical_json,
_durable_json,
_safe_child,
)
class BuilderSigner(Protocol):
@property
def key_id(self) -> str:
...
def sign(self, payload: bytes) -> bytes:
...
class LocalTestBuilderSigner:
"""Ephemeral in-memory signer. It is forbidden for production execution."""
test_only = True
def __init__(self, key_id: str, private_key: Ed25519PrivateKey):
self._key_id = str(key_id)
self._private_key = private_key
@property
def key_id(self) -> str:
return self._key_id
@classmethod
def generate(
cls, key_id: str = "shadow-builder-test-key"
) -> "LocalTestBuilderSigner":
return cls(key_id, Ed25519PrivateKey.generate())
@property
def public_key(self):
return self._private_key.public_key()
def sign(self, payload: bytes) -> bytes:
return self._private_key.sign(payload)
class ExternalKmsBuilderSigner:
"""Signer facade whose private operation remains in KMS/HSM."""
def __init__(
self,
key_id: str,
credential_reference: str,
sign_callback: Callable[[str, bytes], bytes],
):
if not str(credential_reference).startswith(
("kms://", "hsm://", "secret-manager://")
):
raise ValueError("production builder requires a KMS/HSM reference")
self._key_id = str(key_id)
self.credential_reference = str(credential_reference)
self._sign_callback = sign_callback
@property
def key_id(self) -> str:
return self._key_id
def sign(self, payload: bytes) -> bytes:
return bytes(self._sign_callback(self.credential_reference, payload))
class SignedTrustedBuilderAdapter:
"""Signs evidence produced by a trusted collector, never caller evidence."""
def __init__(
self,
collector: Callable[[Mapping[str, Any]], TrustedCandidateBundle],
signer: BuilderSigner,
):
self.collector = collector
self.signer = signer
def build(self, intent: Mapping[str, Any]) -> TrustedCandidateBundle:
forbidden = {
"artifact_sha256",
"canary",
"checks",
"manifest_sha256",
"tests",
"tree_oid",
}
if forbidden.intersection(intent):
raise CapabilityAuthorizationError(
"trusted builder rejects caller-supplied evidence"
)
unsigned = self.collector(dict(intent))
if unsigned.attestation is not None:
raise CapabilityAuthorizationError(
"collector cannot self-attest its evidence"
)
candidate = unsigned.candidate
test_sha = hashlib.sha256(
_canonical_json(_sanitize(candidate.tests))
).hexdigest()
canary_sha = hashlib.sha256(
_canonical_json(_sanitize(candidate.canary))
).hexdigest()
attestation_id = "builder-attestation-" + hashlib.sha256(
(
candidate.candidate_id
+ "\0"
+ candidate.source_commit
+ "\0"
+ candidate.artifact_sha256
).encode("utf-8")
).hexdigest()[:24]
attestation = BuilderAttestation(
builder_attestation_id=attestation_id,
builder_key_id=self.signer.key_id,
source_commit=candidate.source_commit,
tree_oid=candidate.tree_oid,
artifact_sha256=candidate.artifact_sha256,
manifest_sha256=candidate.manifest_sha256,
package_closure_sha256=(
unsigned.release_plan.deployment.package_closure_sha256
),
test_evidence_sha256=test_sha,
canary_evidence_sha256=canary_sha,
issued_at=utc_now(),
signature="",
)
attestation = dataclasses.replace(
attestation,
signature=base64.b64encode(
self.signer.sign(attestation.canonical_bytes())
).decode("ascii"),
)
return dataclasses.replace(unsigned, attestation=attestation)
class ApprovalBackendAdapter(Protocol):
@property
def available(self) -> bool:
...
def verify(
self,
approval: SignedReleasePlanApproval,
*,
candidate,
release_plan,
policy_sha256: str,
) -> SignedReleasePlanApproval:
...
class LocalTestApprovalSigner:
"""Test-only approval signer used solely for Shadow acceptance."""
test_only = True
def __init__(self, key_id: str, private_key: Ed25519PrivateKey):
self.key_id = str(key_id)
self.private_key = private_key
@classmethod
def generate(
cls, key_id: str = "shadow-owner-test-key"
) -> "LocalTestApprovalSigner":
return cls(key_id, Ed25519PrivateKey.generate())
def sign(
self, approval: SignedReleasePlanApproval
) -> SignedReleasePlanApproval:
unsigned = dataclasses.replace(
approval, key_id=self.key_id, signature=""
)
return dataclasses.replace(
unsigned,
signature=base64.b64encode(
self.private_key.sign(unsigned.canonical_bytes())
).decode("ascii"),
)
class ProductionApprovalBackendAdapter:
"""Verification-only production adapter configured by a secret/KMS ref."""
def __init__(
self,
verifier: SignedApprovalVerifier,
credential_reference: str,
):
if not str(credential_reference).startswith(
("kms://", "hsm://", "secret-manager://")
):
raise ValueError(
"production approval backend requires a KMS/secret reference"
)
self.verifier = verifier
self.credential_reference = str(credential_reference)
@property
def available(self) -> bool:
return True
def verify(
self,
approval: SignedReleasePlanApproval,
*,
candidate,
release_plan,
policy_sha256: str,
) -> SignedReleasePlanApproval:
return self.verifier.verify_release_plan(
approval, candidate, release_plan, policy_sha256
)
class UnavailableProductionApprovalBackend:
credential_reference = None
@property
def available(self) -> bool:
return False
def verify(self, *args, **kwargs):
raise CapabilityAuthorizationError(
"production approval backend is not configured"
)
class LeaderLock:
def __init__(self, path: Path):
self.path = Path(path)
self.handle = None
def __enter__(self):
self.path.parent.mkdir(parents=True, exist_ok=True)
os.chmod(self.path.parent, 0o700)
self.handle = self.path.open("a+", encoding="utf-8")
os.chmod(self.path, 0o600)
try:
import fcntl
fcntl.flock(
self.handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB
)
except BlockingIOError as exc:
self.handle.close()
self.handle = None
raise RuntimeError("silent release leader is already active") from exc
return self
def __exit__(self, exc_type, exc, tb):
if self.handle is not None:
try:
import fcntl
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
finally:
self.handle.close()
self.handle = None
class PersistentSilentReleaseController:
"""Idempotent main-event controller with durable startup/recovery reports."""
def __init__(
self,
root: Path,
service: SilentReleaseActionService,
*,
main_probe: Callable[[], str],
recovery_worker: ReleaseRecoveryWorker | None = None,
):
self.root = Path(root)
self.service = service
self.main_probe = main_probe
self.recovery_worker = recovery_worker
self.leader_path = self.root / "controller.leader.lock"
self.status_path = self.root / "controller-status.json"
def startup_verify(self) -> dict[str, Any]:
checked = 0
failed = 0
for journal in sorted(self.service.sagas.root.glob("*.events.jsonl")):
candidate_id = journal.name[: -len(".events.jsonl")]
saga = self.service.sagas.load(candidate_id)
checked += 1
if not saga.journal_integrity:
failed += 1
status = {
"checked_sagas": checked,
"execution_enabled": self.service.execution_enabled,
"failed_closed": failed,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"status": "READY" if failed == 0 else "FAIL_CLOSED",
}
_durable_json(self.status_path, status)
return status
def run_once(self) -> dict[str, Any]:
with LeaderLock(self.leader_path):
startup = self.startup_verify()
if startup["status"] != "READY":
result = {
"execution_enabled": self.service.execution_enabled,
"intent": None,
"notification": None,
"recovery": None,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
"status": "FAIL_CLOSED",
}
_durable_json(self.status_path, result)
return _sanitize(result)
source_commit = str(self.main_probe())
event_id = f"main-{source_commit}"
intent = self.service.create_release_intent(
{"event_id": event_id, "source_commit": source_commit}
)
notification = None
if intent.get("candidate_id"):
notification = self.service.ensure_owner_notification(
str(intent["candidate_id"])
)
recovery = (
self.recovery_worker.run_once()
if self.recovery_worker is not None
else self.service.get_recovery_status()
)
result = {
"execution_enabled": self.service.execution_enabled,
"intent": intent,
"notification": notification,
"recovery": recovery,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
}
_durable_json(self.status_path, result)
return _sanitize(result)
class ShadowActionHandler(BaseHTTPRequestHandler):
service: SilentReleaseActionService
controller: PersistentSilentReleaseController
def log_message(self, fmt, *args):
return
def _json(self, payload: Mapping[str, Any], status=HTTPStatus.OK):
body = json.dumps(_sanitize(dict(payload)), sort_keys=True).encode()
self.send_response(int(status))
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _body(self) -> dict[str, Any]:
size = int(self.headers.get("Content-Length", "0"))
if size > 1024 * 1024:
raise ValueError("request body is too large")
return json.loads(self.rfile.read(size) or b"{}")
def do_GET(self):
parsed = urlparse(self.path)
query = parse_qs(parsed.query)
candidate_id = str((query.get("candidate_id") or [""])[0])
try:
if parsed.path == "/healthz":
return self._json(
{
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
"status": "ok",
}
)
routes = {
"/getReleaseCandidate": self.service.get_release_candidate,
"/getReleasePlan": self.service.get_release_plan,
"/getReleaseAcceptance": self.service.get_acceptance,
"/getReleaseSaga": self.service.get_release_saga,
"/getCapabilityStatus": self.service.get_capability_status,
}
if parsed.path in routes:
return self._json(routes[parsed.path](candidate_id))
if parsed.path == "/getRecoveryStatus":
return self._json(self.service.get_recovery_status())
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
except (ValueError, FileNotFoundError) as exc:
self._json(
{"error": type(exc).__name__}, HTTPStatus.BAD_REQUEST
)
def do_POST(self):
parsed = urlparse(self.path)
try:
body = self._body()
if parsed.path == "/createReleaseIntent":
return self._json(self.service.create_release_intent(body))
if parsed.path in {
"/submitSignedApproval",
"/submitOwnerApproval",
}:
return self._json(self.service.submit_owner_approval(body))
if parsed.path == "/startDeployment":
return self._json(
self.service.start_deployment(
str(body.get("candidate_id") or "")
)
)
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
except (ValueError, FileNotFoundError, CapabilityAuthorizationError) as exc:
self._json(
{"error": type(exc).__name__}, HTTPStatus.BAD_REQUEST
)
def git_main_probe(repository: Path) -> Callable[[], str]:
repository = Path(repository)
def probe() -> str:
completed = subprocess.run(
[
"git",
f"--git-dir={repository}",
"rev-parse",
"refs/heads/main",
],
check=True,
capture_output=True,
text=True,
timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
return completed.stdout.strip()
return probe
def serve_shadow(
*,
state_root: Path,
repository: Path,
host: str,
port: int,
poll_seconds: float,
) -> None:
service = SilentReleaseActionService.shadow(state_root)
controller = PersistentSilentReleaseController(
state_root,
service,
main_probe=git_main_probe(repository),
)
controller.run_once()
stop = threading.Event()
def controller_loop() -> None:
while not stop.wait(poll_seconds):
try:
controller.run_once()
except Exception as exc: # fail closed and keep the API available
_durable_json(
controller.status_path,
{
"error": type(exc).__name__,
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
"status": "FAIL_CLOSED",
},
)
handler = type(
"ConfiguredShadowActionHandler",
(ShadowActionHandler,),
{"service": service, "controller": controller},
)
server = ThreadingHTTPServer((host, port), handler)
api_thread = threading.Thread(
target=server.serve_forever,
name="silent-release-shadow-api",
daemon=False,
)
controller_thread = threading.Thread(
target=controller_loop,
name="silent-release-shadow-controller",
daemon=True,
)
api_thread.start()
controller_thread.start()
try:
while api_thread.is_alive():
api_thread.join(timeout=1)
finally:
stop.set()
server.shutdown()
server.server_close()
controller_thread.join(timeout=max(1.0, poll_seconds + 1.0))
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--state-root", required=True, type=Path)
parser.add_argument("--repository", required=True, type=Path)
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", default=18891, type=int)
parser.add_argument("--poll-seconds", default=30.0, type=float)
arguments = parser.parse_args()
if arguments.poll_seconds <= 0:
raise SystemExit("poll seconds must be positive")
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
raise SystemExit("GitOps writes must remain disabled")
if (
os.environ.get(
"AIOS_SILENT_RELEASE_EXECUTION_ENABLED", "false"
).lower()
!= "false"
):
raise SystemExit("Silent Release execution must remain disabled")
serve_shadow(
state_root=arguments.state_root,
repository=arguments.repository,
host=arguments.host,
port=arguments.port,
poll_seconds=arguments.poll_seconds,
)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+28 -2
View File
@@ -1,5 +1,5 @@
{
"schema_version": 3,
"schema_version": 4,
"automatic": [
"build",
"test",
@@ -66,12 +66,38 @@
"trusted_candidate_builder_required": true,
"actual_plan_regeneration_required": true,
"startup_fail_closed_on_missing_component": true,
"recovery_worker_shadow_default": true
"recovery_worker_shadow_default": true,
"production_approval_backend_required": true,
"production_builder_signer": "kms-hsm-secret-manager-only",
"production_execution_without_backend": "unavailable"
},
"durability": {
"journal": "sha256-hash-chain",
"probe_journal": "sha256-hash-chain",
"exclusive_capability_claims": true,
"side_effect_probe_before_lease_takeover": true,
"side_effect_probe_before_failed_recoverable_retry": true,
"probe_results": [
"NOT_APPLIED",
"APPLIED_VERIFIED",
"INDETERMINATE",
"CONFLICT"
],
"probe_operation_digest_required": true,
"probe_freshness_required": true,
"tamper_result": "MANUAL_OWNER_REVIEW_REQUIRED"
},
"tag_pending": {
"required_observations": [
"remote_git_ref",
"annotated_tag_object",
"peeled_target",
"gitea_api",
"gitea_listing",
"repository_identity"
],
"indeterminate_result": "RECONCILIATION_REQUIRED",
"conflict_result": "MANUAL_OWNER_REVIEW_REQUIRED",
"automatic_reconciliation": false
}
}
@@ -0,0 +1,25 @@
[Unit]
Description=AIOS Silent Release Shadow Controller
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
UMask=0077
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 /usr/libexec/aios-silent-release-shadow/current/active-runtime/app/silent_release_execution.py --state-root /var/lib/aios-silent-release-shadow --repository /data/aios-gitops/repo.git --host 127.0.0.1 --port 18891 --poll-seconds 30
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/aios-silent-release-shadow
ReadOnlyPaths=/data/aios-gitops/repo.git /usr/libexec/aios-silent-release-shadow
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,107 @@
# AIOS Silent Release Execution Readiness
## Scope and stop point
This change makes the controller execution-ready while deploying only a
localhost-bound Shadow control plane. Both production gates remain false:
```text
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
```
Shadow completion is evidence preparation, not a production release. It cannot
write a production tag, switch a business runtime, or mutate Baseline/Registry.
## Recovery state machine
Every expired or `FAILED_RECOVERABLE` capability requires fresh, operation-bound
probe evidence before another adapter may run:
```text
NOT_APPLIED -> one new exclusive claim may retry
APPLIED_VERIFIED -> mark COMPLETED without adapter replay
INDETERMINATE -> RECONCILIATION_REQUIRED; no automatic writes
CONFLICT -> MANUAL_OWNER_REVIEW_REQUIRED; no automatic writes
```
Probe evidence binds the immutable operation digest, is freshness checked, and
is appended to a separate SHA-256 hash-chained journal. Completion still
requires the active claim ID. File locks and leases ensure that concurrent
recovery workers cannot execute the same adapter.
`TAG_PENDING` uses independent remote ref, annotated object, peeled target,
Gitea API, Gitea listing, and repository identity evidence. Exact absence may
roll back a switched runtime. Exact publication continues to finalization.
Unavailable/divergent evidence stops at reconciliation; a different OID or
target stops for Owner review. No branch can force, delete, or rewrite a tag.
## Trusted build and approval
Callers submit only an event ID and exact source commit. Test, canary, artifact,
manifest, rollback, secret scan, Git and Gitea evidence are generated by the
trusted builder. The builder emits an Ed25519 attestation binding:
- builder attestation/key IDs;
- source commit and tree OID;
- artifact, manifest and package-closure SHA-256;
- test and canary evidence SHA-256;
- issuance time and signature.
The controller contains only trusted public keys. Production signing is exposed
through a KMS/HSM/secret-manager adapter; a local private-key adapter is marked
test-only and is used only for ephemeral Shadow acceptance.
Owner approvals are immutable, time-bounded, revocable, trust-domain bound, and
single-use per capability ledger. If the production approval backend reference
is absent, production execution is unavailable while Shadow remains available.
Caller-supplied `owner_id` is never a fallback.
## Persistent controller and API
`aios-silent-release-controller.service` runs independently from the business
GitOps API. It uses a single leader lock, durable intents/candidates/Sagas,
startup journal verification, idempotent main events, one notification record,
and recovery reporting. The final deployed service is report-only Shadow mode.
The Action contract exposes:
- `createReleaseIntent`
- `getReleaseCandidate`
- `getReleasePlan`
- `getReleaseAcceptance`
- `submitSignedApproval`
- `getReleaseSaga`
- `getCapabilityStatus`
- `startDeployment`
- `getRecoveryStatus`
Every Shadow response declares `execution_enabled=false`, `shadow_mode=true`
and `side_effects_executed=false`. `startDeployment` returns a simulated
`SHADOW_COMPLETED` result and never claims production capabilities.
## Migration and rollback
1. Merge through a governed pull request.
2. Build from a clean detached worktree at the exact merge commit.
3. Verify artifact, manifest, package closure, tests, live fixture, no-.git
canary, rollback archive and secret scan.
4. Install a versioned Shadow release without changing the business runtime.
5. Start the separate localhost-only controller with both gates false.
6. Run Shadow candidates and freeze production PID/tag/Baseline/Registry hashes.
7. Remove ephemeral test keys and temporary repositories.
Rollback stops and disables only the Shadow controller and restores/removes its
versioned `current` link. The existing production API, tag set, Baseline and
Registry are not involved.
## Future activation envelope
Activation requires a new one-time Owner approval binding the exact controller
commit, artifact/manifest/policy/unit hashes, trusted key IDs, KMS/secret
references, release and rollback roots, authoritative Git/repository identity,
Shadow candidate/evidence IDs, test results, activation plan and rollback plan.
That approval may only flip
`AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false` to `true`; each real release still
requires its own signed ReleasePlan. This change does not request or consume
that activation approval.
+63
View File
@@ -172,6 +172,13 @@ components:
candidate_id: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.-]{2,127}$'}
source_commit: {type: string, pattern: '^[0-9a-f]{40}$'}
version: {type: string, pattern: '^V[0-9]+R[0-9]+C[0-9]+(?:[-.][A-Za-z0-9][A-Za-z0-9.-]*)?$'}
ReleaseIntentInput:
type: object
required: [event_id, source_commit]
additionalProperties: false
properties:
event_id: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$'}
source_commit: {type: string, pattern: '^[0-9a-f]{40}$'}
SignedReleasePlanApproval:
type: object
required: [release_plan_id, candidate_id, source_commit, tree_oid, artifact_sha256, manifest_sha256, deployment_plan_sha256, release_plan_sha256, policy_sha256, approval_plan_id, evidence_closure_oid, owner_approval_record_oid, capabilities, owner_identity, trust_domain, issued_at, not_before, expires_at, nonce, key_id, signature]
@@ -209,6 +216,20 @@ components:
properties:
candidate_id: {type: string}
paths:
/createReleaseIntent:
post:
operationId: createReleaseIntent
summary: Create an idempotent intent; all build evidence comes from the trusted builder
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ReleaseIntentInput'}
responses:
'200': {description: Intent created or idempotently returned}
'400': {description: Caller evidence or invalid intent rejected}
x-openai-isConsequential: true
/createReleaseCandidate:
post:
operationId: createReleaseCandidate
@@ -233,6 +254,22 @@ paths:
responses:
'200': {description: Candidate}
'404': {description: Candidate not found}
/getReleasePlan:
get:
operationId: getReleasePlan
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Immutable release plan}
'404': {description: Candidate not found}
/getReleaseAcceptance:
get:
operationId: getReleaseAcceptance
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Builder attestation and acceptance checks}
'404': {description: Candidate not found}
/getApprovalEnvelope:
get:
operationId: getApprovalEnvelope
@@ -258,6 +295,20 @@ paths:
'403': {description: Signature, key, expiry, or revocation failure}
'409': {description: Conflicting release plan}
x-openai-isConsequential: true
/submitSignedApproval:
post:
operationId: submitSignedApproval
summary: Alias for immutable signed owner approval submission
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/SignedReleasePlanApproval'}
responses:
'200': {description: Signature verified and approval stored}
'403': {description: Production approval backend unavailable or signature invalid}
x-openai-isConsequential: true
/startDeployment:
post:
operationId: startDeployment
@@ -282,6 +333,18 @@ paths:
responses:
'200': {description: Saga snapshot}
'404': {description: Saga not found}
/getCapabilityStatus:
get:
operationId: getCapabilityStatus
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Durable single-use capability records}
/getRecoveryStatus:
get:
operationId: getRecoveryStatus
responses:
'200': {description: Fail-closed recovery classification}
/api/base:
get:
operationId: getBaseline
+268
View File
@@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""Build a deterministic, secret-scanned Silent Release evidence package."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import shutil
import subprocess
import tarfile
from pathlib import Path
from typing import Any
OID_RE = re.compile(r"^[0-9a-f]{40}$")
AUTHORIZATION_PATTERN = re.compile(
rb"Authorization:\s*(?:Bearer|token)\s+([A-Za-z0-9._~+/=-]{16,})",
re.I,
)
SECRET_PATTERNS = (
re.compile(rb"(?:ghp|glpat)_[A-Za-z0-9_-]{20,}", re.I),
re.compile(
rb"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----\r?\n"
rb"[A-Za-z0-9+/=\r\n]{64,}\r?\n"
rb"-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"
),
)
ALLOWED_AUTHORIZATION_PLACEHOLDER_SHA256 = frozenset(
{
"009f7191815ec615d09d0698457d048432e9a73f62ab4f26af1875009d0afbbc",
"599a7f359d1e11124054f8afeae201f2d265b8988d91cb5b7d4dc4c9e2225c30",
"f39dac6cbaba535e2c207cd0cd8f154974223c848f727f98b3564cea569b41cf",
}
)
TARGET_TESTS = (
"tests/test_silent_release_execution_readiness.py",
"tests/test_silent_release_controller.py",
"tests/test_silent_release_hardening.py",
"tests/test_silent_release_production_contract.py",
"tests/test_chatgpt_actions_tag.py",
"tests/test_gitea_tag_publication.py",
"tests/test_authoritative_git_context.py",
)
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def atomic_json(path: Path, payload: dict[str, Any]) -> None:
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", encoding="utf-8", newline="\n") as output:
json.dump(payload, output, sort_keys=True, separators=(",", ":"))
output.write("\n")
output.flush()
os.fsync(output.fileno())
os.replace(temporary, path)
def redact_output(value: str) -> str:
value = re.sub(
r"(?i)(Authorization:\s*(?:Bearer|token)\s+)"
r"[A-Za-z0-9._~+/=-]{8,}",
r"\1<redacted>",
str(value),
)
value = re.sub(
r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----.*?"
r"-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
"<redacted-private-key>",
value,
flags=re.DOTALL,
)
return value
def git(git_dir: Path, *arguments: str) -> str:
completed = subprocess.run(
["git", f"--git-dir={git_dir}", *arguments],
check=True,
capture_output=True,
text=True,
timeout=60,
env={"PATH": os.environ.get("PATH", "")},
)
return completed.stdout.strip()
def run_evidence(
command: list[str],
*,
cwd: Path,
output: Path,
timeout: int,
) -> dict[str, Any]:
completed = subprocess.run(
command,
cwd=cwd,
capture_output=True,
text=True,
timeout=timeout,
env={
"HOME": str(cwd / ".builder-home"),
"PATH": os.environ.get("PATH", ""),
"PYTHONDONTWRITEBYTECODE": "1",
},
)
record = {
"command": command,
"exit_code": completed.returncode,
"stderr": redact_output(completed.stderr[-20000:]),
"stdout": redact_output(completed.stdout[-20000:]),
}
atomic_json(output, record)
if completed.returncode != 0:
raise RuntimeError(f"trusted builder command failed: {command[0]}")
return record
def secret_scan(root: Path) -> dict[str, Any]:
findings: list[str] = []
for path in sorted(item for item in root.rglob("*") if item.is_file()):
if path.stat().st_size > 5 * 1024 * 1024:
continue
data = path.read_bytes()
authorization_tokens = AUTHORIZATION_PATTERN.findall(data)
unsafe_authorization = any(
hashlib.sha256(token.lower()).hexdigest()
not in ALLOWED_AUTHORIZATION_PLACEHOLDER_SHA256
for token in authorization_tokens
)
if unsafe_authorization or any(
pattern.search(data) for pattern in SECRET_PATTERNS
):
findings.append(path.relative_to(root).as_posix())
return {"findings": findings, "passed": not findings}
def build(git_dir: Path, commit: str, output: Path) -> dict[str, Any]:
commit = git(git_dir, "rev-parse", f"{commit}^{{commit}}")
if not OID_RE.fullmatch(commit):
raise ValueError("commit must resolve to a full OID")
tree_oid = git(git_dir, "rev-parse", f"{commit}^{{tree}}")
if output.exists():
raise FileExistsError("artifact output already exists")
output.mkdir(parents=True, mode=0o700)
source_tar = output / "source.tar"
subprocess.run(
[
"git",
f"--git-dir={git_dir}",
"archive",
"--format=tar",
f"--output={source_tar}",
commit,
],
check=True,
timeout=120,
env={"PATH": os.environ.get("PATH", "")},
)
canary = output / "canary-src"
canary.mkdir(mode=0o700)
with tarfile.open(source_tar, "r") as archive:
archive.extractall(canary, filter="data")
if any(path.name == ".git" for path in canary.rglob(".git")):
raise RuntimeError("no-.git canary contains Git metadata")
test_record = run_evidence(
["python3", "-m", "pytest", "-q", *TARGET_TESTS],
cwd=canary,
output=output / "test-evidence.json",
timeout=600,
)
fixture_record = run_evidence(
["bash", "tests/run_live_gitea_fixture.sh"],
cwd=canary,
output=output / "live-fixture-evidence.json",
timeout=600,
)
canary_record = {
"git_directory_present": False,
"live_fixture_exit_code": fixture_record["exit_code"],
"passed": True,
"target_test_exit_code": test_record["exit_code"],
}
atomic_json(output / "canary-evidence.json", canary_record)
scan = secret_scan(canary)
atomic_json(output / "secret-scan.json", scan)
if not scan["passed"]:
raise RuntimeError("secret scan failed")
manifest_files: list[dict[str, Any]] = []
tracked = git(git_dir, "ls-tree", "-r", "--name-only", commit).splitlines()
for relative in tracked:
path = canary / relative
if path.is_file():
manifest_files.append(
{
"path": relative,
"sha256": sha256(path),
"size": path.stat().st_size,
}
)
manifest = {
"commit": commit,
"file_count": len(manifest_files),
"files": manifest_files,
"tree_oid": tree_oid,
}
atomic_json(output / "manifest.json", manifest)
shutil.copyfile(source_tar, output / "rollback-source.tar")
closure = {
"artifact_sha256": sha256(source_tar),
"canary_evidence_sha256": sha256(output / "canary-evidence.json"),
"commit": commit,
"live_fixture_evidence_sha256": sha256(
output / "live-fixture-evidence.json"
),
"manifest_sha256": sha256(output / "manifest.json"),
"no_git_canary": True,
"rollback_artifact_sha256": sha256(
output / "rollback-source.tar"
),
"secret_scan_sha256": sha256(output / "secret-scan.json"),
"test_evidence_sha256": sha256(output / "test-evidence.json"),
"tree_oid": tree_oid,
}
atomic_json(output / "package-closure.json", closure)
closure["package_closure_sha256"] = sha256(
output / "package-closure.json"
)
checksummed = sorted(
path
for path in output.iterdir()
if path.is_file() and path.name != "checksums.sha256"
)
with (output / "checksums.sha256").open(
"w", encoding="utf-8", newline="\n"
) as checksums:
for path in checksummed:
checksums.write(f"{sha256(path)} {path.name}\n")
return closure
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--git-dir", required=True, type=Path)
parser.add_argument("--commit", required=True)
parser.add_argument("--output", required=True, type=Path)
arguments = parser.parse_args()
result = build(
arguments.git_dir.resolve(),
arguments.commit,
arguments.output.resolve(),
)
print(json.dumps(result, sort_keys=True))
if __name__ == "__main__":
main()
@@ -0,0 +1,470 @@
#!/usr/bin/env python3
"""Run two zero-side-effect Silent Release candidates in Shadow mode."""
from __future__ import annotations
import argparse
import dataclasses
import hashlib
import json
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from silent_release_controller import ReleaseCandidate, ReleaseState # noqa: E402
from silent_release_execution import ( # noqa: E402
LocalTestApprovalSigner,
LocalTestBuilderSigner,
PersistentSilentReleaseController,
SignedTrustedBuilderAdapter,
)
from silent_release_hardening import ( # noqa: E402
ApprovalKeyRing,
ApprovalRevocationStore,
BuilderAttestationVerifier,
DeploymentPlan,
FinalizationPlan,
ReleasePlan,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TagPlan,
TagPublicationProbeEvidence,
TrustedCandidateBundle,
TrustedReleasePolicy,
classify_tag_publication,
)
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def write_json(path: Path, value: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(
json.dumps(value, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
temporary.replace(path)
def oid(seed: str) -> str:
return hashlib.sha1(seed.encode("utf-8")).hexdigest()
def candidate_id(event_id: str, source_commit: str) -> str:
return "rc-shadow-" + hashlib.sha256(
f"{event_id}\0{source_commit}".encode("utf-8")
).hexdigest()[:20]
def make_plan(arguments: argparse.Namespace, candidate: str) -> ReleasePlan:
version = "shadow-" + candidate.removeprefix("rc-shadow-")
tag_oid = oid("tag:" + candidate)
approval_plan_id = "shadow-plan-" + candidate.removeprefix("rc-shadow-")
release_path = str(Path(arguments.release_path))
previous_baseline = arguments.baseline_before_sha256
return ReleasePlan(
deployment=DeploymentPlan(
service="aios-silent-release-controller",
release_path=release_path,
release_root=str(Path(release_path).parent.parent),
cutover_link=str(Path(release_path).parent.parent / "current"),
dropin_path=str(
Path(release_path)
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
),
dropin_sha256=arguments.systemd_unit_sha256,
systemd_unit_sha256=arguments.systemd_unit_sha256,
rollback_path=str(Path(arguments.rollback_artifact)),
rollback_sha256=arguments.artifact_sha256,
source_commit=arguments.source_commit,
tree_oid=arguments.tree_oid,
artifact_sha256=arguments.artifact_sha256,
manifest_sha256=arguments.manifest_sha256,
package_closure_sha256=arguments.package_closure_sha256,
authoritative_git_dir=str(Path(arguments.repository)),
repository_identity=arguments.repository_identity,
writer_expected=False,
baseline_before_sha256=previous_baseline,
registry_before_sha256=arguments.registry_before_sha256,
production_tag_refset_before_sha256=(
arguments.tag_refset_before_sha256
),
health_contract_version="shadow-health/v1",
timeout_policy={"health_seconds": 10},
retry_policy={"health_attempts": 1},
policy_sha256=arguments.policy_sha256,
health_contract={
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
},
),
tag=TagPlan(
version=version,
tag_type="annotated",
expected_tag_oid=tag_oid,
expected_target_oid=arguments.source_commit,
evidence_closure_oid=arguments.source_commit,
approval_plan_id=approval_plan_id,
repository_identity=arguments.repository_identity,
gitea_owner="aios",
gitea_repository="aios",
current_remote_tag_oid=None,
remote_absent_evidence_sha256=hashlib.sha256(
f"shadow-only:{candidate}".encode("utf-8")
).hexdigest(),
verification_contract="shadow-no-tag-write/v1",
),
finalization=FinalizationPlan(
version=version,
expected_tag_oid=tag_oid,
expected_target_oid=arguments.source_commit,
expected_previous_baseline_sha256=previous_baseline,
registry_expected_record={
"shadow_only": True,
"target_oid": arguments.source_commit,
},
baseline_expected_record={
"shadow_only": True,
"target_oid": arguments.source_commit,
},
final_consistency_contract="shadow-no-finalization/v1",
),
policy_sha256=arguments.policy_sha256,
)
def make_candidate(
arguments: argparse.Namespace, event_id: str
) -> ReleaseCandidate:
return ReleaseCandidate(
candidate_id=candidate_id(event_id, arguments.source_commit),
source_commit=arguments.source_commit,
tree_oid=arguments.tree_oid,
artifact_sha256=arguments.artifact_sha256,
manifest_sha256=arguments.manifest_sha256,
tests={"passed": True, "source": "trusted-artifact-evidence"},
canary={"passed": True, "without_git": True},
rollback_ready=True,
git_consistency={"consistent": True},
gitea_consistency={"consistent": True},
approval_state="PENDING",
state=ReleaseState.READY_FOR_APPROVAL,
)
def sign_approval(
service: SilentReleaseActionService,
signer: LocalTestApprovalSigner,
candidate: str,
nonce_suffix: str,
) -> SignedReleasePlanApproval:
release_candidate = service.candidates.load(candidate)
plan = service.release_plans.load(candidate)
now = datetime.now(timezone.utc)
unsigned = SignedReleasePlanApproval(
release_plan_id="shadow-release-" + candidate.removeprefix("rc-"),
candidate_id=candidate,
source_commit=release_candidate.source_commit,
tree_oid=release_candidate.tree_oid,
artifact_sha256=release_candidate.artifact_sha256,
manifest_sha256=release_candidate.manifest_sha256,
deployment_plan_sha256=plan.deployment.sha256,
release_plan_sha256=plan.sha256,
policy_sha256=plan.policy_sha256,
approval_plan_id=plan.tag.approval_plan_id,
evidence_closure_oid=plan.tag.evidence_closure_oid,
owner_approval_record_oid=oid("shadow-owner:" + candidate),
capabilities=(
"runtime_switch",
"tag_publish",
"baseline_registry_finalize",
),
owner_identity="owner:shadow-acceptance-test",
trust_domain="aios.shadow",
issued_at=now.isoformat().replace("+00:00", "Z"),
not_before=now.isoformat().replace("+00:00", "Z"),
expires_at=(now + timedelta(minutes=10))
.isoformat()
.replace("+00:00", "Z"),
nonce="shadow-nonce-" + nonce_suffix,
key_id=signer.key_id,
signature="",
)
return signer.sign(unsigned)
def tag_probe_results(plan: TagPlan) -> dict[str, str]:
exact = TagPublicationProbeEvidence(
repository_identity=plan.repository_identity,
remote_reachable=True,
gitea_api_reachable=True,
gitea_listing_reachable=True,
remote_tag_oid=plan.expected_tag_oid,
annotated_tag_object_oid=plan.expected_tag_oid,
remote_peeled_target_oid=plan.expected_target_oid,
gitea_api_tag_oid=plan.expected_tag_oid,
gitea_api_peeled_target_oid=plan.expected_target_oid,
gitea_listing_visible=True,
gitea_listing_tag_oid=plan.expected_tag_oid,
gitea_listing_peeled_target_oid=plan.expected_target_oid,
observed_at=1,
)
absent = dataclasses.replace(
exact,
remote_tag_oid=None,
annotated_tag_object_oid=None,
remote_peeled_target_oid=None,
gitea_api_tag_oid=None,
gitea_api_peeled_target_oid=None,
gitea_listing_visible=False,
gitea_listing_tag_oid=None,
gitea_listing_peeled_target_oid=None,
)
indeterminate = dataclasses.replace(exact, gitea_api_reachable=False)
conflict = dataclasses.replace(exact, remote_tag_oid=oid("conflict"))
return {
"APPLIED_VERIFIED": classify_tag_publication(exact, plan).value,
"CONFLICT": classify_tag_publication(conflict, plan).value,
"INDETERMINATE": classify_tag_publication(
indeterminate, plan
).value,
"NOT_APPLIED": classify_tag_publication(absent, plan).value,
}
def parser() -> argparse.ArgumentParser:
value = argparse.ArgumentParser()
value.add_argument("--state-root", required=True, type=Path)
value.add_argument("--repository", required=True, type=Path)
value.add_argument("--release-path", required=True, type=Path)
value.add_argument("--rollback-artifact", required=True, type=Path)
value.add_argument("--source-commit", required=True)
value.add_argument("--tree-oid", required=True)
value.add_argument("--artifact-sha256", required=True)
value.add_argument("--manifest-sha256", required=True)
value.add_argument("--package-closure-sha256", required=True)
value.add_argument("--policy-sha256", required=True)
value.add_argument("--systemd-unit-sha256", required=True)
value.add_argument("--baseline-before-sha256", required=True)
value.add_argument("--registry-before-sha256", required=True)
value.add_argument("--tag-refset-before-sha256", required=True)
value.add_argument("--repository-identity", default="aios/aios")
value.add_argument("--output", required=True, type=Path)
return value
def main() -> None:
arguments = parser().parse_args()
if sha256_file(arguments.rollback_artifact) != arguments.artifact_sha256:
raise SystemExit("rollback artifact hash mismatch")
unit_path = (
arguments.release_path
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
)
if sha256_file(unit_path) != arguments.systemd_unit_sha256:
raise SystemExit("systemd unit hash mismatch")
builder_signer = LocalTestBuilderSigner.generate()
builder_public = builder_signer.public_key.public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
owner_private = Ed25519PrivateKey.generate()
owner_public = owner_private.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
owner_signer = LocalTestApprovalSigner(
"shadow-owner-ephemeral", owner_private
)
def collector(intent):
event_id = str(intent["event_id"])
candidate = make_candidate(arguments, event_id)
return TrustedCandidateBundle(
candidate=candidate,
release_plan=make_plan(arguments, candidate.candidate_id),
checks={
"authoritative_git": True,
"artifact": True,
"canary": True,
"gitea": True,
"manifest": True,
"package_closure": True,
"rollback": True,
"secret_scan": True,
"tests": True,
},
)
release_root = str(arguments.release_path.parent.parent)
dropin = str(
arguments.release_path
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
)
policy = TrustedReleasePolicy(
services=frozenset({"aios-silent-release-controller"}),
release_roots=frozenset({release_root}),
cutover_links=frozenset({str(Path(release_root) / "current")}),
dropin_paths=frozenset({dropin}),
authoritative_git_dirs=frozenset({str(arguments.repository)}),
repository_identities=frozenset(
{arguments.repository_identity}
),
)
verifier = SignedApprovalVerifier(
ApprovalKeyRing({"shadow-owner-ephemeral": owner_public}),
ApprovalRevocationStore(arguments.state_root / "revocations"),
allowed_trust_domains={"aios.shadow"},
)
service = SilentReleaseActionService(
arguments.state_root,
execution_enabled=False,
approval_verifier=verifier,
candidate_builder=SignedTrustedBuilderAdapter(
collector, builder_signer
),
builder_attestation_verifier=BuilderAttestationVerifier(
{builder_signer.key_id: builder_public}
),
trusted_policy=policy,
expected_repository=arguments.repository_identity,
policy_sha256=arguments.policy_sha256,
)
first_event = "main-" + arguments.source_commit
first = service.create_release_intent(
{
"event_id": first_event,
"source_commit": arguments.source_commit,
}
)
first_duplicate = service.create_release_intent(
{
"event_id": first_event,
"source_commit": arguments.source_commit,
}
)
first_notification = service.ensure_owner_notification(
first["candidate_id"]
)
repeated_notification = service.ensure_owner_notification(
first["candidate_id"]
)
first_approval = sign_approval(
service, owner_signer, first["candidate_id"], "candidate-one"
)
first_submit = service.submit_owner_approval(
first_approval.to_dict()
)
first_submit_repeated = service.submit_owner_approval(
first_approval.to_dict()
)
first_start = service.start_deployment(first["candidate_id"])
second = service.create_release_intent(
{
"event_id": "shadow-indeterminate-" + arguments.source_commit,
"source_commit": arguments.source_commit,
}
)
second_plan = service.release_plans.load(second["candidate_id"])
probe_results = tag_probe_results(second_plan.tag)
first_controller = PersistentSilentReleaseController(
arguments.state_root,
service,
main_probe=lambda: arguments.source_commit,
)
controller_first = first_controller.run_once()
controller_restarted = PersistentSilentReleaseController(
arguments.state_root,
service,
main_probe=lambda: arguments.source_commit,
).run_once()
report = {
"approval_backend": "ephemeral-shadow-test-only",
"artifact_sha256": arguments.artifact_sha256,
"builder_attestation": "VERIFIED",
"candidate_count": 2,
"candidates": [
{
"candidate_id": first["candidate_id"],
"duplicate_intent_idempotent": first_duplicate["idempotent"],
"notification_idempotent": (
repeated_notification["idempotent"]
),
"notification_initial_idempotent": (
first_notification["idempotent"]
),
"replayed_approval_status": (
first_submit_repeated["status"]
),
"shadow_start": first_start,
"signed_approval_status": first_submit["status"],
},
{
"candidate_id": second["candidate_id"],
"tag_publication_probe": "INDETERMINATE",
},
],
"controller_restart_idempotent": (
controller_restarted["intent"]["idempotent"]
),
"controller_restart_notification_idempotent": (
controller_restarted["notification"]["idempotent"]
),
"controller_status": controller_first["startup"]["status"],
"execution_enabled": False,
"manifest_sha256": arguments.manifest_sha256,
"package_closure_sha256": arguments.package_closure_sha256,
"private_key_files_written": False,
"production_tag": "NOT_EXECUTED",
"repository_identity": arguments.repository_identity,
"shadow_mode": True,
"side_effects_executed": False,
"source_commit": arguments.source_commit,
"tag_probe_outcomes": probe_results,
"tree_oid": arguments.tree_oid,
}
write_json(arguments.output, report)
print(
json.dumps(
{
"candidate_count": 2,
"execution_enabled": False,
"output": str(arguments.output),
"shadow_mode": True,
"side_effects_executed": False,
"status": "SHADOW_ACCEPTANCE_PASSED",
},
sort_keys=True,
)
)
if __name__ == "__main__":
main()
@@ -0,0 +1,728 @@
from __future__ import annotations
import dataclasses
import json
import sys
import threading
import urllib.request
from datetime import datetime, timedelta, timezone
from http.server import ThreadingHTTPServer
from pathlib import Path
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
sys.path.insert(0, str(ROOT / "scripts"))
from silent_release_hardening import ( # noqa: E402
CapabilityAuthorizationError,
CapabilityController,
CapabilityLedger,
ClaimProbeResult,
ApprovalKeyRing,
ApprovalRevocationStore,
BuilderAttestationVerifier,
DeploymentPlan,
FinalizationPlan,
ProbeEvidence,
ReleasePlan,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TagPlan,
TagPublicationProbeEvidence,
TrustedCandidateBundle,
TrustedReleasePolicy,
classify_tag_publication,
)
from silent_release_controller import ReleaseCandidate, ReleaseState # noqa: E402
from silent_release_execution import ( # noqa: E402
LocalTestApprovalSigner,
LocalTestBuilderSigner,
PersistentSilentReleaseController,
ShadowActionHandler,
SignedTrustedBuilderAdapter,
UnavailableProductionApprovalBackend,
)
from build_silent_release_artifact import ( # noqa: E402
redact_output,
secret_scan,
)
COMMIT = "1" * 40
TAG_OID = "2" * 40
def approval() -> SignedReleasePlanApproval:
return SignedReleasePlanApproval(
release_plan_id="release-plan-readiness-001",
candidate_id="rc-readiness-001",
source_commit=COMMIT,
tree_oid="3" * 40,
artifact_sha256="4" * 64,
manifest_sha256="5" * 64,
deployment_plan_sha256="6" * 64,
policy_sha256="7" * 64,
approval_plan_id="owner-plan-readiness-001",
evidence_closure_oid="8" * 40,
owner_approval_record_oid="9" * 40,
capabilities=(
"runtime_switch",
"tag_publish",
"baseline_registry_finalize",
),
expires_at="2099-01-01T00:00:00Z",
nonce="nonce-readiness-000000001",
key_id="owner-key-readiness-1",
signature="test-signature",
)
def tag_plan() -> TagPlan:
return TagPlan(
version="shadow-readiness-001",
tag_type="annotated",
expected_tag_oid=TAG_OID,
expected_target_oid=COMMIT,
evidence_closure_oid="8" * 40,
approval_plan_id="owner-plan-readiness-001",
repository_identity="aios/aios",
gitea_owner="aios",
gitea_repository="aios",
current_remote_tag_oid=None,
remote_absent_evidence_sha256="a" * 64,
verification_contract="git+api+listing/v1",
)
def make_failed_claim(
ledger: CapabilityLedger,
*,
operation: str,
) -> dict:
claimed = ledger.claim(
approval(),
"runtime_switch",
operation=operation,
executor_id="worker-initial",
lease_seconds=10,
now=100,
)
ledger.fail_recoverable(
approval(),
"runtime_switch",
operation=operation,
claim_id=claimed["claim_id"],
reason="adapter_error",
)
return claimed
def test_failed_recoverable_not_applied_retries_once(tmp_path):
ledger = CapabilityLedger(
tmp_path / "ledger", maximum_probe_age_seconds=30
)
make_failed_claim(ledger, operation="runtime-retry")
calls: list[str] = []
controller = CapabilityController(
capability="runtime_switch",
ledger=ledger,
adapter=lambda: calls.append("adapter") or {"verified": True},
recovery_probe=lambda: ClaimProbeResult.NOT_APPLIED,
executor_id="worker-recovery",
)
assert controller.execute(
approval(), operation="runtime-retry"
)["status"] == "EXECUTED"
assert controller.execute(
approval(), operation="runtime-retry"
)["status"] == "IDEMPOTENT"
assert calls == ["adapter"]
def test_failed_recoverable_applied_verified_skips_adapter(tmp_path):
ledger = CapabilityLedger(tmp_path / "ledger")
make_failed_claim(ledger, operation="runtime-applied")
calls: list[str] = []
controller = CapabilityController(
capability="runtime_switch",
ledger=ledger,
adapter=lambda: calls.append("adapter") or {"verified": True},
recovery_probe=lambda: ClaimProbeResult.APPLIED_VERIFIED,
executor_id="worker-recovery",
)
assert controller.execute(
approval(), operation="runtime-applied"
)["status"] == "IDEMPOTENT"
assert calls == []
@pytest.mark.parametrize(
("probe", "expected"),
(
(
ClaimProbeResult.INDETERMINATE,
"RECONCILIATION_REQUIRED",
),
(
ClaimProbeResult.CONFLICT,
"MANUAL_OWNER_REVIEW_REQUIRED",
),
),
)
def test_failed_recoverable_unknown_or_conflict_stops_writes(
tmp_path, probe, expected
):
ledger = CapabilityLedger(tmp_path / "ledger")
make_failed_claim(ledger, operation=f"runtime-{probe.value.lower()}")
calls: list[str] = []
controller = CapabilityController(
capability="runtime_switch",
ledger=ledger,
adapter=lambda: calls.append("adapter") or {"verified": True},
recovery_probe=lambda: probe,
executor_id="worker-recovery",
)
assert controller.execute(
approval(), operation=f"runtime-{probe.value.lower()}"
)["status"] == expected
assert calls == []
def test_probe_must_bind_operation_and_be_fresh(tmp_path):
ledger = CapabilityLedger(
tmp_path / "ledger", maximum_probe_age_seconds=10
)
make_failed_claim(ledger, operation="runtime-freshness")
record = ledger.read(
approval(), "runtime_switch", operation="runtime-freshness"
)
assert record is not None
with pytest.raises(
CapabilityAuthorizationError, match="operation digest"
):
ledger.claim(
approval(),
"runtime_switch",
operation="runtime-freshness",
executor_id="worker-recovery",
lease_seconds=10,
now=120,
side_effect_probe=lambda: ProbeEvidence(
result=ClaimProbeResult.NOT_APPLIED,
operation_digest="0" * 64,
observed_at=120,
),
)
with pytest.raises(CapabilityAuthorizationError, match="freshness"):
ledger.claim(
approval(),
"runtime_switch",
operation="runtime-freshness",
executor_id="worker-recovery",
lease_seconds=10,
now=120,
side_effect_probe=lambda: ProbeEvidence(
result=ClaimProbeResult.NOT_APPLIED,
operation_digest=record["operation_digest"],
observed_at=100,
),
)
def test_probe_journal_is_hash_chained_and_tamper_fails_closed(tmp_path):
ledger = CapabilityLedger(tmp_path / "ledger")
make_failed_claim(ledger, operation="runtime-journal")
ledger.claim(
approval(),
"runtime_switch",
operation="runtime-journal",
executor_id="worker-recovery",
lease_seconds=10,
now=101,
side_effect_probe=lambda: ClaimProbeResult.INDETERMINATE,
)
operation_path = ledger._operation_path(
approval(), "runtime_switch", "runtime-journal"
)
records = ledger.verify_probe_journal(operation_path)
assert records[0]["payload"]["result"] == "INDETERMINATE"
journal = ledger._probe_journal_path(operation_path)
payload = json.loads(journal.read_text(encoding="utf-8"))
payload["payload"]["result"] = "NOT_APPLIED"
journal.write_text(json.dumps(payload) + "\n", encoding="utf-8")
with pytest.raises(Exception, match="hash chain"):
ledger.verify_probe_journal(operation_path)
def tag_evidence(**overrides) -> TagPublicationProbeEvidence:
values = {
"repository_identity": "aios/aios",
"remote_reachable": True,
"gitea_api_reachable": True,
"gitea_listing_reachable": True,
"remote_tag_oid": TAG_OID,
"annotated_tag_object_oid": TAG_OID,
"remote_peeled_target_oid": COMMIT,
"gitea_api_tag_oid": TAG_OID,
"gitea_api_peeled_target_oid": COMMIT,
"gitea_listing_visible": True,
"gitea_listing_tag_oid": TAG_OID,
"gitea_listing_peeled_target_oid": COMMIT,
"observed_at": 100,
}
values.update(overrides)
return TagPublicationProbeEvidence(**values)
def test_tag_probe_exact_absence_is_not_applied():
evidence = tag_evidence(
remote_tag_oid=None,
annotated_tag_object_oid=None,
remote_peeled_target_oid=None,
gitea_api_tag_oid=None,
gitea_api_peeled_target_oid=None,
gitea_listing_visible=False,
gitea_listing_tag_oid=None,
gitea_listing_peeled_target_oid=None,
)
assert (
classify_tag_publication(evidence, tag_plan())
== ClaimProbeResult.NOT_APPLIED
)
def test_tag_probe_exact_publication_is_applied_verified():
assert (
classify_tag_publication(tag_evidence(), tag_plan())
== ClaimProbeResult.APPLIED_VERIFIED
)
def test_tag_probe_unavailable_or_divergent_is_indeterminate():
assert (
classify_tag_publication(
tag_evidence(gitea_api_reachable=False), tag_plan()
)
== ClaimProbeResult.INDETERMINATE
)
assert (
classify_tag_publication(
tag_evidence(gitea_listing_tag_oid=None), tag_plan()
)
== ClaimProbeResult.INDETERMINATE
)
def test_tag_probe_same_name_different_oid_is_conflict():
assert (
classify_tag_publication(
tag_evidence(remote_tag_oid="f" * 40), tag_plan()
)
== ClaimProbeResult.CONFLICT
)
assert (
classify_tag_publication(
tag_evidence(remote_peeled_target_oid="e" * 40), tag_plan()
)
== ClaimProbeResult.CONFLICT
)
def readiness_release_plan(source_commit: str = COMMIT) -> ReleasePlan:
policy_sha = "7" * 64
return ReleasePlan(
deployment=DeploymentPlan(
service="aios-silent-release-controller",
release_path="/usr/libexec/aios-silent-release-shadow/release-1",
release_root="/usr/libexec/aios-silent-release-shadow",
cutover_link="/usr/libexec/aios-silent-release-shadow/current",
dropin_path=(
"/usr/libexec/aios-silent-release-shadow/release-1/"
"deploy/systemd/aios-silent-release-controller.service"
),
dropin_sha256="a" * 64,
systemd_unit_sha256="a" * 64,
rollback_path=(
"/usr/libexec/aios-silent-release-shadow/rollback-1"
),
rollback_sha256="b" * 64,
source_commit=source_commit,
tree_oid="3" * 40,
artifact_sha256="4" * 64,
manifest_sha256="5" * 64,
package_closure_sha256="c" * 64,
authoritative_git_dir="/data/aios-gitops/repo.git",
repository_identity="aios/aios",
writer_expected=False,
baseline_before_sha256="d" * 64,
registry_before_sha256="e" * 64,
production_tag_refset_before_sha256="f" * 64,
health_contract_version="shadow-health/v1",
timeout_policy={"health_seconds": 10},
retry_policy={"health_attempts": 1},
policy_sha256=policy_sha,
health_contract={"shadow_mode": True},
),
tag=TagPlan(
version="shadow-readiness-001",
tag_type="annotated",
expected_tag_oid=TAG_OID,
expected_target_oid=source_commit,
evidence_closure_oid="8" * 40,
approval_plan_id="owner-plan-readiness-001",
repository_identity="aios/aios",
gitea_owner="aios",
gitea_repository="aios",
current_remote_tag_oid=None,
remote_absent_evidence_sha256="a" * 64,
verification_contract="shadow-only/v1",
),
finalization=FinalizationPlan(
version="shadow-readiness-001",
expected_tag_oid=TAG_OID,
expected_target_oid=source_commit,
expected_previous_baseline_sha256="d" * 64,
registry_expected_record={
"shadow": True,
"target_oid": source_commit,
},
baseline_expected_record={
"shadow": True,
"target_oid": source_commit,
},
final_consistency_contract="shadow-simulated/v1",
),
policy_sha256=policy_sha,
)
def readiness_candidate(source_commit: str = COMMIT) -> ReleaseCandidate:
return ReleaseCandidate(
candidate_id="rc-shadow-" + source_commit[:12],
source_commit=source_commit,
tree_oid="3" * 40,
artifact_sha256="4" * 64,
manifest_sha256="5" * 64,
tests={"passed": True, "source": "trusted-builder"},
canary={"passed": True, "without_git": True},
rollback_ready=True,
git_consistency={"consistent": True},
gitea_consistency={"consistent": True},
approval_state="PENDING",
state=ReleaseState.READY_FOR_APPROVAL,
)
def trusted_service(tmp_path):
builder_signer = LocalTestBuilderSigner.generate()
builder_public = builder_signer.public_key.public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
def collector(intent):
source_commit = str(intent["source_commit"])
return TrustedCandidateBundle(
candidate=readiness_candidate(source_commit),
release_plan=readiness_release_plan(source_commit),
checks={
"authoritative_git": True,
"artifact": True,
"canary": True,
"gitea": True,
"manifest": True,
"package_closure": True,
"rollback": True,
"secret_scan": True,
"tests": True,
},
)
owner_private = Ed25519PrivateKey.generate()
owner_public = owner_private.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
verifier = SignedApprovalVerifier(
ApprovalKeyRing({"shadow-owner-test-key": owner_public}),
ApprovalRevocationStore(tmp_path / "revocations"),
allowed_trust_domains={"aios.shadow"},
)
policy = TrustedReleasePolicy(
services=frozenset({"aios-silent-release-controller"}),
release_roots=frozenset(
{"/usr/libexec/aios-silent-release-shadow"}
),
cutover_links=frozenset(
{"/usr/libexec/aios-silent-release-shadow/current"}
),
dropin_paths=frozenset(
{
"/usr/libexec/aios-silent-release-shadow/release-1/"
"deploy/systemd/aios-silent-release-controller.service"
}
),
authoritative_git_dirs=frozenset(
{"/data/aios-gitops/repo.git"}
),
repository_identities=frozenset({"aios/aios"}),
)
service = SilentReleaseActionService(
tmp_path / "state",
execution_enabled=False,
approval_verifier=verifier,
candidate_builder=SignedTrustedBuilderAdapter(
collector, builder_signer
),
builder_attestation_verifier=BuilderAttestationVerifier(
{builder_signer.key_id: builder_public}
),
trusted_policy=policy,
expected_repository="aios/aios",
policy_sha256="7" * 64,
)
return service, LocalTestApprovalSigner(
"shadow-owner-test-key", owner_private
)
def signed_shadow_approval(
service: SilentReleaseActionService,
signer: LocalTestApprovalSigner,
candidate_id: str,
) -> SignedReleasePlanApproval:
candidate = service.candidates.load(candidate_id)
plan = service.release_plans.load(candidate_id)
now = datetime.now(timezone.utc)
value = SignedReleasePlanApproval(
release_plan_id="release-plan-shadow-readiness-001",
candidate_id=candidate_id,
source_commit=candidate.source_commit,
tree_oid=candidate.tree_oid,
artifact_sha256=candidate.artifact_sha256,
manifest_sha256=candidate.manifest_sha256,
deployment_plan_sha256=plan.deployment.sha256,
release_plan_sha256=plan.sha256,
policy_sha256=plan.policy_sha256,
approval_plan_id=plan.tag.approval_plan_id,
evidence_closure_oid=plan.tag.evidence_closure_oid,
owner_approval_record_oid="9" * 40,
capabilities=(
"runtime_switch",
"tag_publish",
"baseline_registry_finalize",
),
owner_identity="owner:shadow-test",
trust_domain="aios.shadow",
issued_at=now.isoformat().replace("+00:00", "Z"),
not_before=now.isoformat().replace("+00:00", "Z"),
expires_at=(now + timedelta(minutes=5))
.isoformat()
.replace("+00:00", "Z"),
nonce="nonce-shadow-readiness-000001",
key_id="shadow-owner-test-key",
signature="",
)
return signer.sign(value)
def test_trusted_builder_attestation_and_caller_evidence_gate(tmp_path):
service, _ = trusted_service(tmp_path)
result = service.create_release_intent(
{"event_id": "webhook-main-001", "source_commit": COMMIT}
)
acceptance = service.get_acceptance(result["candidate_id"])
assert acceptance["attestation"]["builder_attestation_id"].startswith(
"builder-attestation-"
)
assert acceptance["checks"]["tests"] is True
with pytest.raises(
CapabilityAuthorizationError, match="caller-supplied evidence"
):
service.create_release_intent(
{
"event_id": "webhook-main-002",
"source_commit": COMMIT,
"tests": {"passed": True},
}
)
def test_builder_attestation_tamper_is_rejected(tmp_path):
service, _ = trusted_service(tmp_path)
bundle = service.candidate_builder.build(
{"event_id": "webhook-main-001", "source_commit": COMMIT}
)
tampered = dataclasses.replace(
bundle.attestation, artifact_sha256="0" * 64
)
with pytest.raises(
CapabilityAuthorizationError, match="does not bind"
):
dataclasses.replace(bundle, attestation=tampered).validate(
service.builder_attestation_verifier
)
def test_production_approval_backend_missing_disables_execution():
backend = UnavailableProductionApprovalBackend()
assert backend.available is False
with pytest.raises(
CapabilityAuthorizationError, match="not configured"
):
backend.verify()
def test_controller_duplicate_webhook_notification_and_restart_are_idempotent(
tmp_path,
):
service, _ = trusted_service(tmp_path)
first_controller = PersistentSilentReleaseController(
tmp_path / "state",
service,
main_probe=lambda: COMMIT,
)
first = first_controller.run_once()
second = first_controller.run_once()
restarted = PersistentSilentReleaseController(
tmp_path / "state",
service,
main_probe=lambda: COMMIT,
).run_once()
assert first["intent"]["candidate_id"] == second["intent"]["candidate_id"]
assert second["intent"]["idempotent"] is True
assert first["notification"]["idempotent"] is False
assert second["notification"]["idempotent"] is True
assert restarted["notification"]["idempotent"] is True
assert restarted["side_effects_executed"] is False
def test_controller_startup_journal_failure_stops_before_main_event(tmp_path):
service, _ = trusted_service(tmp_path)
service.sagas.create(
"rc-shadow-" + COMMIT[:12], "release-plan-shadow-readiness-001"
)
journal = next(service.sagas.root.glob("*.events.jsonl"))
journal.write_text('{"tampered":true}\n', encoding="utf-8")
probes: list[str] = []
result = PersistentSilentReleaseController(
tmp_path / "state",
service,
main_probe=lambda: probes.append("main") or COMMIT,
).run_once()
assert result["status"] == "FAIL_CLOSED"
assert result["intent"] is None
assert result["side_effects_executed"] is False
assert probes == []
def test_action_http_shadow_flow_has_zero_side_effects(tmp_path):
service, signer = trusted_service(tmp_path)
handler = type(
"TestShadowHandler",
(ShadowActionHandler,),
{"service": service, "controller": None},
)
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
base = f"http://127.0.0.1:{server.server_port}"
def post(path, payload):
request = urllib.request.Request(
base + path,
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request, timeout=5) as response:
return json.load(response)
try:
intent = post(
"/createReleaseIntent",
{"event_id": "http-main-001", "source_commit": COMMIT},
)
candidate_id = intent["candidate_id"]
approval = signed_shadow_approval(service, signer, candidate_id)
accepted = post("/submitSignedApproval", approval.to_dict())
deployed = post(
"/startDeployment", {"candidate_id": candidate_id}
)
with urllib.request.urlopen(
base + "/getReleasePlan?candidate_id=" + candidate_id,
timeout=5,
) as response:
plan = json.load(response)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
assert accepted["status"] == "VERIFIED_AND_STORED"
assert deployed == {
"candidate_id": candidate_id,
"execution_enabled": False,
"shadow_mode": True,
"side_effects": [],
"side_effects_executed": False,
"status": "SHADOW_COMPLETED",
}
assert plan["shadow_mode"] is True
assert plan["side_effects_executed"] is False
def test_secret_scan_redacts_placeholders_but_rejects_real_material(tmp_path):
(tmp_path / "safe.txt").write_text(
"Authorization: Bearer test-placeholder-token-000000",
encoding="utf-8",
)
assert secret_scan(tmp_path) == {"findings": [], "passed": True}
(tmp_path / "unsafe.txt").write_text(
"-----BEGIN "
+ "OPENSSH PRIVATE KEY-----\n"
+ ("A" * 80)
+ "\n-----END "
+ "OPENSSH PRIVATE KEY-----",
encoding="utf-8",
)
result = secret_scan(tmp_path)
assert result["passed"] is False
assert result["findings"] == ["unsafe.txt"]
secret = "live-" + ("A" * 40)
redacted = redact_output("Authorization: Bearer " + secret)
assert secret not in redacted
assert "<redacted>" in redacted
(tmp_path / "unsafe.txt").write_text(
"Authorization: " + "Bearer live-test-looking-secret-000001",
encoding="utf-8",
)
result = secret_scan(tmp_path)
assert result["passed"] is False
assert result["findings"] == ["unsafe.txt"]
def test_systemd_shadow_unit_has_both_gates_and_no_secret_environment():
unit = (
ROOT
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
).read_text(encoding="utf-8")
assert "AIOS_GITOPS_WRITES_ENABLED=false" in unit
assert "AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false" in unit
assert "--host 127.0.0.1" in unit
assert "PRIVATE_KEY=" not in unit
assert "TOKEN=" not in unit
assert "PASSWORD=" not in unit
+4 -1
View File
@@ -32,6 +32,7 @@ from silent_release_hardening import ( # noqa: E402
CapabilityAuthorizationError,
CapabilityController,
CapabilityLedger,
ClaimProbeResult,
DeploymentPlan,
PreSwitchValidator,
HardenedTagController,
@@ -367,6 +368,7 @@ def test_capability_failure_stays_reserved_and_retry_can_complete():
capability="runtime_switch",
ledger=ledger,
adapter=lambda: {"verified": True},
recovery_probe=lambda: ClaimProbeResult.NOT_APPLIED,
).execute(verified, operation="runtime-switch-001")
assert recovered["status"] == "EXECUTED"
assert ledger.reserve(
@@ -713,7 +715,8 @@ def test_shadow_mode_never_invokes_runtime_tag_or_finalize_adapters():
)
service.create_release_candidate(candidate().to_dict())
result = service.start_deployment("rc-hardening-001")
assert result["status"] == "SHADOW_MODE"
assert result["status"] == "SHADOW_COMPLETED"
assert result["side_effects_executed"] is False
def test_action_service_candidate_and_saga_are_durable():
@@ -501,7 +501,7 @@ def test_two_processes_execute_adapter_only_once(tmp_path):
(ClaimProbeResult.APPLIED_VERIFIED, "IDEMPOTENT"),
(
ClaimProbeResult.INDETERMINATE,
"MANUAL_OWNER_REVIEW_REQUIRED",
"RECONCILIATION_REQUIRED",
),
(
ClaimProbeResult.CONFLICT,
@@ -947,10 +947,11 @@ def test_failure_after_tag_enters_reconciliation_without_rollback(
assert first["state"] == SagaState.RECONCILIATION_REQUIRED
assert "rollback" not in calls
second = orchestrator.execute("rc-contract-001")
assert second["state"] == SagaState.COMPLETED
assert second["state"] == SagaState.RECONCILIATION_REQUIRED
assert calls.count("tag_publish") == 1
assert calls.count("registry_finalize") == 1
assert calls.count("baseline_finalize") == 2
assert calls.count("baseline_finalize") == 1
assert failures["count"] == 1
def test_actual_plan_change_blocks_before_any_adapter(tmp_path):
@@ -1051,6 +1052,7 @@ def test_start_deployment_ignores_caller_approval_object(tmp_path):
execution_enabled=True,
approval_verifier=object(),
candidate_builder=builder,
builder_attestation_verifier=object(),
trusted_policy=trusted_policy(),
pre_switch_validator=object(),
orchestrator=orchestrator,
@@ -1151,6 +1153,7 @@ def test_action_execution_true_runs_complete_orchestrator(tmp_path):
execution_enabled=True,
approval_verifier=orchestrator.approval_verifier,
candidate_builder=object(),
builder_attestation_verifier=object(),
trusted_policy=orchestrator.trusted_policy,
pre_switch_validator=orchestrator.pre_switch_validator,
orchestrator=orchestrator,