Compare commits

...
31 changed files with 3512 additions and 0 deletions
@@ -0,0 +1,26 @@
[Unit]
Description=AIOS dormant single-owner machine-status prerequisite
RefuseManualStop=no
[Service]
Type=oneshot
User=aiman
Group=gitops
Environment=AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED=false
Environment=AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE=false
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.service --component machine-status-producer --self-check --checkpoint-dir /var/lib/aios-gitops/runtime/single-owner-governance
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadWritePaths=/var/lib/aios-gitops/runtime/single-owner-governance
@@ -0,0 +1,26 @@
[Unit]
Description=AIOS dormant single-owner review adapter prerequisite
RefuseManualStop=no
[Service]
Type=oneshot
User=aiman
Group=gitops
Environment=AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED=false
Environment=AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE=false
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.service --component owner-review-adapter --self-check --checkpoint-dir /var/lib/aios-gitops/runtime/single-owner-governance
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadWritePaths=/var/lib/aios-gitops/runtime/single-owner-governance
@@ -0,0 +1,20 @@
[Unit]
Description=AIOS single-owner governance activation verifier
ConditionPathExists=/etc/aios-gitops/single-owner-activation-bindings.json
[Service]
Type=oneshot
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-single-owner-governance --verify --bindings /etc/aios-gitops/single-owner-activation-bindings.json --state-root /var/lib/aios-gitops/governance-activation --lock-path /run/aios-gitops/governance-activation.lock
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/aios-gitops/governance-activation /run/aios-gitops
RestrictSUIDSGID=true
UMask=0027
# Deliberately not installable. Deployment remains disabled/inactive;
# activation is an explicit approved CLI transaction, never an always-on unit.
@@ -0,0 +1,4 @@
d /var/lib/aios-gitops/governance-activation 0750 root gitops -
d /var/lib/aios-gitops/governance-activation/wal 0750 root gitops -
d /var/lib/aios-gitops/governance-activation/receipts 0750 root gitops -
d /run/aios-gitops 0750 root gitops -
@@ -0,0 +1,25 @@
{
"artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
"artifact_sha256": "32bc49c207ec454b6fd81b0484f5b257bb789481b37175995029a2ec78cc62fa",
"copied_live_db_acceptance": "PASS",
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
"independent_validator": "ALLOW_CANDIDATE",
"production_mutation": false,
"result": "PASS",
"rollback_artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-rollback-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
"rollback_artifact_sha256": "4efe1ea73d4fd72e5364fc91a16ad5e9d1e6ebe78d8aa466f7b11c4915b08403",
"runtime_source_sha256": {
"services/aios-single-owner-governance/aios_single_owner_governance/activation.py": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003",
"services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768",
"services/aios-single-owner-governance/aios_single_owner_governance/journal.py": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873",
"services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
"services/aios-single-owner-governance/aios_single_owner_governance/validator.py": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
},
"subtests_passed": 17,
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
"test_evidence_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/test-evidence-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.json",
"test_evidence_sha256": "c1633732767f3f051ffb01404d12d05a6168a4b01b91efdb34a64988d6dee684",
"tests_failed": 0,
"tests_passed": 90
}
@@ -0,0 +1,51 @@
# AIOS Single-Owner Governance Prerequisites
This package is a dormant candidate produced under the PR #29 prerequisite
authorization. It validates exact-head Owner approvals and machine gates,
models protection-policy compare-and-swap with compensation, builds evidence,
and exercises every mutation path only against disposable fixtures.
The prerequisite shadow services remain static and refuse live mutation. This
branch adds a separate, one-shot activation executor with `--plan`,
`--activate`, `--verify`, `--rollback`, and `--status` commands. It is not an
always-on writer and never enables global GitOps writes.
The executor is fail-closed: every consequential command is bound to an exact
approval merge, governance OID, evidence closure, Owner review, component
digests, full branch-protection snapshot, operation ID, and plan ID. It writes
a fsynced WAL before the single-row SQLite CAS, verifies the full applied row,
and only rolls back with a target-to-previous CAS. Audit records are
hash-chained and credentials are redacted.
## Hardening Contract
Fresh activation is authorized only before the approved `expires_at` instant.
An already-recorded `PREPARED` transaction may recover after expiry only to
reach a safe terminal state using the same exact bindings; `--verify` and
`--rollback` remain available for that recorded transaction after expiry.
`ROLLED_BACK` and `FAILED` are terminal states and must never be replayed as a
new activation.
The approved execution binding must pin the implementation commit and its
exact tree, the installed hashes of the five runtime source files
(`activation.py`, `gitea_protected_branch.py`, `journal.py`, `validator.py`,
and `state_machine.py`), and independent SHA-256 values for the deployment
artifact, rollback artifact, and test evidence. It must also bind an evidence
closure that is a commit with the exact direct parent and exactly one approved
closure file, including that file's Git blob OID and SHA-256.
Every required machine status is accepted only when its exact status record,
creator identity, producer provenance, target URL, description digest, head
OID, state, and observation time match the approved binding. A fixture status
or a dormant self-check is not machine-status producer provenance.
This contract hardens validation; it does not authorize deployment. The
existing governed approval rules still apply, and installation remains
disabled and inactive until the required merged approval authorizes one
explicit execution.
Installation must remain disabled and inactive until a merged
`SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION`
approval authorizes one explicit execution. The approved runtime paths are
`/var/lib/aios-gitops/governance-activation/` and
`/run/aios-gitops/governance-activation.lock`.
@@ -0,0 +1,115 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
"activation_approval_merge_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"activation_approval_pr_number": {"minimum": 1, "type": "integer"},
"activation_manifest_blob_oid": {"pattern": "^[0-9a-f]{40,64}$", "type": "string"},
"activation_manifest_path": {"pattern": "^approvals/.+\\.json$", "type": "string"},
"activation_manifest_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"artifact_path": {"pattern": "^/", "type": "string"},
"artifact_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"approval_db_path": {"minLength": 1, "type": "string"},
"approval_request_head_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"backend": {"enum": ["file", "sqlite"]},
"backend_path": {"minLength": 1, "type": "string"},
"branch": {"const": "governance/approvals"},
"evidence_closure_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"evidence_closure_path": {"pattern": "^(?!/)(?!.*\\.\\.).+", "type": "string"},
"evidence_closure_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"evidence_git_dir": {"minLength": 1, "type": "string"},
"executor": {"not": {"const": "owner"}, "type": "string"},
"expected_branch_protection_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expected_gitea_schema_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expected_governance_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"expected_state_machine_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expected_validator_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expires_at": {"format": "date-time", "type": "string"},
"forbidden_operations": {"additionalProperties": {"const": false}, "type": "object"},
"governance_git_dir": {"minLength": 1, "type": "string"},
"governance_ref": {"const": "refs/heads/governance/approvals"},
"implementation_commit_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"implementation_tree_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"machine_status_producer_id": {"minimum": 1, "type": "integer"},
"machine_status_producer_name": {"minLength": 1, "type": "string"},
"machine_checks": {
"additionalProperties": false,
"properties": {
"aios/branch-protection": {"$ref": "#/$defs/machineCheck"},
"aios/evidence-closure": {"$ref": "#/$defs/machineCheck"},
"aios/secret-scan": {"$ref": "#/$defs/machineCheck"},
"aios/state-machine": {"$ref": "#/$defs/machineCheck"},
"aios/validator": {"$ref": "#/$defs/machineCheck"}
},
"required": ["aios/branch-protection", "aios/evidence-closure", "aios/secret-scan", "aios/state-machine", "aios/validator"],
"type": "object"
},
"observed_evidence_closure_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"observed_governance_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"observed_owner_review_oid": {"type": ["integer", "string"]},
"observed_state_machine_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"observed_validator_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"operation_id": {"minLength": 1, "type": "string"},
"owner_review_oid": {"type": ["integer", "string"]},
"repo_id": {"minimum": 1, "type": "integer"},
"requester": {"not": {"const": "owner"}, "type": "string"},
"rollback_artifact_path": {"pattern": "^/", "type": "string"},
"rollback_artifact_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"runtime_sources": {
"items": {
"additionalProperties": false,
"properties": {
"git_path": {"minLength": 1, "type": "string"},
"runtime_path": {"pattern": "^/", "type": "string"},
"sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"}
},
"required": ["git_path", "runtime_path", "sha256"],
"type": "object"
},
"maxItems": 5,
"minItems": 5,
"type": "array"
},
"state_machine_path": {"minLength": 1, "type": "string"},
"target_branch_protection": {"type": "object"},
"task_id": {"minLength": 1, "type": "string"},
"test_evidence_path": {"pattern": "^/", "type": "string"},
"test_evidence_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"validator_path": {"minLength": 1, "type": "string"},
"wip": {"const": false}
},
"$defs": {
"machineCheck": {
"additionalProperties": false,
"properties": {
"head_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"status_id": {"minimum": 1, "type": "integer"},
"creator_id": {"minimum": 1, "type": "integer"},
"creator_login": {"minLength": 1, "type": "string"},
"state": {"const": "success"}
},
"required": ["state", "head_oid", "status_id", "creator_id", "creator_login"],
"type": "object"
}
},
"required": [
"task_id", "operation_id", "expected_governance_oid", "observed_governance_oid",
"expected_validator_sha256", "observed_validator_sha256", "validator_path",
"expected_state_machine_sha256", "observed_state_machine_sha256", "state_machine_path",
"expected_branch_protection_sha256", "expected_gitea_schema_sha256",
"activation_approval_merge_oid", "activation_approval_pr_number", "approval_request_head_oid",
"activation_manifest_path", "activation_manifest_blob_oid", "activation_manifest_sha256",
"evidence_closure_oid", "observed_evidence_closure_oid", "evidence_git_dir",
"evidence_closure_path", "evidence_closure_sha256",
"implementation_commit_oid", "implementation_tree_oid", "runtime_sources",
"artifact_path", "artifact_sha256", "rollback_artifact_path", "rollback_artifact_sha256",
"test_evidence_path", "test_evidence_sha256",
"machine_status_producer_id", "machine_status_producer_name",
"owner_review_oid", "observed_owner_review_oid", "approval_db_path", "expires_at",
"governance_git_dir", "governance_ref", "machine_checks", "forbidden_operations",
"target_branch_protection", "backend", "backend_path", "repo_id", "branch",
"requester", "executor", "wip"
],
"title": "AIOS Single Owner Live Activation Bindings",
"type": "object"
}
@@ -0,0 +1,20 @@
"""Dormant single-owner governance activation prerequisites."""
from .models import (
ApprovalContext,
GovernanceState,
MachineGate,
ProtectionSnapshot,
Review,
)
from .validator import SingleOwnerApprovalValidator, ValidationDenied
__all__ = [
"ApprovalContext",
"GovernanceState",
"MachineGate",
"ProtectionSnapshot",
"Review",
"SingleOwnerApprovalValidator",
"ValidationDenied",
]
@@ -0,0 +1,860 @@
from __future__ import annotations
import argparse
from contextlib import contextmanager
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import sqlite3
import subprocess
from typing import Any, Iterator, Mapping, Protocol
try:
import fcntl
except ImportError: # pragma: no cover
fcntl = None
from .gitea_protected_branch import (
CASConflict,
FileProtectedBranchAdapter,
SQLiteProtectedBranchAdapter,
calculate_snapshot_sha256,
)
from .journal import JournalStore, atomic_write_json, read_json, redact, sha256_json
from . import state_machine as state_machine_module
from . import validator as validator_module
DEFAULT_STATE_ROOT = Path("/var/lib/aios-gitops/governance-activation")
DEFAULT_LOCK_PATH = Path("/run/aios-gitops/governance-activation.lock")
LIVE_GITEA_DB = Path("/data/aios-gitops/runtime/gitea-bin/data/gitea.db")
LIVE_AUTHORITATIVE_GIT = Path("/data/aios-gitops/repo.git")
HEX40 = re.compile(r"^[0-9a-f]{40}$")
HEX64 = re.compile(r"^[0-9a-f]{64}$")
REQUIRED_BINDINGS = {
"task_id",
"operation_id",
"expected_governance_oid",
"expected_validator_sha256",
"expected_state_machine_sha256",
"expected_branch_protection_sha256",
"activation_approval_merge_oid",
"evidence_closure_oid",
"owner_review_oid",
"observed_governance_oid",
"observed_validator_sha256",
"observed_state_machine_sha256",
"observed_evidence_closure_oid",
"observed_owner_review_oid",
"approval_request_head_oid",
"activation_approval_pr_number",
"expires_at",
"validator_path",
"state_machine_path",
"governance_git_dir",
"governance_ref",
"evidence_git_dir",
"approval_db_path",
"expected_gitea_schema_sha256",
"backend",
"backend_path",
"repo_id",
"branch",
"activation_manifest_path",
"activation_manifest_blob_oid",
"activation_manifest_sha256",
"implementation_commit_oid",
"implementation_tree_oid",
"runtime_sources",
"artifact_path",
"artifact_sha256",
"rollback_artifact_path",
"rollback_artifact_sha256",
"test_evidence_path",
"test_evidence_sha256",
"evidence_closure_path",
"evidence_closure_sha256",
"machine_status_producer_id",
"machine_status_producer_name",
}
REQUIRED_MACHINE_CONTEXTS = {
"aios/validator",
"aios/state-machine",
"aios/branch-protection",
"aios/secret-scan",
"aios/evidence-closure",
}
REQUIRED_RUNTIME_GIT_PATHS = {
"services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
"services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
"services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
"services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
"services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
}
class InjectedCrash(BaseException):
pass
class Adapter(Protocol):
def read_current(self) -> dict[str, Any]: ...
def calculate_snapshot_sha256(self, snapshot: Mapping[str, Any]) -> str: ...
def apply_atomic(self, expected_sha256: str, target: Mapping[str, Any]) -> dict[str, Any]: ...
def verify_applied(self, expected_sha256: str) -> bool: ...
def rollback_atomic(self, expected_target_sha256: str, previous: Mapping[str, Any]) -> dict[str, Any]: ...
def verify_rollback(self, expected_sha256: str) -> bool: ...
@contextmanager
def exclusive_lock(path: Path) -> Iterator[None]:
path.parent.mkdir(parents=True, exist_ok=True, mode=0o750)
flags = os.O_RDWR | os.O_CREAT
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
fd = os.open(path, flags, 0o640)
try:
os.fchmod(fd, 0o640)
if fcntl is not None:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
yield
finally:
if fcntl is not None:
fcntl.flock(fd, fcntl.LOCK_UN)
os.close(fd)
def _require_hex(name: str, value: object, lengths: tuple[int, ...]) -> None:
if not isinstance(value, str) or len(value) not in lengths or not re.fullmatch(r"[0-9a-f]+", value):
raise RuntimeError(f"{name.upper()}_INVALID")
def _sha256_file(path: Path) -> str:
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise RuntimeError("VERIFIED_COMPONENT_PATH_INVALID")
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def _git(git_dir: Path, *args: str) -> str:
if not git_dir.is_absolute() or not git_dir.exists() or git_dir.is_symlink():
raise RuntimeError("GIT_OBJECT_STORE_INVALID")
result = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise RuntimeError("GIT_EVIDENCE_VERIFICATION_FAILED")
return result.stdout.strip()
def _git_raw(git_dir: Path, *args: str) -> bytes:
if not git_dir.is_absolute() or not git_dir.exists() or git_dir.is_symlink():
raise RuntimeError("GIT_OBJECT_STORE_INVALID")
result = subprocess.run(
["git", f"--git-dir={git_dir}", *args], capture_output=True, check=False
)
if result.returncode != 0:
raise RuntimeError("GIT_EVIDENCE_VERIFICATION_FAILED")
return result.stdout
def _parse_utc(value: object) -> datetime:
if not isinstance(value, str):
raise RuntimeError("EXPIRY_INVALID")
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise RuntimeError("EXPIRY_INVALID") from exc
if parsed.tzinfo is None:
raise RuntimeError("EXPIRY_INVALID")
return parsed.astimezone(timezone.utc)
def _utc_now() -> datetime:
return datetime.now(timezone.utc)
def _approved_execution_binding(bindings: Mapping[str, Any]) -> dict[str, Any]:
return {
"task_id": bindings["task_id"],
"operation_id": bindings["operation_id"],
"expected_validator_sha256": bindings["expected_validator_sha256"],
"expected_state_machine_sha256": bindings["expected_state_machine_sha256"],
"expected_branch_protection_sha256": bindings["expected_branch_protection_sha256"],
"expected_gitea_schema_sha256": bindings["expected_gitea_schema_sha256"],
"target_branch_protection_sha256": calculate_snapshot_sha256(
bindings["target_branch_protection"]
),
"evidence_closure_oid": bindings["evidence_closure_oid"],
"evidence_closure_path": bindings["evidence_closure_path"],
"evidence_closure_sha256": bindings["evidence_closure_sha256"],
"implementation_commit_oid": bindings["implementation_commit_oid"],
"implementation_tree_oid": bindings["implementation_tree_oid"],
"runtime_sources": bindings["runtime_sources"],
"artifact_path": bindings["artifact_path"],
"artifact_sha256": bindings["artifact_sha256"],
"rollback_artifact_path": bindings["rollback_artifact_path"],
"rollback_artifact_sha256": bindings["rollback_artifact_sha256"],
"test_evidence_path": bindings["test_evidence_path"],
"test_evidence_sha256": bindings["test_evidence_sha256"],
"machine_status_producer_id": bindings["machine_status_producer_id"],
"machine_status_producer_name": bindings["machine_status_producer_name"],
"backend": bindings["backend"],
"repo_id": bindings["repo_id"],
"branch": bindings["branch"],
"requester": bindings["requester"],
"executor": bindings["executor"],
"forbidden_operations": bindings["forbidden_operations"],
}
def _same_path(left: Path, right: Path) -> bool:
try:
return left.resolve(strict=True) == right.resolve(strict=True) and os.path.samefile(left, right)
except (FileNotFoundError, OSError):
return False
def _verify_authority_paths(bindings: Mapping[str, Any]) -> None:
if bindings["backend"] != "sqlite":
return
approval_db = Path(str(bindings["approval_db_path"]))
backend_db = Path(str(bindings["backend_path"]))
if not _same_path(approval_db, backend_db):
raise RuntimeError("APPROVAL_DB_BACKEND_DB_IDENTITY_MISMATCH")
live = _same_path(backend_db, LIVE_GITEA_DB)
if live:
if not _same_path(Path(str(bindings["governance_git_dir"])), LIVE_AUTHORITATIVE_GIT):
raise RuntimeError("AUTHORITATIVE_GOVERNANCE_GIT_REQUIRED")
if not _same_path(Path(str(bindings["evidence_git_dir"])), LIVE_AUTHORITATIVE_GIT):
raise RuntimeError("AUTHORITATIVE_EVIDENCE_GIT_REQUIRED")
return
acceptance_root = os.environ.get("AIOS_GOVERNANCE_ACTIVATION_ACCEPTANCE_ROOT")
if not acceptance_root:
raise RuntimeError("NON_LIVE_SQLITE_BACKEND_DENIED")
root = Path(acceptance_root).resolve(strict=True)
if not str(root).startswith("/run/aios-live-activation-acceptance-"):
raise RuntimeError("ACCEPTANCE_ROOT_INVALID")
for path in (approval_db, backend_db, Path(str(bindings["governance_git_dir"])), Path(str(bindings["evidence_git_dir"]))):
try:
path.resolve(strict=True).relative_to(root)
except (FileNotFoundError, ValueError) as exc:
raise RuntimeError("ACCEPTANCE_AUTHORITY_ESCAPE_DENIED") from exc
def _load_approved_manifest(bindings: Mapping[str, Any], git_dir: Path) -> dict[str, Any]:
path = str(bindings["activation_manifest_path"])
if not path.startswith("approvals/") or not path.endswith(".json") or ".." in Path(path).parts:
raise RuntimeError("ACTIVATION_MANIFEST_PATH_INVALID")
merge_oid = str(bindings["activation_approval_merge_oid"])
blob_oid = _git(git_dir, "rev-parse", f"{merge_oid}:{path}")
if blob_oid != bindings["activation_manifest_blob_oid"]:
raise RuntimeError("ACTIVATION_MANIFEST_BLOB_MISMATCH")
raw_result = subprocess.run(
["git", f"--git-dir={git_dir}", "show", f"{merge_oid}:{path}"],
capture_output=True,
check=False,
)
if raw_result.returncode != 0:
raise RuntimeError("ACTIVATION_MANIFEST_READ_FAILED")
if hashlib.sha256(raw_result.stdout).hexdigest() != bindings["activation_manifest_sha256"]:
raise RuntimeError("ACTIVATION_MANIFEST_SHA256_MISMATCH")
try:
manifest = json.loads(raw_result.stdout)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RuntimeError("ACTIVATION_MANIFEST_INVALID") from exc
if not isinstance(manifest, dict):
raise RuntimeError("ACTIVATION_MANIFEST_INVALID")
return manifest
def _require_approval_unexpired(bindings: Mapping[str, Any]) -> None:
if _utc_now() >= _parse_utc(bindings["expires_at"]):
raise RuntimeError("APPROVAL_EXPIRED")
def _safe_git_path(value: object) -> str:
path = str(value)
if not path or Path(path).is_absolute() or ".." in Path(path).parts:
raise RuntimeError("APPROVED_GIT_PATH_INVALID")
return path
def _verify_approved_entities(bindings: Mapping[str, Any], git_dir: Path) -> None:
implementation_oid = str(bindings["implementation_commit_oid"])
_git(git_dir, "cat-file", "-e", f"{implementation_oid}^{{commit}}")
if _git(git_dir, "rev-parse", f"{implementation_oid}^{{tree}}") != bindings["implementation_tree_oid"]:
raise RuntimeError("IMPLEMENTATION_TREE_MISMATCH")
sources = bindings["runtime_sources"]
if not isinstance(sources, list) or not sources:
raise RuntimeError("RUNTIME_SOURCES_INVALID")
seen_git: set[str] = set()
seen_runtime: set[str] = set()
loaded_runtime_paths = {
"services/aios-single-owner-governance/aios_single_owner_governance/activation.py": Path(__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py": Path(__import__(SQLiteProtectedBranchAdapter.__module__, fromlist=["x"]).__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/journal.py": Path(__import__(JournalStore.__module__, fromlist=["x"]).__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py": Path(state_machine_module.__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/validator.py": Path(validator_module.__file__).resolve(),
}
for source in sources:
if not isinstance(source, dict) or set(source) != {"git_path", "runtime_path", "sha256"}:
raise RuntimeError("RUNTIME_SOURCE_BINDING_INVALID")
git_path = _safe_git_path(source["git_path"])
runtime_path = str(source["runtime_path"])
_require_hex("runtime_source_sha256", source["sha256"], (64,))
if git_path in seen_git or runtime_path in seen_runtime:
raise RuntimeError("RUNTIME_SOURCE_BINDING_DUPLICATE")
seen_git.add(git_path)
seen_runtime.add(runtime_path)
if Path(runtime_path).resolve() != loaded_runtime_paths.get(git_path):
raise RuntimeError("RUNTIME_SOURCE_PATH_MISMATCH")
if hashlib.sha256(_git_raw(git_dir, "show", f"{implementation_oid}:{git_path}")).hexdigest() != source["sha256"]:
raise RuntimeError("IMPLEMENTATION_SOURCE_DIGEST_MISMATCH")
if _sha256_file(Path(runtime_path)) != source["sha256"]:
raise RuntimeError("RUNTIME_SOURCE_DIGEST_MISMATCH")
if seen_git != REQUIRED_RUNTIME_GIT_PATHS:
raise RuntimeError("RUNTIME_SOURCE_SET_MISMATCH")
for prefix in ("artifact", "rollback_artifact", "test_evidence"):
if _sha256_file(Path(str(bindings[f"{prefix}_path"]))) != bindings[f"{prefix}_sha256"]:
raise RuntimeError("APPROVED_ENTITY_DIGEST_MISMATCH")
closure_oid = str(bindings["evidence_closure_oid"])
if _git(git_dir, "cat-file", "-t", closure_oid) != "commit":
raise RuntimeError("EVIDENCE_CLOSURE_NOT_COMMIT")
parents = _git(git_dir, "rev-list", "--parents", "-n", "1", closure_oid).split()
if parents != [closure_oid, implementation_oid]:
raise RuntimeError("EVIDENCE_CLOSURE_PARENT_MISMATCH")
closure_path = _safe_git_path(bindings["evidence_closure_path"])
if hashlib.sha256(_git_raw(git_dir, "show", f"{closure_oid}:{closure_path}")).hexdigest() != bindings["evidence_closure_sha256"]:
raise RuntimeError("EVIDENCE_CLOSURE_SHA256_MISMATCH")
if _git(git_dir, "diff", "--name-status", implementation_oid, closure_oid) != f"A\t{closure_path}":
raise RuntimeError("EVIDENCE_CLOSURE_DIFF_INVALID")
def _verify_external_evidence(
bindings: Mapping[str, Any], *, require_unexpired: bool = True
) -> None:
_verify_authority_paths(bindings)
validator_sha = _sha256_file(Path(str(bindings["validator_path"])))
state_machine_sha = _sha256_file(Path(str(bindings["state_machine_path"])))
if validator_sha != bindings["expected_validator_sha256"] or validator_sha != bindings["observed_validator_sha256"]:
raise RuntimeError("VALIDATOR_DIGEST_MISMATCH")
if state_machine_sha != bindings["expected_state_machine_sha256"] or state_machine_sha != bindings["observed_state_machine_sha256"]:
raise RuntimeError("STATE_MACHINE_DIGEST_MISMATCH")
governance_git_dir = Path(str(bindings["governance_git_dir"]))
governance_oid = _git(governance_git_dir, "rev-parse", "--verify", str(bindings["governance_ref"]))
if governance_oid != bindings["expected_governance_oid"] or governance_oid != bindings["observed_governance_oid"]:
raise RuntimeError("WRONG_GOVERNANCE_OID_DENIED")
merge_oid = str(bindings["activation_approval_merge_oid"])
_git(governance_git_dir, "cat-file", "-e", f"{merge_oid}^{{commit}}")
if subprocess.run(
["git", f"--git-dir={governance_git_dir}", "merge-base", "--is-ancestor", merge_oid, governance_oid],
capture_output=True,
check=False,
).returncode != 0:
raise RuntimeError("ACTIVATION_APPROVAL_NOT_IN_GOVERNANCE_REF")
manifest = _load_approved_manifest(bindings, governance_git_dir)
if manifest.get("approval_type") != "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION":
raise RuntimeError("ACTIVATION_APPROVAL_TYPE_MISMATCH")
if manifest.get("execution_binding") != _approved_execution_binding(bindings):
raise RuntimeError("APPROVED_EXECUTION_BINDING_MISMATCH")
if manifest.get("execution_binding_sha256") != sha256_json(manifest["execution_binding"]):
raise RuntimeError("APPROVED_EXECUTION_BINDING_SHA256_MISMATCH")
if manifest.get("expires_at") != bindings["expires_at"]:
raise RuntimeError("APPROVAL_EXPIRY_BINDING_MISMATCH")
manifest_bound_fields = (
"implementation_commit_oid", "implementation_tree_oid", "runtime_sources",
"artifact_path", "artifact_sha256", "rollback_artifact_path",
"rollback_artifact_sha256", "test_evidence_path", "test_evidence_sha256",
"evidence_closure_oid", "evidence_closure_path", "evidence_closure_sha256",
"machine_status_producer_id", "machine_status_producer_name",
)
if any(manifest.get(field) != bindings[field] for field in manifest_bound_fields):
raise RuntimeError("APPROVED_ARTIFACT_BINDING_MISMATCH")
evidence_git_dir = Path(str(bindings["evidence_git_dir"]))
evidence_oid = str(bindings["evidence_closure_oid"])
if evidence_oid != bindings["observed_evidence_closure_oid"]:
raise RuntimeError("WRONG_EVIDENCE_CLOSURE_DENIED")
_verify_approved_entities(bindings, evidence_git_dir)
db_path = Path(str(bindings["approval_db_path"]))
if not db_path.is_absolute() or not db_path.is_file() or db_path.is_symlink():
raise RuntimeError("APPROVAL_DATABASE_INVALID")
expiry = _parse_utc(bindings["expires_at"])
if require_unexpired:
_require_approval_unexpired(bindings)
with sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) as connection:
connection.row_factory = sqlite3.Row
pr = connection.execute(
"SELECT pr.issue_id,pr.has_merged,pr.merged_commit_id,pr.merged_unix,i.is_closed "
"FROM pull_request pr JOIN issue i ON i.id=pr.issue_id "
"WHERE i.repo_id=? AND pr.\"index\"=? AND pr.base_branch=?",
(int(bindings["repo_id"]), int(bindings["activation_approval_pr_number"]), str(bindings["branch"])),
).fetchone()
if pr is None or int(pr["has_merged"]) != 1 or int(pr["is_closed"]) != 1:
raise RuntimeError("ACTIVATION_APPROVAL_PR_NOT_MERGED")
if pr["merged_commit_id"] != bindings["activation_approval_merge_oid"]:
raise RuntimeError("ACTIVATION_APPROVAL_MERGE_MISMATCH")
if int(pr["merged_unix"] or 0) <= 0 or datetime.fromtimestamp(int(pr["merged_unix"]), timezone.utc) > expiry:
raise RuntimeError("ACTIVATION_APPROVAL_MERGED_AFTER_EXPIRY")
review = connection.execute(
"SELECT r.id,r.type,r.reviewer_id,r.commit_id,r.stale,r.dismissed,r.created_unix,u.lower_name "
"FROM review r JOIN user u ON u.id=r.reviewer_id WHERE r.id=? AND r.issue_id=?",
(int(bindings["owner_review_oid"]), int(pr["issue_id"])),
).fetchone()
if review is None or int(review["id"]) != int(bindings["observed_owner_review_oid"]):
raise RuntimeError("WRONG_APPROVAL_OID_DENIED")
if (
int(review["type"]) != 1
or int(review["reviewer_id"]) != 2
or review["lower_name"] != "owner"
or review["commit_id"] != bindings["approval_request_head_oid"]
or int(review["stale"]) != 0
or int(review["dismissed"]) != 0
or datetime.fromtimestamp(int(review["created_unix"]), timezone.utc) >= expiry
):
raise RuntimeError("OWNER_APPROVAL_INVALID")
statuses = connection.execute(
"SELECT cs.id,cs.context,cs.state,cs.sha,cs.creator_id,u.lower_name AS creator_login "
"FROM commit_status cs LEFT JOIN user u ON u.id=cs.creator_id "
"WHERE cs.repo_id=? AND cs.sha=? ORDER BY cs.id DESC",
(int(bindings["repo_id"]), str(bindings["approval_request_head_oid"])),
).fetchall()
observed: dict[str, sqlite3.Row] = {}
for row in statuses:
if row["context"] in REQUIRED_MACHINE_CONTEXTS:
observed.setdefault(row["context"], row)
if set(observed) != REQUIRED_MACHINE_CONTEXTS:
raise RuntimeError("MACHINE_GATE_SET_MISMATCH")
for context, row in observed.items():
if row["state"] != "success" or row["sha"] != bindings["approval_request_head_oid"]:
raise RuntimeError("MACHINE_GATE_FAILED")
if (
int(row["creator_id"] or 0) != int(bindings["machine_status_producer_id"])
or row["creator_login"] != bindings["machine_status_producer_name"]
):
raise RuntimeError("MACHINE_GATE_PRODUCER_MISMATCH")
expected = bindings["machine_checks"][context]
actual = {
"state": row["state"], "head_oid": row["sha"], "status_id": int(row["id"]),
"creator_id": int(row["creator_id"]), "creator_login": row["creator_login"],
}
if expected != actual:
raise RuntimeError("MACHINE_GATE_BINDING_MISMATCH")
def validate_bindings(
bindings: Mapping[str, Any], *, require_unexpired: bool = True
) -> None:
missing = sorted(REQUIRED_BINDINGS - set(bindings))
if missing:
raise RuntimeError("MISSING_BINDINGS:" + ",".join(missing))
for name in (
"expected_governance_oid",
"activation_approval_merge_oid",
"evidence_closure_oid",
"implementation_commit_oid",
"implementation_tree_oid",
):
_require_hex(name, bindings[name], (40,))
for name in (
"expected_validator_sha256",
"expected_state_machine_sha256",
"expected_branch_protection_sha256",
"observed_validator_sha256",
"observed_state_machine_sha256",
"expected_gitea_schema_sha256",
"activation_manifest_sha256",
"artifact_sha256",
"rollback_artifact_sha256",
"test_evidence_sha256",
"evidence_closure_sha256",
):
_require_hex(name, bindings[name], (64,))
_require_hex("activation_manifest_blob_oid", bindings["activation_manifest_blob_oid"], (40, 64))
if not isinstance(bindings["owner_review_oid"], (int, str)) or str(bindings["owner_review_oid"]).strip() == "":
raise RuntimeError("OWNER_REVIEW_OID_INVALID")
if bindings["governance_ref"] != "refs/heads/governance/approvals":
raise RuntimeError("GOVERNANCE_REF_INVALID")
if bindings["branch"] != "governance/approvals":
raise RuntimeError("BRANCH_BINDING_DENIED")
if bindings["backend"] not in {"file", "sqlite"}:
raise RuntimeError("UNKNOWN_PROTECTION_BACKEND")
if bindings["observed_governance_oid"] != bindings["expected_governance_oid"]:
raise RuntimeError("WRONG_GOVERNANCE_OID_DENIED")
if bindings["observed_evidence_closure_oid"] != bindings["evidence_closure_oid"]:
raise RuntimeError("WRONG_EVIDENCE_CLOSURE_DENIED")
if str(bindings["observed_owner_review_oid"]) != str(bindings["owner_review_oid"]):
raise RuntimeError("WRONG_APPROVAL_OID_DENIED")
if bindings.get("requester") == "owner":
raise RuntimeError("REQUESTER_SELF_APPROVAL_DENIED")
if bindings.get("executor") == "owner":
raise RuntimeError("EXECUTOR_SELF_APPROVAL_DENIED")
if bindings.get("wip") is not False:
raise RuntimeError("WIP_BLOCKED")
checks = bindings.get("machine_checks")
if not isinstance(checks, dict) or set(checks) != REQUIRED_MACHINE_CONTEXTS:
raise RuntimeError("MACHINE_GATE_SET_MISMATCH")
if not isinstance(bindings["machine_status_producer_id"], int) or bindings["machine_status_producer_id"] < 1:
raise RuntimeError("MACHINE_STATUS_PRODUCER_INVALID")
if not isinstance(bindings["machine_status_producer_name"], str) or not bindings["machine_status_producer_name"]:
raise RuntimeError("MACHINE_STATUS_PRODUCER_INVALID")
for context, check in checks.items():
if (
not isinstance(check, dict)
or set(check) != {"state", "head_oid", "status_id", "creator_id", "creator_login"}
or check.get("state") != "success"
or check.get("head_oid") != bindings["approval_request_head_oid"]
or not isinstance(check.get("status_id"), int)
or check.get("status_id") < 1
or check.get("creator_id") != bindings["machine_status_producer_id"]
or check.get("creator_login") != bindings["machine_status_producer_name"]
):
raise RuntimeError(f"MACHINE_GATE_FAILED:{context}")
forbidden = bindings.get("forbidden_operations", {})
if any(bool(value) for value in forbidden.values()):
raise RuntimeError("FORBIDDEN_OPERATION_REQUESTED")
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
raise RuntimeError("GLOBAL_GITOPS_WRITES_MUST_REMAIN_FALSE")
if not isinstance(bindings.get("target_branch_protection"), dict):
raise RuntimeError("TARGET_BRANCH_PROTECTION_REQUIRED")
_verify_external_evidence(bindings, require_unexpired=require_unexpired)
def binding_payload_hash(bindings: Mapping[str, Any]) -> str:
stable = dict(bindings)
stable.pop("crash_at", None)
return sha256_json(redact(stable))
def plan_id(bindings: Mapping[str, Any], previous: Mapping[str, Any], target: Mapping[str, Any]) -> str:
stable = dict(bindings)
stable.pop("crash_at", None)
return sha256_json(
{
"bindings": redact(stable),
"previous": dict(previous),
"target": dict(target),
}
)
class ActivationEngine:
def __init__(self, adapter: Adapter, state_root: Path, lock_path: Path) -> None:
self.adapter = adapter
self.journal = JournalStore(state_root)
self.lock_path = lock_path
def plan(self, bindings: Mapping[str, Any]) -> dict[str, Any]:
validate_bindings(bindings)
previous = self.adapter.read_current()
previous_sha = calculate_snapshot_sha256(previous)
if previous_sha != bindings["expected_branch_protection_sha256"]:
raise CASConflict("EXPECTED_BRANCH_PROTECTION_SHA256_MISMATCH")
target = dict(bindings["target_branch_protection"])
# Validate without writing by using the public target validator through a
# disposable comparison: adapters enforce the same checks again in CAS.
from .gitea_protected_branch import validate_target
validate_target(previous, target)
result = {
"status": "PLAN_READY",
"task_id": bindings["task_id"],
"operation_id": bindings["operation_id"],
"plan_id": plan_id(bindings, previous, target),
"payload_hash": binding_payload_hash(bindings),
"previous_sha256": previous_sha,
"target_sha256": calculate_snapshot_sha256(target),
}
return result
def _recover(self, record: dict[str, Any]) -> dict[str, Any]:
current = self.adapter.read_current()
current_sha = calculate_snapshot_sha256(current)
previous_sha = record["previous_sha256"]
target_sha = record["target_sha256"]
if record["phase"] == "PREPARED":
if current_sha == previous_sha:
return {**record, "recovery": "RESUME_BEFORE_APPLY"}
if current_sha == target_sha:
record = {**record, "phase": "APPLIED", "recovery": "RESUME_AFTER_APPLY"}
self.journal.write(record["operation_id"], record)
return record
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
if record["phase"] in {"APPLIED", "COMMITTED"} and current_sha not in {target_sha, previous_sha}:
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
return record
def _ensure_closure(self, record: dict[str, Any], event: str) -> None:
expected_sha = (
record["target_sha256"] if record["phase"] == "COMMITTED" else record["previous_sha256"]
)
if calculate_snapshot_sha256(self.adapter.read_current()) != expected_sha:
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
atomic_write_json(
self.journal.root / "current-snapshot.json",
record["target"] if record["phase"] == "COMMITTED" else record["previous"],
)
atomic_write_json(self.journal.root / "rollback-snapshot.json", record["previous"])
self.journal.write_receipt(record["operation_id"], record)
if not self.journal.audit.has_event(record["operation_id"], event):
payload = {"event": event}
if event == "SINGLE_OWNER_ACTIVATION_COMMITTED":
payload.update(
payload_hash=record["payload_hash"],
target_sha256=record["target_sha256"],
)
self.journal.audit.append(record["operation_id"], payload)
atomic_write_json(self.journal.root / "state.json", record)
def activate(self, bindings: Mapping[str, Any], supplied_plan_id: str) -> dict[str, Any]:
with exclusive_lock(self.lock_path):
self.journal.audit.verify()
operation_id = str(bindings["operation_id"])
payload_hash = binding_payload_hash(bindings)
record = self.journal.load(operation_id)
if record:
if record.get("payload_hash") != payload_hash:
raise RuntimeError("OPERATION_ID_PAYLOAD_MISMATCH_DENIED")
if record.get("plan_id") != supplied_plan_id:
raise RuntimeError("PLAN_ID_MISMATCH")
if record.get("phase") == "ROLLED_BACK":
raise RuntimeError("OPERATION_TERMINAL_ROLLED_BACK")
if record.get("phase") == "FAILED":
raise RuntimeError("OPERATION_TERMINAL_FAILED")
prepared_at = _parse_utc(record.get("prepared_at"))
if prepared_at >= _parse_utc(bindings["expires_at"]):
raise RuntimeError("RECOVERY_AUTHORIZATION_INVALID")
record = self._recover(record)
if record["phase"] == "COMMITTED":
self._ensure_closure(record, "SINGLE_OWNER_ACTIVATION_COMMITTED")
return {"status": "ALREADY_COMMITTED", **record}
else:
validate_bindings(bindings)
planned = self.plan(bindings)
if supplied_plan_id != planned["plan_id"]:
raise RuntimeError("PLAN_ID_MISMATCH")
previous = self.adapter.read_current()
target = dict(bindings["target_branch_protection"])
if record is None:
if calculate_snapshot_sha256(previous) != planned["previous_sha256"]:
raise CASConflict("PREPARED_BOUNDARY_SNAPSHOT_CHANGED")
prepared_at = _utc_now()
if prepared_at >= _parse_utc(bindings["expires_at"]):
raise RuntimeError("APPROVAL_EXPIRED")
record = {
"task_id": bindings["task_id"],
"operation_id": operation_id,
"transaction_id": operation_id,
"payload_hash": payload_hash,
"plan_id": supplied_plan_id,
"phase": "PREPARED",
"prepared_at": prepared_at.isoformat().replace("+00:00", "Z"),
"previous": previous,
"target": target,
"previous_sha256": calculate_snapshot_sha256(previous),
"target_sha256": calculate_snapshot_sha256(target),
"activation_approval_merge_oid": bindings["activation_approval_merge_oid"],
"evidence_closure_oid": bindings["evidence_closure_oid"],
"owner_review_oid": bindings["owner_review_oid"],
}
self.journal.write(operation_id, record)
atomic_write_json(self.journal.root / "current-snapshot.json", previous)
atomic_write_json(self.journal.root / "rollback-snapshot.json", previous)
crash_at = bindings.get("crash_at")
if crash_at == "before_apply":
raise InjectedCrash("before_apply")
try:
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
if current_sha == record["previous_sha256"]:
applied = self.adapter.apply_atomic(record["previous_sha256"], record["target"])
record["target"] = applied
record["target_sha256"] = calculate_snapshot_sha256(applied)
elif current_sha != record["target_sha256"]:
raise CASConflict("CAS_CONFLICT")
if crash_at == "during_apply":
raise InjectedCrash("during_apply")
record["phase"] = "APPLIED"
self.journal.write(operation_id, record)
if crash_at == "after_apply_before_receipt":
raise InjectedCrash("after_apply_before_receipt")
if not self.adapter.verify_applied(record["target_sha256"]):
raise RuntimeError("POST_ACTIVATION_VERIFY_FAILED")
record["phase"] = "COMMITTED"
record["single_owner_policy_active"] = True
record["break_glass_active"] = False
self.journal.write(operation_id, record)
if crash_at == "after_commit_wal":
raise InjectedCrash("after_commit_wal")
self._ensure_closure(record, "SINGLE_OWNER_ACTIVATION_COMMITTED")
return {"status": "ACTIVATED", **record}
except InjectedCrash:
raise
except Exception as exc:
failure = {**record, "phase": "FAILED", "error": type(exc).__name__}
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
if current_sha == record["target_sha256"]:
self.adapter.rollback_atomic(record["target_sha256"], record["previous"])
if not self.adapter.verify_rollback(record["previous_sha256"]):
raise RuntimeError("ROLLBACK_VERIFY_FAILED") from exc
failure["phase"] = "ROLLED_BACK"
self.journal.write(operation_id, failure)
self.journal.write_receipt(operation_id, failure)
self.journal.audit.append(operation_id, {"event": failure["phase"], "error": type(exc).__name__})
raise
def verify(self, bindings: Mapping[str, Any], operation_id: str) -> dict[str, Any]:
record = self.journal.load(operation_id)
if not record:
raise RuntimeError("TRANSACTION_NOT_FOUND")
if record.get("payload_hash") != binding_payload_hash(bindings):
raise RuntimeError("OPERATION_ID_PAYLOAD_MISMATCH_DENIED")
self.journal.audit.verify()
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
passed = record.get("phase") == "COMMITTED" and current_sha == record.get("target_sha256")
if not passed:
raise RuntimeError("ACTIVATION_VERIFY_FAILED")
return {"status": "VERIFIED", "operation_id": operation_id, "target_sha256": current_sha}
def rollback(
self, bindings: Mapping[str, Any], operation_id: str, supplied_plan_id: str
) -> dict[str, Any]:
with exclusive_lock(self.lock_path):
self.journal.audit.verify()
record = self.journal.load(operation_id)
if not record:
raise RuntimeError("TRANSACTION_NOT_FOUND")
if record.get("payload_hash") != binding_payload_hash(bindings):
raise RuntimeError("OPERATION_ID_PAYLOAD_MISMATCH_DENIED")
if record.get("plan_id") != supplied_plan_id:
raise RuntimeError("PLAN_ID_MISMATCH")
for field in (
"activation_approval_merge_oid",
"evidence_closure_oid",
"owner_review_oid",
):
if str(record.get(field)) != str(bindings[field]):
raise RuntimeError("ROLLBACK_AUTHORIZATION_MISMATCH_DENIED")
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
if current_sha == record["previous_sha256"]:
record["phase"] = "ROLLED_BACK"
record["single_owner_policy_active"] = False
self.journal.write(operation_id, record)
self._ensure_closure(record, "ACTIVATION_ROLLED_BACK")
return {"status": "ALREADY_ROLLED_BACK", "operation_id": operation_id}
if current_sha != record["target_sha256"]:
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
self.adapter.rollback_atomic(record["target_sha256"], record["previous"])
if not self.adapter.verify_rollback(record["previous_sha256"]):
raise RuntimeError("ROLLBACK_VERIFY_FAILED")
if bindings.get("crash_at") == "after_rollback_cas":
raise InjectedCrash("after_rollback_cas")
record["phase"] = "ROLLED_BACK"
record["single_owner_policy_active"] = False
self.journal.write(operation_id, record)
if bindings.get("crash_at") == "after_rollback_wal":
raise InjectedCrash("after_rollback_wal")
self._ensure_closure(record, "ACTIVATION_ROLLED_BACK")
return {"status": "ROLLED_BACK", "operation_id": operation_id}
def status(self, operation_id: str | None = None) -> dict[str, Any]:
audit_head = self.journal.audit.verify()
if operation_id:
record = self.journal.load(operation_id)
if not record:
raise RuntimeError("TRANSACTION_NOT_FOUND")
return {"status": "OK", "audit_head": audit_head, "transaction": record}
state_path = self.journal.root / "state.json"
return {
"status": "OK",
"audit_head": audit_head,
"state": read_json(state_path) if state_path.exists() else None,
"transactions": list(self.journal.iter_transactions()),
}
def load_bindings(path: Path) -> dict[str, Any]:
return read_json(path)
def adapter_from_bindings(bindings: Mapping[str, Any]) -> Adapter:
backend = bindings.get("backend", "file")
backend_path = Path(str(bindings.get("backend_path", "")))
if not backend_path.is_absolute():
raise RuntimeError("BACKEND_PATH_MUST_BE_ABSOLUTE")
if backend == "file":
return FileProtectedBranchAdapter(backend_path)
if backend == "sqlite":
return SQLiteProtectedBranchAdapter(
backend_path,
repo_id=int(bindings.get("repo_id", 1)),
branch=str(bindings.get("branch", "governance/approvals")),
expected_schema_sha256=str(bindings["expected_gitea_schema_sha256"]),
)
raise RuntimeError("UNKNOWN_PROTECTION_BACKEND")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
action = parser.add_mutually_exclusive_group(required=True)
for name in ("plan", "activate", "verify", "rollback", "status"):
action.add_argument(f"--{name}", action="store_true")
parser.add_argument("--bindings", type=Path)
parser.add_argument("--plan-id")
parser.add_argument("--transaction")
parser.add_argument("--state-root", type=Path, default=DEFAULT_STATE_ROOT)
parser.add_argument("--lock-path", type=Path, default=DEFAULT_LOCK_PATH)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if args.status and args.bindings is None:
# Status without bindings is intentionally limited to durable local state.
journal = JournalStore(args.state_root)
result = {
"status": "OK",
"audit_head": journal.audit.verify(),
"transactions": list(journal.iter_transactions()),
}
print(json.dumps(result, sort_keys=True))
return 0
if args.bindings is None:
raise SystemExit("--bindings is required")
bindings = load_bindings(args.bindings)
engine = ActivationEngine(adapter_from_bindings(bindings), args.state_root, args.lock_path)
if args.plan:
result = engine.plan(bindings)
elif args.activate:
if not args.plan_id:
raise SystemExit("--plan-id is required")
result = engine.activate(bindings, args.plan_id)
elif args.verify:
result = engine.verify(bindings, args.transaction or str(bindings["operation_id"]))
elif args.rollback:
if not args.plan_id:
raise SystemExit("--plan-id is required")
result = engine.rollback(
bindings, args.transaction or str(bindings["operation_id"]), args.plan_id
)
else:
result = engine.status(args.transaction)
print(json.dumps(redact(result), sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,61 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
from typing import Mapping
def canonical_json(value: object) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
@dataclass(frozen=True)
class EvidenceClosure:
evidence: Mapping[str, str]
closure_sha256: str
class ActivationEvidenceClosureBuilder:
def build(self, evidence: Mapping[str, str]) -> EvidenceClosure:
required = {
"activation_prerequisites_commit_oid",
"activation_prerequisites_tree_oid",
"artifact_sha256",
"rollback_artifact_sha256",
"validator_sha256",
"state_machine_sha256",
"branch_protection_adapter_sha256",
"rollback_configuration_sha256",
"test_evidence_sha256",
"deployment_manifest_sha256",
"dormant_unit_manifest_sha256",
"dormant_deployment_evidence_sha256",
}
if set(evidence) != required:
raise ValueError("evidence set mismatch")
for value in evidence.values():
if len(value) != 40 and len(value) != 64:
raise ValueError("invalid evidence digest")
return EvidenceClosure(
dict(evidence), hashlib.sha256(canonical_json(evidence)).hexdigest()
)
class ApprovalMirrorStatusVerifier:
def verify(
self,
expected_oid: str,
*,
gitea_oid: str,
quarantine_oid: str,
authoritative_oid: str,
controller_visible_oid: str,
fsck_connectivity: str,
) -> bool:
return (
len(expected_oid) == 40
and {gitea_oid, quarantine_oid, authoritative_oid, controller_visible_oid}
== {expected_oid}
and fsck_connectivity == "PASS"
)
@@ -0,0 +1,26 @@
from __future__ import annotations
from contextlib import AbstractContextManager
from pathlib import Path
import shutil
import tempfile
from .models import ProtectionSnapshot
from .protection import MemoryProtectionStore
class DisposableGiteaFixture(AbstractContextManager):
def __init__(self, initial: ProtectionSnapshot) -> None:
self.root = Path(tempfile.mkdtemp(prefix="aios-governance-fixture-"))
self.protection = MemoryProtectionStore(initial)
self.destroyed = False
def __enter__(self) -> "DisposableGiteaFixture":
return self
def __exit__(self, exc_type, exc, tb) -> None:
resolved = self.root.resolve()
if not resolved.name.startswith("aios-governance-fixture-"):
raise RuntimeError("fixture cleanup path guard failed")
shutil.rmtree(resolved)
self.destroyed = True
@@ -0,0 +1,260 @@
from __future__ import annotations
import json
from pathlib import Path
import sqlite3
import time
from typing import Any, Mapping
from .journal import atomic_write_json, read_json, sha256_json
class CASConflict(RuntimeError):
pass
class UnsafePolicy(RuntimeError):
pass
def calculate_snapshot_sha256(snapshot: Mapping[str, Any]) -> str:
return sha256_json(dict(snapshot))
def validate_target(previous: Mapping[str, Any], target: Mapping[str, Any]) -> None:
if set(previous) != set(target):
raise UnsafePolicy("PROTECTION_ROW_SHAPE_CHANGED")
for field in ("id", "repo_id", "branch_name"):
if previous.get(field) != target.get(field):
raise UnsafePolicy(f"PROTECTION_IDENTITY_CHANGED:{field}")
if target.get("branch_name") != "governance/approvals":
raise UnsafePolicy("BRANCH_BINDING_DENIED")
if int(target.get("can_push", 1)) != 0:
raise UnsafePolicy("DIRECT_PUSH_MUST_REMAIN_DISABLED")
if int(target.get("required_approvals", 0)) != 1:
raise UnsafePolicy("EXACTLY_ONE_APPROVAL_REQUIRED")
if int(target.get("enable_approvals_whitelist", 0)) != 1:
raise UnsafePolicy("APPROVAL_WHITELIST_MUST_REMAIN_ENABLED")
if json.loads(target.get("approvals_whitelist_user_i_ds", "[]")) != [2]:
raise UnsafePolicy("OWNER_APPROVAL_WHITELIST_REQUIRED")
if int(target.get("enable_merge_whitelist", 0)) != 1:
raise UnsafePolicy("MERGE_WHITELIST_MUST_REMAIN_ENABLED")
if target.get("merge_whitelist_user_i_ds") != previous.get("merge_whitelist_user_i_ds"):
raise UnsafePolicy("MERGE_EXECUTOR_WHITELIST_CHANGE_DENIED")
if int(target.get("dismiss_stale_approvals", 0)) != 1:
raise UnsafePolicy("DISMISS_STALE_APPROVALS_REQUIRED")
if int(target.get("enable_status_check", 0)) != 1:
raise UnsafePolicy("STATUS_CHECKS_REQUIRED")
required_contexts = {
"aios/validator",
"aios/state-machine",
"aios/branch-protection",
"aios/secret-scan",
"aios/evidence-closure",
}
try:
contexts = set(json.loads(target.get("status_check_contexts", "[]")))
except (TypeError, ValueError) as exc:
raise UnsafePolicy("STATUS_CHECK_CONTEXTS_INVALID") from exc
if contexts != required_contexts:
raise UnsafePolicy("STATUS_CHECK_CONTEXTS_MISMATCH")
if int(target.get("block_on_rejected_reviews", 0)) != 1:
raise UnsafePolicy("REJECTED_REVIEWS_MUST_BLOCK")
if int(target.get("block_on_outdated_branch", 0)) != 1:
raise UnsafePolicy("OUTDATED_BRANCH_MUST_BLOCK")
if target.get("require_signed_commits") != previous.get("require_signed_commits"):
raise UnsafePolicy("SIGNATURE_POLICY_CHANGE_DENIED")
class FileProtectedBranchAdapter:
def __init__(self, path: Path) -> None:
self.path = path
@staticmethod
def calculate_snapshot_sha256(snapshot: Mapping[str, Any]) -> str:
return calculate_snapshot_sha256(snapshot)
def read_current(self) -> dict[str, Any]:
return read_json(self.path)
def compare_expected(self, expected_sha256: str) -> bool:
return calculate_snapshot_sha256(self.read_current()) == expected_sha256
def apply_atomic(
self, expected_sha256: str, target: Mapping[str, Any]
) -> dict[str, Any]:
previous = self.read_current()
if calculate_snapshot_sha256(previous) != expected_sha256:
raise CASConflict("CAS_CONFLICT")
validate_target(previous, target)
atomic_write_json(self.path, dict(target))
applied = self.read_current()
if calculate_snapshot_sha256(applied) != calculate_snapshot_sha256(target):
raise CASConflict("CAS_VERIFY_FAILED")
return applied
def verify_applied(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)
def rollback_atomic(
self, expected_target_sha256: str, previous: Mapping[str, Any]
) -> dict[str, Any]:
current = self.read_current()
if calculate_snapshot_sha256(current) != expected_target_sha256:
raise CASConflict("ROLLBACK_CONFLICT")
atomic_write_json(self.path, dict(previous))
restored = self.read_current()
if calculate_snapshot_sha256(restored) != calculate_snapshot_sha256(previous):
raise CASConflict("ROLLBACK_VERIFY_FAILED")
return restored
def verify_rollback(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)
class SQLiteProtectedBranchAdapter:
ALLOWED_UPDATE_FIELDS = (
"can_push",
"enable_merge_whitelist",
"merge_whitelist_user_i_ds",
"enable_status_check",
"status_check_contexts",
"enable_approvals_whitelist",
"approvals_whitelist_user_i_ds",
"required_approvals",
"block_on_rejected_reviews",
"block_on_official_review_requests",
"block_on_outdated_branch",
"dismiss_stale_approvals",
"ignore_stale_approvals",
"updated_unix",
)
def __init__(
self,
db_path: Path,
*,
repo_id: int = 1,
branch: str = "governance/approvals",
expected_schema_sha256: str,
) -> None:
self.db_path = db_path
self.repo_id = repo_id
self.branch = branch
self.expected_schema_sha256 = expected_schema_sha256
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.db_path, timeout=10)
connection.row_factory = sqlite3.Row
return connection
def schema_sha256(self, connection: sqlite3.Connection | None = None) -> str:
own = connection is None
connection = connection or self._connect()
try:
rows = [dict(row) for row in connection.execute("PRAGMA table_info(protected_branch)")]
return sha256_json(rows)
finally:
if own:
connection.close()
def _check_schema(self, connection: sqlite3.Connection) -> None:
if not isinstance(self.expected_schema_sha256, str) or len(self.expected_schema_sha256) != 64:
raise RuntimeError("GITEA_SCHEMA_FINGERPRINT_REQUIRED")
if self.schema_sha256(connection) != self.expected_schema_sha256:
raise RuntimeError("GITEA_SCHEMA_FINGERPRINT_MISMATCH")
def _read(self, connection: sqlite3.Connection) -> dict[str, Any]:
row = connection.execute(
"SELECT * FROM protected_branch WHERE repo_id=? AND branch_name=?",
(self.repo_id, self.branch),
).fetchone()
if row is None:
raise RuntimeError("PROTECTED_BRANCH_ROW_NOT_FOUND")
return dict(row)
def read_current(self) -> dict[str, Any]:
with self._connect() as connection:
self._check_schema(connection)
return self._read(connection)
@staticmethod
def calculate_snapshot_sha256(snapshot: Mapping[str, Any]) -> str:
return calculate_snapshot_sha256(snapshot)
def compare_expected(self, expected_sha256: str) -> bool:
return calculate_snapshot_sha256(self.read_current()) == expected_sha256
def apply_atomic(
self, expected_sha256: str, target: Mapping[str, Any]
) -> dict[str, Any]:
connection = self._connect()
try:
connection.execute("BEGIN IMMEDIATE")
self._check_schema(connection)
previous = self._read(connection)
if calculate_snapshot_sha256(previous) != expected_sha256:
raise CASConflict("CAS_CONFLICT")
validate_target(previous, target)
values = dict(target)
values["updated_unix"] = int(values.get("updated_unix") or time.time())
assignments = ",".join(f"{field}=?" for field in self.ALLOWED_UPDATE_FIELDS)
parameters = [values[field] for field in self.ALLOWED_UPDATE_FIELDS]
parameters.extend((self.repo_id, self.branch))
cursor = connection.execute(
f"UPDATE protected_branch SET {assignments} WHERE repo_id=? AND branch_name=?",
parameters,
)
if cursor.rowcount != 1:
raise CASConflict("CAS_ROWCOUNT_INVALID")
applied = self._read(connection)
expected_target = dict(target)
expected_target["updated_unix"] = values["updated_unix"]
if calculate_snapshot_sha256(applied) != calculate_snapshot_sha256(expected_target):
raise CASConflict("CAS_VERIFY_FAILED")
connection.commit()
return applied
except Exception:
connection.rollback()
raise
finally:
connection.close()
def verify_applied(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)
def rollback_atomic(
self, expected_target_sha256: str, previous: Mapping[str, Any]
) -> dict[str, Any]:
connection = self._connect()
try:
connection.execute("BEGIN IMMEDIATE")
self._check_schema(connection)
current = self._read(connection)
if calculate_snapshot_sha256(current) != expected_target_sha256:
raise CASConflict("ROLLBACK_CONFLICT")
for field in ("id", "repo_id", "branch_name"):
if current.get(field) != previous.get(field):
raise CASConflict("ROLLBACK_IDENTITY_MISMATCH")
assignments = ",".join(f"{field}=?" for field in self.ALLOWED_UPDATE_FIELDS)
parameters = [previous[field] for field in self.ALLOWED_UPDATE_FIELDS]
parameters.extend((self.repo_id, self.branch))
cursor = connection.execute(
f"UPDATE protected_branch SET {assignments} WHERE repo_id=? AND branch_name=?",
parameters,
)
if cursor.rowcount != 1:
raise CASConflict("ROLLBACK_ROWCOUNT_INVALID")
restored = self._read(connection)
if calculate_snapshot_sha256(restored) != calculate_snapshot_sha256(previous):
raise CASConflict("ROLLBACK_VERIFY_FAILED")
connection.commit()
return restored
except Exception:
connection.rollback()
raise
finally:
connection.close()
def verify_rollback(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)
@@ -0,0 +1,189 @@
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import tempfile
from typing import Any, Iterable
SENSITIVE_FRAGMENTS = (
"authorization",
"cookie",
"credential",
"password",
"private_key",
"secret",
"token",
)
def canonical_json(value: object) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
def sha256_json(value: object) -> str:
return hashlib.sha256(canonical_json(value)).hexdigest()
def redact(value: Any) -> Any:
if isinstance(value, dict):
return {
str(key): (
"[REDACTED]"
if any(fragment in str(key).lower() for fragment in SENSITIVE_FRAGMENTS)
else redact(item)
)
for key, item in value.items()
}
if isinstance(value, list):
return [redact(item) for item in value]
if isinstance(value, tuple):
return [redact(item) for item in value]
return value
def ensure_state_directory(path: Path) -> None:
if path.is_symlink():
raise RuntimeError("STATE_DIRECTORY_SYMLINK_DENIED")
path.mkdir(parents=True, exist_ok=True, mode=0o750)
path.chmod(0o750)
mode = path.stat().st_mode & 0o777
if mode != 0o750:
raise RuntimeError("STATE_DIRECTORY_MODE_INVALID")
def atomic_write_json(path: Path, value: object) -> None:
ensure_state_directory(path.parent)
raw = json.dumps(redact(value), sort_keys=True, indent=2).encode("utf-8") + b"\n"
with tempfile.NamedTemporaryFile(
dir=path.parent, prefix=f".{path.name}.", delete=False
) as handle:
temporary = Path(handle.name)
handle.write(raw)
handle.flush()
os.fsync(handle.fileno())
temporary.chmod(0o640)
os.replace(temporary, path)
path.chmod(0o640)
directory_fd = os.open(path.parent, os.O_RDONLY)
try:
os.fsync(directory_fd)
finally:
os.close(directory_fd)
def read_json(path: Path) -> dict[str, Any]:
raw = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(raw, dict):
raise RuntimeError("STATE_DOCUMENT_NOT_OBJECT")
return raw
class AuditChain:
def __init__(self, path: Path) -> None:
self.path = path
def _records(self) -> list[dict[str, Any]]:
if not self.path.exists():
return []
records: list[dict[str, Any]] = []
for line in self.path.read_text(encoding="utf-8").splitlines():
if line.strip():
record = json.loads(line)
if not isinstance(record, dict):
raise RuntimeError("AUDIT_RECORD_INVALID")
records.append(record)
return records
def verify(self) -> str:
previous = "0" * 64
for index, record in enumerate(self._records(), 1):
payload = record.get("payload")
if record.get("sequence") != index:
raise RuntimeError("AUDIT_SEQUENCE_INVALID")
if record.get("previous_event_hash") != previous:
raise RuntimeError("AUDIT_PREVIOUS_HASH_INVALID")
payload_hash = sha256_json(payload)
if record.get("payload_hash") != payload_hash:
raise RuntimeError("AUDIT_PAYLOAD_HASH_INVALID")
material = {
"sequence": index,
"previous_event_hash": previous,
"payload_hash": payload_hash,
"transaction_id": record.get("transaction_id"),
}
event_hash = sha256_json(material)
if record.get("event_hash") != event_hash:
raise RuntimeError("AUDIT_EVENT_HASH_INVALID")
previous = event_hash
return previous
def append(self, transaction_id: str, payload: dict[str, Any]) -> dict[str, Any]:
ensure_state_directory(self.path.parent)
previous = self.verify()
sequence = len(self._records()) + 1
clean_payload = redact(payload)
payload_hash = sha256_json(clean_payload)
material = {
"sequence": sequence,
"previous_event_hash": previous,
"payload_hash": payload_hash,
"transaction_id": transaction_id,
}
record = {
**material,
"payload": clean_payload,
"event_hash": sha256_json(material),
}
fd = os.open(self.path, os.O_WRONLY | os.O_APPEND | os.O_CREAT, 0o640)
try:
os.write(fd, canonical_json(record) + b"\n")
os.fsync(fd)
finally:
os.close(fd)
self.path.chmod(0o640)
return record
def has_event(self, transaction_id: str, event: str) -> bool:
self.verify()
return any(
record.get("transaction_id") == transaction_id
and isinstance(record.get("payload"), dict)
and record["payload"].get("event") == event
for record in self._records()
)
class JournalStore:
def __init__(self, root: Path) -> None:
self.root = root
self.wal = root / "wal"
self.receipts = root / "receipts"
ensure_state_directory(root)
ensure_state_directory(self.wal)
ensure_state_directory(self.receipts)
self.audit = AuditChain(root / "audit.jsonl")
def transaction_path(self, operation_id: str) -> Path:
if not operation_id or any(char not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-" for char in operation_id):
raise RuntimeError("OPERATION_ID_INVALID")
return self.wal / f"{operation_id}.json"
def receipt_path(self, operation_id: str) -> Path:
return self.receipts / f"{operation_id}.json"
def load(self, operation_id: str) -> dict[str, Any] | None:
path = self.transaction_path(operation_id)
return read_json(path) if path.exists() else None
def write(self, operation_id: str, value: dict[str, Any]) -> None:
atomic_write_json(self.transaction_path(operation_id), value)
def write_receipt(self, operation_id: str, value: dict[str, Any]) -> None:
atomic_write_json(self.receipt_path(operation_id), value)
def iter_transactions(self) -> Iterable[dict[str, Any]]:
for path in sorted(self.wal.glob("*.json")):
yield read_json(path)
@@ -0,0 +1,47 @@
from __future__ import annotations
from pathlib import Path
try:
import fcntl
except ImportError: # pragma: no cover - exercised by Windows validation
fcntl = None
import msvcrt
class MaintenanceLock:
"""Process fence used only by fixture and later approved activation executors."""
def __init__(self, path: Path) -> None:
self.path = path
self._handle = None
def __enter__(self) -> "MaintenanceLock":
self.path.parent.mkdir(parents=True, exist_ok=True)
self._handle = self.path.open("a+")
if fcntl is not None:
try:
fcntl.flock(self._handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
self._handle.close()
self._handle = None
raise
else:
self._handle.seek(0)
try:
msvcrt.locking(self._handle.fileno(), msvcrt.LK_NBLCK, 1)
except OSError as exc:
self._handle.close()
self._handle = None
raise BlockingIOError("maintenance lock held") from exc
return self
def __exit__(self, exc_type, exc, tb) -> None:
if self._handle is not None:
if fcntl is not None:
fcntl.flock(self._handle.fileno(), fcntl.LOCK_UN)
else:
self._handle.seek(0)
msvcrt.locking(self._handle.fileno(), msvcrt.LK_UNLCK, 1)
self._handle.close()
self._handle = None
@@ -0,0 +1,90 @@
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Mapping
class GovernanceState(str, Enum):
HUMAN_OWNER_APPROVAL_PENDING = "HUMAN_OWNER_APPROVAL_PENDING"
OWNER_APPROVAL_VERIFIED = "OWNER_APPROVAL_VERIFIED"
MACHINE_GATES_VERIFIED = "MACHINE_GATES_VERIFIED"
AUTO_MERGE_READY = "AUTO_MERGE_READY"
OWNER_APPROVED_EXECUTION_READY = "OWNER_APPROVED_EXECUTION_READY"
@dataclass(frozen=True)
class Review:
reviewer: str
reviewer_id: int
state: str
head_oid: str
reviewed_at: datetime
stale: bool = False
dismissed: bool = False
def __post_init__(self) -> None:
if self.reviewed_at.tzinfo is None:
raise ValueError("reviewed_at must be timezone-aware")
@dataclass(frozen=True)
class MachineGate:
context: str
state: str
head_oid: str
observed_at: datetime
def __post_init__(self) -> None:
if self.observed_at.tzinfo is None:
raise ValueError("observed_at must be timezone-aware")
@dataclass(frozen=True)
class ApprovalContext:
current_head_oid: str
requester: str
executor: str
owner: str
owner_id: int
reviews: tuple[Review, ...]
machine_gates: tuple[MachineGate, ...]
required_machine_contexts: tuple[str, ...]
expires_at: datetime
wip: bool
now: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def __post_init__(self) -> None:
if self.expires_at.tzinfo is None or self.now.tzinfo is None:
raise ValueError("approval timestamps must be timezone-aware")
@dataclass(frozen=True)
class ProtectionSnapshot:
branch: str
required_approvals: int
approval_whitelist: tuple[str, ...]
merge_whitelist: tuple[str, ...]
dismiss_stale_approvals: bool
require_status_checks: bool
status_contexts: tuple[str, ...]
direct_push: bool
force_push: bool
branch_deletion: bool
version: int
def as_dict(self) -> Mapping[str, Any]:
return {
"branch": self.branch,
"required_approvals": self.required_approvals,
"approval_whitelist": list(self.approval_whitelist),
"merge_whitelist": list(self.merge_whitelist),
"dismiss_stale_approvals": self.dismiss_stale_approvals,
"require_status_checks": self.require_status_checks,
"status_contexts": list(self.status_contexts),
"direct_push": self.direct_push,
"force_push": self.force_push,
"branch_deletion": self.branch_deletion,
"version": self.version,
}
@@ -0,0 +1,23 @@
from __future__ import annotations
class OperationDenied(PermissionError):
pass
class DormantOperationGuard:
"""Fail-closed operation boundary for the prerequisite deployment."""
DENIED = frozenset({
"protected_branch.direct_push",
"governance.direct_push",
"tag.direct_write",
"git.force_push",
"production.without_owner",
"branch_protection.live_update",
"single_owner_policy.activate",
})
def authorize(self, operation: str, *, fixture: bool = False) -> None:
if operation in self.DENIED or not fixture:
raise OperationDenied(operation)
@@ -0,0 +1,98 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
from typing import Protocol
from .models import ProtectionSnapshot
class CASConflict(RuntimeError):
pass
class DormantViolation(RuntimeError):
pass
def snapshot_sha256(snapshot: ProtectionSnapshot) -> str:
raw = json.dumps(snapshot.as_dict(), sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(raw).hexdigest()
class ProtectionStore(Protocol):
fixture: bool
def read(self, branch: str) -> ProtectionSnapshot: ...
def compare_and_swap(
self, branch: str, expected_sha256: str, target: ProtectionSnapshot
) -> bool: ...
@dataclass(frozen=True)
class ProtectionPlan:
branch: str
expected_previous_sha256: str
previous: ProtectionSnapshot
target: ProtectionSnapshot
target_sha256: str
class BranchProtectionCASMigrationAdapter:
"""Candidate adapter. Mutations are restricted to disposable fixtures."""
def plan(
self,
current: ProtectionSnapshot,
target: ProtectionSnapshot,
expected_previous_sha256: str,
) -> ProtectionPlan:
actual = snapshot_sha256(current)
if actual != expected_previous_sha256:
raise CASConflict("EXPECTED_PREVIOUS_SHA256_MISMATCH")
if current.branch != target.branch:
raise CASConflict("BRANCH_BINDING_MISMATCH")
if target.direct_push or target.force_push or target.branch_deletion:
raise DormantViolation("UNSAFE_TARGET_POLICY")
return ProtectionPlan(
current.branch, actual, current, target, snapshot_sha256(target)
)
def apply_fixture(self, store: ProtectionStore, plan: ProtectionPlan) -> ProtectionSnapshot:
if not store.fixture:
raise DormantViolation("LIVE_PROTECTION_MUTATION_DENIED")
if not store.compare_and_swap(
plan.branch, plan.expected_previous_sha256, plan.target
):
raise CASConflict("CONCURRENT_PROTECTION_CHANGE")
return store.read(plan.branch)
def rollback_fixture(self, store: ProtectionStore, plan: ProtectionPlan) -> ProtectionSnapshot:
if not store.fixture:
raise DormantViolation("LIVE_PROTECTION_ROLLBACK_DENIED")
if not store.compare_and_swap(plan.branch, plan.target_sha256, plan.previous):
raise CASConflict("ROLLBACK_CONFLICT")
return store.read(plan.branch)
class MemoryProtectionStore:
fixture = True
def __init__(self, snapshot: ProtectionSnapshot) -> None:
self._snapshot = snapshot
def read(self, branch: str) -> ProtectionSnapshot:
if self._snapshot.branch != branch:
raise KeyError(branch)
return self._snapshot
def compare_and_swap(
self, branch: str, expected_sha256: str, target: ProtectionSnapshot
) -> bool:
if self._snapshot.branch != branch:
return False
if snapshot_sha256(self._snapshot) != expected_sha256:
return False
self._snapshot = target
return True
@@ -0,0 +1,69 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
from pathlib import Path
import tempfile
from .models import ProtectionSnapshot
from .protection import MemoryProtectionStore, ProtectionPlan, snapshot_sha256
@dataclass(frozen=True)
class RollbackConfiguration:
branch: str
previous_sha256: str
target_sha256: str
payload: dict
class RollbackConfigurationManager:
def build(self, plan: ProtectionPlan) -> RollbackConfiguration:
return RollbackConfiguration(
plan.branch,
plan.expected_previous_sha256,
plan.target_sha256,
{"previous": plan.previous.as_dict(), "target": plan.target.as_dict()},
)
def install_fixture(self, root: Path, config: RollbackConfiguration) -> Path:
if not root.name.startswith("aios-governance-fixture-"):
raise PermissionError("rollback configuration may only be installed in fixture root")
root.mkdir(parents=True, exist_ok=True)
destination = root / "rollback-configuration.json"
raw = json.dumps(config.payload, sort_keys=True, indent=2).encode() + b"\n"
with tempfile.NamedTemporaryFile(dir=root, prefix=".rollback.", delete=False) as handle:
handle.write(raw)
handle.flush()
temporary = Path(handle.name)
temporary.chmod(0o600)
temporary.replace(destination)
return destination
def verify(self, config: RollbackConfiguration, plan: ProtectionPlan) -> bool:
return (
config.previous_sha256 == snapshot_sha256(plan.previous)
and config.target_sha256 == snapshot_sha256(plan.target)
)
@dataclass
class ProtectionCompensationSaga:
adapter: object
store: MemoryProtectionStore
plan: ProtectionPlan
applied: bool = False
rolled_back: bool = False
def execute_fixture(self) -> ProtectionSnapshot:
result = self.adapter.apply_fixture(self.store, self.plan)
self.applied = True
return result
def compensate_fixture(self) -> ProtectionSnapshot:
if not self.applied:
return self.store.read(self.plan.branch)
result = self.adapter.rollback_fixture(self.store, self.plan)
self.rolled_back = True
return result
@@ -0,0 +1,69 @@
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import tempfile
def runtime_state(component: str, prior_invocations: int = 0) -> dict:
return {
"component": component,
"deployment_mode": "DORMANT_SHADOW_ONLY",
"enabled": False,
"active": False,
"production_routing": False,
"production_mutation": False,
"global_gitops_writes_enabled": False,
"prior_invocations": prior_invocations,
"restart_recovery": prior_invocations > 0,
}
def checkpoint_state(component: str, root: Path) -> dict:
root.mkdir(parents=True, exist_ok=True)
destination = root / f"{component}.json"
prior = 0
if destination.exists():
previous = json.loads(destination.read_text(encoding="utf-8"))
if previous.get("component") != component or previous.get("deployment_mode") != "DORMANT_SHADOW_ONLY":
raise RuntimeError("checkpoint identity mismatch")
prior = int(previous.get("invocations", 0))
state = runtime_state(component, prior)
checkpoint = {**state, "invocations": prior + 1}
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=root, delete=False) as handle:
json.dump(checkpoint, handle, sort_keys=True)
handle.write("\n")
handle.flush()
temporary = Path(handle.name)
temporary.replace(destination)
return state
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--component", required=True, choices=("owner-review-adapter", "machine-status-producer"))
parser.add_argument("--self-check", action="store_true")
parser.add_argument("--activate", action="store_true")
parser.add_argument("--checkpoint-dir", type=Path)
args = parser.parse_args(argv)
if args.activate:
parser.error("activation is not authorized by the prerequisites candidate")
if os.environ.get("AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED", "false").lower() != "false":
parser.error("candidate must remain disabled")
if os.environ.get("AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE", "false").lower() != "false":
parser.error("activation must remain inactive")
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
parser.error("production mutation must remain disabled")
state = (
checkpoint_state(args.component, args.checkpoint_dir)
if args.checkpoint_dir is not None
else runtime_state(args.component)
)
print(json.dumps(state, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,54 @@
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
import tempfile
from .models import GovernanceState
class InvalidTransition(RuntimeError):
pass
_NEXT = {
GovernanceState.HUMAN_OWNER_APPROVAL_PENDING: GovernanceState.OWNER_APPROVAL_VERIFIED,
GovernanceState.OWNER_APPROVAL_VERIFIED: GovernanceState.MACHINE_GATES_VERIFIED,
GovernanceState.MACHINE_GATES_VERIFIED: GovernanceState.AUTO_MERGE_READY,
GovernanceState.AUTO_MERGE_READY: GovernanceState.OWNER_APPROVED_EXECUTION_READY,
}
@dataclass
class GovernanceStateMachine:
state: GovernanceState = GovernanceState.HUMAN_OWNER_APPROVAL_PENDING
def transition(self, target: GovernanceState) -> GovernanceState:
if _NEXT.get(self.state) != target:
raise InvalidTransition(f"{self.state.value}->{target.value}")
self.state = target
return self.state
def restore(self, checkpoint: GovernanceState) -> None:
if checkpoint not in GovernanceState:
raise InvalidTransition("unknown checkpoint")
self.state = checkpoint
def save_fixture(self, path: Path) -> None:
if not any(part.startswith("aios-governance-fixture-") for part in path.resolve().parts):
raise PermissionError("state checkpoint writes are fixture-only")
path.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle:
json.dump({"state": self.state.value}, handle, sort_keys=True)
handle.write("\n")
handle.flush()
temporary = Path(handle.name)
temporary.replace(path)
@classmethod
def load_fixture(cls, path: Path) -> "GovernanceStateMachine":
if not any(part.startswith("aios-governance-fixture-") for part in path.resolve().parts):
raise PermissionError("state checkpoint reads are fixture-only")
raw = json.loads(path.read_text(encoding="utf-8"))
return cls(GovernanceState(raw["state"]))
@@ -0,0 +1,38 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
class DormantStatusProducer(RuntimeError):
pass
@dataclass(frozen=True)
class StatusRecord:
context: str
state: str
head_oid: str
observed_at: str
class MachineStatusProducer:
def __init__(self, *, enabled: bool = False, fixture: bool = False) -> None:
self.enabled = enabled
self.fixture = fixture
def produce(self, context: str, state: str, head_oid: str) -> StatusRecord:
if not self.fixture:
raise DormantStatusProducer("LIVE_STATUS_PUBLICATION_DENIED")
if self.enabled:
raise DormantStatusProducer("DORMANT_CANDIDATE_MUST_NOT_BE_ENABLED")
if context not in {
"aios/machine-gates", "aios/secret-scan", "aios/evidence-closure"
}:
raise ValueError("unknown status context")
if state not in {"success", "failure"} or len(head_oid) != 40:
raise ValueError("invalid status record")
return StatusRecord(
context, state, head_oid,
datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
)
@@ -0,0 +1,69 @@
from __future__ import annotations
from dataclasses import dataclass
from .models import ApprovalContext, Review
class ValidationDenied(RuntimeError):
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
@dataclass(frozen=True)
class ValidationResult:
owner_review: Review
machine_contexts: tuple[str, ...]
class SingleOwnerApprovalValidator:
"""Pure candidate validator. It has no Gitea or production write path."""
def validate(self, context: ApprovalContext) -> ValidationResult:
if context.now >= context.expires_at:
raise ValidationDenied("APPROVAL_EXPIRED")
if context.wip:
raise ValidationDenied("WIP_BLOCKED")
if context.requester == context.owner:
raise ValidationDenied("REQUESTER_SELF_APPROVAL")
if context.executor == context.owner:
raise ValidationDenied("EXECUTOR_SELF_APPROVAL")
approved = [review for review in context.reviews if review.state == "APPROVED"]
owner_reviews = [
review for review in approved
if review.reviewer == context.owner and review.reviewer_id == context.owner_id
]
if len(owner_reviews) != 1:
raise ValidationDenied("OWNER_APPROVAL_REQUIRED")
owner_review = owner_reviews[0]
if owner_review.stale:
raise ValidationDenied("STALE_OWNER_APPROVAL")
if owner_review.dismissed:
raise ValidationDenied("DISMISSED_OWNER_APPROVAL")
if owner_review.head_oid != context.current_head_oid:
raise ValidationDenied("OWNER_APPROVAL_NOT_LATEST_HEAD")
if owner_review.reviewed_at >= context.expires_at:
raise ValidationDenied("OWNER_APPROVAL_EXPIRED")
if any(
review.state == "APPROVED"
and review.reviewer != context.owner
and review.reviewer_id != context.owner_id
for review in context.reviews
):
# Non-owner approvals may exist for legacy mechanics, but never
# satisfy or replace the human authorization decision.
pass
gates = {gate.context: gate for gate in context.machine_gates}
if set(gates) != set(context.required_machine_contexts):
raise ValidationDenied("MACHINE_GATE_SET_MISMATCH")
for gate in gates.values():
if gate.state != "success":
raise ValidationDenied("MACHINE_GATE_FAILED")
if gate.head_oid != context.current_head_oid:
raise ValidationDenied("MACHINE_GATE_NOT_LATEST_HEAD")
if gate.observed_at >= context.expires_at:
raise ValidationDenied("MACHINE_GATE_EXPIRED")
return ValidationResult(owner_review, tuple(sorted(gates)))
@@ -0,0 +1,16 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "aios-single-owner-governance"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []
[project.scripts]
aios-single-owner-governance = "aios_single_owner_governance.activation:main"
[tool.setuptools.packages.find]
where = ["."]
include = ["aios_single_owner_governance*"]
@@ -0,0 +1 @@
+51
View File
@@ -0,0 +1,51 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from aios_single_owner_governance.models import (
ApprovalContext,
MachineGate,
ProtectionSnapshot,
Review,
)
HEAD = "a" * 40
NOW = datetime(2026, 7, 28, 19, 0, tzinfo=timezone.utc)
CONTEXTS = ("aios/machine-gates", "aios/secret-scan", "aios/evidence-closure")
def owner_review(**changes) -> Review:
values = dict(
reviewer="owner", reviewer_id=2, state="APPROVED", head_oid=HEAD,
reviewed_at=NOW - timedelta(minutes=1), stale=False, dismissed=False,
)
values.update(changes)
return Review(**values)
def gates(*, state="success", head_oid=HEAD):
return tuple(MachineGate(name, state, head_oid, NOW) for name in CONTEXTS)
def context(**changes) -> ApprovalContext:
values = dict(
current_head_oid=HEAD, requester="agent", executor="merge-executor",
owner="owner", owner_id=2, reviews=(owner_review(),), machine_gates=gates(),
required_machine_contexts=CONTEXTS, expires_at=NOW + timedelta(hours=1),
wip=False, now=NOW,
)
values.update(changes)
return ApprovalContext(**values)
def legacy_protection(version=1) -> ProtectionSnapshot:
return ProtectionSnapshot(
"governance/approvals", 2, ("codex", "gpt"), ("codex", "gpt"),
True, False, (), False, False, False, version,
)
def single_owner_protection(version=2) -> ProtectionSnapshot:
return ProtectionSnapshot(
"governance/approvals", 1, ("owner",), ("owner",), True, True,
CONTEXTS, False, False, False, version,
)
@@ -0,0 +1,72 @@
from __future__ import annotations
from pathlib import Path
import unittest
from aios_single_owner_governance.fixture import DisposableGiteaFixture
from aios_single_owner_governance.protection import (
BranchProtectionCASMigrationAdapter,
DormantViolation,
snapshot_sha256,
)
from aios_single_owner_governance.policy import DormantOperationGuard, OperationDenied
from .helpers import legacy_protection, single_owner_protection
class LiveStore:
fixture = False
def read(self, branch):
return legacy_protection()
def compare_and_swap(self, branch, expected_sha256, target):
raise AssertionError("live compare_and_swap must never be invoked")
class DormantInvariantTests(unittest.TestCase):
def test_live_protection_apply_and_rollback_are_denied(self):
adapter = BranchProtectionCASMigrationAdapter()
old, new = legacy_protection(), single_owner_protection()
plan = adapter.plan(old, new, snapshot_sha256(old))
with self.assertRaises(DormantViolation):
adapter.apply_fixture(LiveStore(), plan)
with self.assertRaises(DormantViolation):
adapter.rollback_fixture(LiveStore(), plan)
def test_unsafe_policy_cannot_be_planned(self):
adapter = BranchProtectionCASMigrationAdapter()
old = legacy_protection()
for unsafe in (
single_owner_protection().__class__(**{**single_owner_protection().as_dict(), "approval_whitelist": ("owner",), "merge_whitelist": ("owner",), "status_contexts": tuple(single_owner_protection().status_contexts), "direct_push": True}),
single_owner_protection().__class__(**{**single_owner_protection().as_dict(), "approval_whitelist": ("owner",), "merge_whitelist": ("owner",), "status_contexts": tuple(single_owner_protection().status_contexts), "force_push": True}),
single_owner_protection().__class__(**{**single_owner_protection().as_dict(), "approval_whitelist": ("owner",), "merge_whitelist": ("owner",), "status_contexts": tuple(single_owner_protection().status_contexts), "branch_deletion": True}),
):
with self.assertRaises(DormantViolation):
adapter.plan(old, unsafe, snapshot_sha256(old))
def test_disposable_fixture_is_destroyed(self):
fixture = DisposableGiteaFixture(legacy_protection())
root = fixture.root
with fixture:
self.assertTrue(root.exists())
self.assertTrue(fixture.destroyed)
self.assertFalse(root.exists())
def test_permanent_and_pre_activation_writes_are_denied(self):
guard = DormantOperationGuard()
for operation in (
"protected_branch.direct_push",
"governance.direct_push",
"tag.direct_write",
"git.force_push",
"production.without_owner",
"branch_protection.live_update",
"single_owner_policy.activate",
):
with self.subTest(operation=operation), self.assertRaises(OperationDenied):
guard.authorize(operation)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,113 @@
from __future__ import annotations
import contextlib
import io
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
from aios_single_owner_governance.evidence import (
ActivationEvidenceClosureBuilder,
ApprovalMirrorStatusVerifier,
)
from aios_single_owner_governance.service import main, runtime_state
from aios_single_owner_governance.status import (
DormantStatusProducer,
MachineStatusProducer,
)
from .helpers import HEAD
class EvidenceStatusServiceTests(unittest.TestCase):
def test_complete_evidence_set_builds_canonical_closure(self):
evidence = {
"activation_prerequisites_commit_oid": "a" * 40,
"activation_prerequisites_tree_oid": "b" * 40,
"artifact_sha256": "c" * 64,
"rollback_artifact_sha256": "d" * 64,
"validator_sha256": "e" * 64,
"state_machine_sha256": "f" * 64,
"branch_protection_adapter_sha256": "0" * 64,
"rollback_configuration_sha256": "1" * 64,
"test_evidence_sha256": "2" * 64,
"deployment_manifest_sha256": "3" * 64,
"dormant_unit_manifest_sha256": "4" * 64,
"dormant_deployment_evidence_sha256": "5" * 64,
}
closure = ActivationEvidenceClosureBuilder().build(evidence)
self.assertEqual(len(closure.closure_sha256), 64)
with self.assertRaises(ValueError):
ActivationEvidenceClosureBuilder().build({})
def test_mirror_verifier_requires_all_four_refs_and_fsck(self):
verifier = ApprovalMirrorStatusVerifier()
self.assertTrue(verifier.verify(
HEAD, gitea_oid=HEAD, quarantine_oid=HEAD,
authoritative_oid=HEAD, controller_visible_oid=HEAD,
fsck_connectivity="PASS",
))
self.assertFalse(verifier.verify(
HEAD, gitea_oid=HEAD, quarantine_oid=HEAD,
authoritative_oid="b" * 40, controller_visible_oid=HEAD,
fsck_connectivity="PASS",
))
def test_machine_status_is_fixture_only_and_dormant(self):
with self.assertRaises(DormantStatusProducer):
MachineStatusProducer().produce("aios/machine-gates", "success", HEAD)
with self.assertRaises(DormantStatusProducer):
MachineStatusProducer(enabled=True, fixture=True).produce(
"aios/machine-gates", "success", HEAD
)
record = MachineStatusProducer(fixture=True).produce(
"aios/evidence-closure", "success", HEAD
)
self.assertEqual(record.head_oid, HEAD)
def test_service_self_check_is_dormant_and_activate_is_rejected(self):
output = io.StringIO()
with contextlib.redirect_stdout(output):
self.assertEqual(main(["--component", "owner-review-adapter", "--self-check"]), 0)
state = json.loads(output.getvalue())
self.assertFalse(state["active"])
self.assertFalse(state["production_mutation"])
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit):
main(["--component", "owner-review-adapter", "--activate"])
def test_write_flags_cannot_be_enabled(self):
for name in (
"AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED",
"AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE",
"AIOS_GITOPS_WRITES_ENABLED",
):
with self.subTest(name=name), patch.dict(os.environ, {name: "true"}, clear=False):
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit):
main(["--component", "machine-status-producer", "--self-check"])
def test_service_entrypoint_restart_recovery_across_processes(self):
with tempfile.TemporaryDirectory(prefix="aios-governance-fixture-") as root:
command = [
sys.executable, "-m", "aios_single_owner_governance.service",
"--component", "owner-review-adapter", "--self-check",
"--checkpoint-dir", root,
]
first = subprocess.run(command, text=True, capture_output=True, check=True)
second = subprocess.run(command, text=True, capture_output=True, check=True)
first_state = json.loads(first.stdout)
second_state = json.loads(second.stdout)
self.assertFalse(first_state["restart_recovery"])
self.assertTrue(second_state["restart_recovery"])
self.assertEqual(second_state["prior_invocations"], 1)
self.assertFalse(second_state["production_mutation"])
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,771 @@
from __future__ import annotations
import json
from pathlib import Path
import sqlite3
import subprocess
import hashlib
import os
from datetime import datetime, timedelta, timezone
import pytest
from jsonschema import Draft202012Validator, ValidationError
from aios_single_owner_governance.activation import (
ActivationEngine,
InjectedCrash,
_verify_approved_entities,
validate_bindings,
)
import aios_single_owner_governance.validator as validator_module
import aios_single_owner_governance.state_machine as state_machine_module
import aios_single_owner_governance.activation as activation_module
from aios_single_owner_governance.gitea_protected_branch import (
CASConflict,
FileProtectedBranchAdapter,
SQLiteProtectedBranchAdapter,
UnsafePolicy,
calculate_snapshot_sha256,
validate_target,
)
from aios_single_owner_governance.journal import sha256_json
MACHINE_CONTEXTS = {
"aios/validator",
"aios/state-machine",
"aios/branch-protection",
"aios/secret-scan",
"aios/evidence-closure",
}
def write_json(path: Path, value: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, sort_keys=True) + "\n", encoding="utf-8")
def legacy_row() -> dict:
return {
"id": 7,
"repo_id": 1,
"branch_name": "governance/approvals",
"can_push": 0,
"enable_whitelist": 0,
"whitelist_user_i_ds": "null",
"whitelist_team_i_ds": "null",
"enable_merge_whitelist": 1,
"whitelist_deploy_keys": 0,
"merge_whitelist_user_i_ds": "[42,43]",
"merge_whitelist_team_i_ds": "null",
"enable_status_check": 0,
"status_check_contexts": "[]",
"enable_approvals_whitelist": 1,
"approvals_whitelist_user_i_ds": "[42,43]",
"approvals_whitelist_team_i_ds": "null",
"required_approvals": 2,
"block_on_rejected_reviews": 1,
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"dismiss_stale_approvals": 1,
"ignore_stale_approvals": 0,
"require_signed_commits": 0,
"protected_file_patterns": "",
"unprotected_file_patterns": "",
"created_unix": 1,
"updated_unix": 10,
}
def owner_row() -> dict:
row = legacy_row()
row.update(
{
"approvals_whitelist_user_i_ds": "[2]",
"required_approvals": 1,
"enable_status_check": 1,
"status_check_contexts": json.dumps(sorted(MACHINE_CONTEXTS), separators=(",", ":")),
"updated_unix": 11,
}
)
return row
def git_output(git_dir: Path, *args: str, stdin: str | None = None) -> str:
return subprocess.run(
["git", f"--git-dir={git_dir}", *args], input=stdin, text=True,
capture_output=True, check=True,
env={
**__import__("os").environ,
"GIT_AUTHOR_NAME": "Test", "GIT_AUTHOR_EMAIL": "test@example.invalid",
"GIT_COMMITTER_NAME": "Test", "GIT_COMMITTER_EMAIL": "test@example.invalid",
},
).stdout.strip()
def commit_files(
git_dir: Path, files: dict[str, str], message: str, *, parent: str | None = None
) -> tuple[str, str]:
root: dict[str, object] = {}
for path, raw in files.items():
node = root
parts = path.split("/")
for directory in parts[:-1]:
node = node.setdefault(directory, {}) # type: ignore[assignment]
node[parts[-1]] = git_output(git_dir, "hash-object", "-w", "--stdin", stdin=raw)
def build_tree(node: dict[str, object]) -> str:
lines = []
for name in sorted(node):
value = node[name]
if isinstance(value, dict):
lines.append(f"040000 tree {build_tree(value)}\t{name}")
else:
lines.append(f"100644 blob {value}\t{name}")
return git_output(git_dir, "mktree", stdin="\n".join(lines) + "\n")
tree = build_tree(root)
args = ["commit-tree", tree]
if parent is not None:
args.extend(("-p", parent))
return git_output(git_dir, *args, stdin=message + "\n"), tree
def evidence_fixture(tmp_path: Path, execution_binding: dict) -> dict:
git_dir = tmp_path / "evidence.git"
subprocess.run(["git", "init", "--bare", str(git_dir)], capture_output=True, check=True)
package_root = Path(activation_module.__file__).resolve().parent
source_files: dict[str, str] = {}
runtime_sources = []
for name in (
"activation.py", "gitea_protected_branch.py", "journal.py",
"state_machine.py", "validator.py",
):
source = package_root / name
raw = source.read_text(encoding="utf-8")
git_path = (
"services/aios-single-owner-governance/"
f"aios_single_owner_governance/{name}"
)
source_files[git_path] = raw
runtime_sources.append({
"git_path": git_path,
"runtime_path": str(source.resolve()),
"sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
})
implementation_oid, implementation_tree = commit_files(
git_dir, source_files, "implementation"
)
closure_path = "evidence/live-activation-closure.json"
closure_raw = '{"result":"PASS"}\n'
closure_oid, _ = commit_files(
git_dir, {**source_files, closure_path: closure_raw},
"closure", parent=implementation_oid
)
closure_sha = hashlib.sha256(closure_raw.encode()).hexdigest()
entities = {}
for name, raw in {
"artifact": b"approved-artifact\n",
"rollback_artifact": b"approved-rollback\n",
"test_evidence": b"approved-tests\n",
}.items():
path = tmp_path / "artifacts" / f"{name}.bin"
path.parent.mkdir(exist_ok=True)
path.write_bytes(raw)
entities[f"{name}_path"] = str(path.resolve())
entities[f"{name}_sha256"] = hashlib.sha256(raw).hexdigest()
head_oid = git_output(git_dir, "hash-object", "-w", "--stdin", stdin="head\n")
expiry = datetime.now(timezone.utc) + timedelta(hours=1)
producer_id = 99
producer_name = "trusted-machine-producer"
execution_binding = {
**execution_binding,
"evidence_closure_oid": closure_oid,
"evidence_closure_path": closure_path,
"evidence_closure_sha256": closure_sha,
"implementation_commit_oid": implementation_oid,
"implementation_tree_oid": implementation_tree,
"runtime_sources": runtime_sources,
"machine_status_producer_id": producer_id,
"machine_status_producer_name": producer_name,
**entities,
}
manifest = {
"approval_type": "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION",
"expires_at": expiry.isoformat().replace("+00:00", "Z"),
"execution_binding": execution_binding,
"execution_binding_sha256": sha256_json(execution_binding),
**{key: execution_binding[key] for key in (
"implementation_commit_oid", "implementation_tree_oid", "runtime_sources",
"artifact_path", "artifact_sha256", "rollback_artifact_path",
"rollback_artifact_sha256", "test_evidence_path", "test_evidence_sha256",
"evidence_closure_oid", "evidence_closure_path", "evidence_closure_sha256",
"machine_status_producer_id", "machine_status_producer_name",
)},
}
manifest_raw = json.dumps(manifest, sort_keys=True, separators=(",", ":")) + "\n"
manifest_blob = git_output(git_dir, "hash-object", "-w", "--stdin", stdin=manifest_raw)
leaf = git_output(git_dir, "mktree", stdin=f"100644 blob {manifest_blob}\tactivation.json\n")
approvals = git_output(git_dir, "mktree", stdin=f"040000 tree {leaf}\tsingle-owner\n")
tree = git_output(git_dir, "mktree", stdin=f"040000 tree {approvals}\tapprovals\n")
merge_oid = git_output(git_dir, "commit-tree", tree, stdin="approved\n")
git_output(git_dir, "update-ref", "refs/heads/governance/approvals", merge_oid)
db = tmp_path / "approval.db"
with sqlite3.connect(db) as connection:
connection.executescript(
"CREATE TABLE issue(id INTEGER,repo_id INTEGER,is_closed INTEGER);"
"CREATE TABLE pull_request(issue_id INTEGER,\"index\" INTEGER,base_branch TEXT,has_merged INTEGER,merged_commit_id TEXT,merged_unix INTEGER);"
"CREATE TABLE user(id INTEGER,lower_name TEXT);"
"CREATE TABLE review(id INTEGER,type INTEGER,reviewer_id INTEGER,issue_id INTEGER,commit_id TEXT,stale INTEGER,dismissed INTEGER,created_unix INTEGER);"
"CREATE TABLE commit_status(id INTEGER,repo_id INTEGER,state TEXT,sha TEXT,context TEXT,creator_id INTEGER);"
)
connection.execute("INSERT INTO issue VALUES(10,1,1)")
merged_unix = int(datetime.now(timezone.utc).timestamp())
connection.execute("INSERT INTO pull_request VALUES(10,31,'governance/approvals',1,?,?)", (merge_oid, merged_unix))
connection.execute("INSERT INTO user VALUES(2,'owner')")
connection.execute("INSERT INTO user VALUES(?,?)", (producer_id, producer_name))
connection.execute(
"INSERT INTO review VALUES(52,1,2,10,?,0,0,?)",
(head_oid, int(datetime.now(timezone.utc).timestamp())),
)
machine_checks = {}
for index, context in enumerate(sorted(MACHINE_CONTEXTS), 101):
connection.execute(
"INSERT INTO commit_status VALUES(?,1,'success',?,?,?)",
(index, head_oid, context, producer_id),
)
machine_checks[context] = {
"state": "success", "head_oid": head_oid, "status_id": index,
"creator_id": producer_id, "creator_login": producer_name,
}
return {
"git_dir": git_dir, "merge_oid": merge_oid, "closure_oid": closure_oid,
"head_oid": head_oid, "db": db, "expiry": expiry.isoformat().replace("+00:00", "Z"),
"manifest_path": "approvals/single-owner/activation.json",
"manifest_blob": manifest_blob,
"manifest_sha256": hashlib.sha256(manifest_raw.encode()).hexdigest(),
"execution_binding": execution_binding,
"machine_checks": machine_checks,
**execution_binding,
}
def bindings(tmp_path: Path, adapter: FileProtectedBranchAdapter, **overrides) -> dict:
validator_path = Path(validator_module.__file__).resolve()
state_machine_path = Path(state_machine_module.__file__).resolve()
validator_sha = hashlib.sha256(validator_path.read_bytes()).hexdigest()
state_machine_sha = hashlib.sha256(state_machine_path.read_bytes()).hexdigest()
execution_binding = {
"task_id": "AIOS-LIVE-ACTIVATION-TEST",
"operation_id": "op-live-001",
"expected_validator_sha256": validator_sha,
"expected_state_machine_sha256": state_machine_sha,
"expected_branch_protection_sha256": adapter.calculate_snapshot_sha256(legacy_row()),
"expected_gitea_schema_sha256": "0" * 64,
"target_branch_protection_sha256": calculate_snapshot_sha256(owner_row()),
"backend": "file",
"repo_id": 1,
"branch": "governance/approvals",
"requester": "agent",
"executor": "activation-executor",
"forbidden_operations": {
"main": False, "governance_direct": False, "integration": False,
"tags": False, "force_push": False,
},
}
evidence = evidence_fixture(tmp_path, execution_binding)
value = {
"task_id": "AIOS-LIVE-ACTIVATION-TEST",
"operation_id": "op-live-001",
"expected_governance_oid": evidence["merge_oid"],
"observed_governance_oid": evidence["merge_oid"],
"expected_validator_sha256": validator_sha,
"observed_validator_sha256": validator_sha,
"expected_state_machine_sha256": state_machine_sha,
"observed_state_machine_sha256": state_machine_sha,
"validator_path": str(validator_path),
"state_machine_path": str(state_machine_path),
"governance_git_dir": str(evidence["git_dir"].resolve()),
"governance_ref": "refs/heads/governance/approvals",
"evidence_git_dir": str(evidence["git_dir"].resolve()),
"approval_db_path": str(evidence["db"].resolve()),
"expected_branch_protection_sha256": adapter.calculate_snapshot_sha256(legacy_row()),
"activation_approval_merge_oid": evidence["merge_oid"],
"approval_request_head_oid": evidence["head_oid"],
"activation_approval_pr_number": 31,
"activation_manifest_path": evidence["manifest_path"],
"activation_manifest_blob_oid": evidence["manifest_blob"],
"activation_manifest_sha256": evidence["manifest_sha256"],
"expires_at": evidence["expiry"],
"evidence_closure_oid": evidence["closure_oid"],
"observed_evidence_closure_oid": evidence["closure_oid"],
**{key: evidence[key] for key in (
"implementation_commit_oid", "implementation_tree_oid", "runtime_sources",
"artifact_path", "artifact_sha256", "rollback_artifact_path",
"rollback_artifact_sha256", "test_evidence_path", "test_evidence_sha256",
"evidence_closure_path", "evidence_closure_sha256",
"machine_status_producer_id", "machine_status_producer_name",
)},
"owner_review_oid": 52,
"observed_owner_review_oid": 52,
"requester": "agent",
"executor": "activation-executor",
"wip": False,
"machine_checks": evidence["machine_checks"],
"forbidden_operations": {
"main": False,
"governance_direct": False,
"integration": False,
"tags": False,
"force_push": False,
},
"target_branch_protection": owner_row(),
"backend": "file",
"backend_path": str(adapter.path.resolve()),
"repo_id": 1,
"branch": "governance/approvals",
"expected_gitea_schema_sha256": "0" * 64,
}
value.update(overrides)
return value
def harness(tmp_path: Path):
backend = tmp_path / "protected.json"
write_json(backend, legacy_row())
adapter = FileProtectedBranchAdapter(backend)
engine = ActivationEngine(adapter, tmp_path / "state", tmp_path / "run" / "activation.lock")
request = bindings(tmp_path, adapter)
return backend, adapter, engine, request
def test_live_plan_success(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
result = engine.plan(request)
assert result["status"] == "PLAN_READY"
assert len(result["plan_id"]) == 64
def test_activation_bindings_schema_matches_runtime_contract(tmp_path: Path) -> None:
_, _, _, request = harness(tmp_path)
schema_path = Path(activation_module.__file__).resolve().parents[1] / "activation-bindings.schema.json"
schema = json.loads(schema_path.read_text(encoding="utf-8"))
Draft202012Validator.check_schema(schema)
validator = Draft202012Validator(schema)
validator.validate(request)
invalid = json.loads(json.dumps(request))
invalid["machine_checks"][sorted(MACHINE_CONTEXTS)[0]]["status_id"] = 0
with pytest.raises(ValidationError):
validator.validate(invalid)
def test_runtime_source_must_be_actual_loaded_module_not_decoy(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
source = Path(request["runtime_sources"][0]["runtime_path"])
decoy = tmp_path / "decoy.py"
decoy.write_bytes(source.read_bytes())
request["runtime_sources"][0]["runtime_path"] = str(decoy.resolve())
with pytest.raises(RuntimeError, match="RUNTIME_SOURCE_PATH_MISMATCH"):
_verify_approved_entities(request, Path(request["evidence_git_dir"]))
@pytest.mark.parametrize("path_field", ["artifact_path", "rollback_artifact_path", "test_evidence_path"])
def test_approved_entity_digest_is_enforced(tmp_path: Path, path_field: str) -> None:
_, _, engine, request = harness(tmp_path)
Path(request[path_field]).write_bytes(b"tampered\n")
with pytest.raises(RuntimeError, match="APPROVED_ENTITY_DIGEST_MISMATCH"):
engine.plan(request)
def test_evidence_closure_must_be_direct_child_commit(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
git_dir = Path(request["evidence_git_dir"])
unrelated, _ = commit_files(
git_dir,
{request["evidence_closure_path"]: '{"result":"PASS"}\n'},
"unrelated",
)
request["evidence_closure_oid"] = unrelated
request["observed_evidence_closure_oid"] = unrelated
with pytest.raises(RuntimeError, match="APPROVED_EXECUTION_BINDING_MISMATCH"):
engine.plan(request)
def test_pr_merge_after_expiry_is_denied(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
with sqlite3.connect(request["approval_db_path"]) as connection:
connection.execute(
"UPDATE pull_request SET merged_unix=?",
(int(_parse_test_utc(request["expires_at"]).timestamp()) + 1,),
)
with pytest.raises(RuntimeError, match="ACTIVATION_APPROVAL_MERGED_AFTER_EXPIRY"):
engine.plan(request)
def _parse_test_utc(value: str) -> datetime:
return datetime.fromisoformat(value.replace("Z", "+00:00"))
def test_machine_status_must_match_bound_producer_and_observed_row(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
context = sorted(MACHINE_CONTEXTS)[0]
request["machine_checks"][context]["status_id"] += 1000
with pytest.raises(RuntimeError, match="MACHINE_GATE_BINDING_MISMATCH"):
engine.plan(request)
def test_latest_machine_status_from_unapproved_producer_is_denied(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
context = sorted(MACHINE_CONTEXTS)[0]
with sqlite3.connect(request["approval_db_path"]) as connection:
connection.execute("INSERT INTO user VALUES(100,'untrusted-producer')")
connection.execute(
"INSERT INTO commit_status VALUES(999,1,'success',?,?,100)",
(request["approval_request_head_oid"], context),
)
with pytest.raises(RuntimeError, match="MACHINE_GATE_PRODUCER_MISMATCH"):
engine.plan(request)
def test_live_activate_verify_status_success(tmp_path: Path) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
activated = engine.activate(request, planned["plan_id"])
assert activated["status"] == "ACTIVATED"
assert adapter.read_current() == owner_row()
assert engine.verify(request, request["operation_id"])["status"] == "VERIFIED"
assert engine.status(request["operation_id"])["transaction"]["phase"] == "COMMITTED"
assert (engine.journal.root / "current-snapshot.json").exists()
assert (engine.journal.root / "rollback-snapshot.json").exists()
def test_live_rollback_success(tmp_path: Path) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
assert engine.rollback(request, request["operation_id"], planned["plan_id"])["status"] == "ROLLED_BACK"
assert adapter.read_current() == legacy_row()
assert engine.rollback(request, request["operation_id"], planned["plan_id"])["status"] == "ALREADY_ROLLED_BACK"
def test_terminal_rollback_cannot_be_reactivated(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
engine.rollback(request, request["operation_id"], planned["plan_id"])
with pytest.raises(RuntimeError, match="OPERATION_TERMINAL_ROLLED_BACK"):
engine.activate(request, planned["plan_id"])
def test_verify_and_rollback_remain_available_after_approval_expiry(
tmp_path: Path, monkeypatch
) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
future = datetime.now(timezone.utc) + timedelta(hours=2)
class FutureDateTime(datetime):
@classmethod
def now(cls, tz=None):
return future if tz is not None else future.replace(tzinfo=None)
monkeypatch.setattr(activation_module, "datetime", FutureDateTime)
assert engine.verify(request, request["operation_id"])["status"] == "VERIFIED"
assert engine.rollback(request, request["operation_id"], planned["plan_id"])["status"] == "ROLLED_BACK"
assert adapter.read_current() == legacy_row()
def test_prepared_crash_can_recover_after_approval_expiry(tmp_path: Path, monkeypatch) -> None:
_, adapter, engine, request = harness(tmp_path)
request["crash_at"] = "before_apply"
planned = engine.plan(request)
with pytest.raises(InjectedCrash):
engine.activate(request, planned["plan_id"])
request.pop("crash_at")
future = datetime.now(timezone.utc) + timedelta(hours=2)
class FutureDateTime(datetime):
@classmethod
def now(cls, tz=None):
return future if tz is not None else future.replace(tzinfo=None)
monkeypatch.setattr(activation_module, "datetime", FutureDateTime)
assert engine.activate(request, planned["plan_id"])["status"] == "ACTIVATED"
assert adapter.read_current() == owner_row()
def test_fresh_activation_rechecks_expiry_at_prepared_boundary(
tmp_path: Path, monkeypatch
) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
expiry = _parse_test_utc(request["expires_at"])
moments = iter((
expiry - timedelta(seconds=2),
expiry - timedelta(seconds=1),
expiry + timedelta(seconds=1),
))
monkeypatch.setattr(activation_module, "_utc_now", lambda: next(moments))
with pytest.raises(RuntimeError, match="APPROVAL_EXPIRED"):
engine.activate(request, planned["plan_id"])
assert adapter.read_current() == legacy_row()
assert engine.journal.load(request["operation_id"]) is None
def test_fresh_activation_rejects_snapshot_drift_at_prepared_boundary(
tmp_path: Path, monkeypatch
) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
original_read = adapter.read_current
reads = 0
def drifting_read() -> dict:
nonlocal reads
reads += 1
if reads == 2:
drifted = original_read()
drifted["can_push"] = 1
drifted["updated_unix"] = 777
return drifted
return original_read()
monkeypatch.setattr(adapter, "read_current", drifting_read)
with pytest.raises(CASConflict, match="PREPARED_BOUNDARY_SNAPSHOT_CHANGED"):
engine.activate(request, planned["plan_id"])
assert original_read() == legacy_row()
assert engine.journal.load(request["operation_id"]) is None
def test_rollback_uses_durable_authorization_after_live_status_rerun(tmp_path: Path) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
context = sorted(MACHINE_CONTEXTS)[0]
with sqlite3.connect(request["approval_db_path"]) as connection:
connection.execute(
"INSERT INTO commit_status VALUES(999,1,'success',?,?,?)",
(
request["approval_request_head_oid"], context,
request["machine_status_producer_id"],
),
)
result = engine.rollback(request, request["operation_id"], planned["plan_id"])
assert result["status"] == "ROLLED_BACK"
assert adapter.read_current() == legacy_row()
def test_missing_approval_denied(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
request.pop("owner_review_oid")
with pytest.raises(RuntimeError, match="MISSING_BINDINGS"):
engine.plan(request)
@pytest.mark.parametrize(
("field", "value", "message"),
[
("observed_owner_review_oid", 99, "WRONG_APPROVAL_OID_DENIED"),
("observed_governance_oid", "f" * 40, "WRONG_GOVERNANCE_OID_DENIED"),
("observed_evidence_closure_oid", "f" * 40, "WRONG_EVIDENCE_CLOSURE_DENIED"),
],
)
def test_wrong_binding_denied(tmp_path: Path, field: str, value: object, message: str) -> None:
_, _, engine, request = harness(tmp_path)
request[field] = value
with pytest.raises(RuntimeError, match=message):
engine.plan(request)
def test_cas_conflict_denied(tmp_path: Path) -> None:
backend, _, engine, request = harness(tmp_path)
planned = engine.plan(request)
changed = legacy_row()
changed["updated_unix"] = 12
write_json(backend, changed)
with pytest.raises(CASConflict):
engine.activate(request, planned["plan_id"])
@pytest.mark.parametrize("crash_at", ["before_apply", "during_apply", "after_apply_before_receipt", "after_commit_wal"])
def test_process_crash_restart_recovery(tmp_path: Path, crash_at: str) -> None:
_, adapter, engine, request = harness(tmp_path)
request["crash_at"] = crash_at
planned = engine.plan(request)
with pytest.raises(InjectedCrash):
engine.activate(request, planned["plan_id"])
request.pop("crash_at")
result = engine.activate(request, planned["plan_id"])
expected_status = "ALREADY_COMMITTED" if crash_at == "after_commit_wal" else "ACTIVATED"
assert result["status"] == expected_status
assert adapter.read_current() == owner_row()
assert engine.journal.receipt_path(request["operation_id"]).exists()
assert engine.journal.audit.has_event(request["operation_id"], "SINGLE_OWNER_ACTIVATION_COMMITTED")
def test_post_activation_verify_failure_rolls_back(tmp_path: Path, monkeypatch) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
monkeypatch.setattr(adapter, "verify_applied", lambda _: False)
with pytest.raises(RuntimeError, match="POST_ACTIVATION_VERIFY_FAILED"):
engine.activate(request, planned["plan_id"])
assert adapter.read_current() == legacy_row()
assert engine.journal.load(request["operation_id"])["phase"] == "ROLLED_BACK"
def test_operation_id_idempotency(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
assert engine.activate(request, planned["plan_id"])["status"] == "ALREADY_COMMITTED"
@pytest.mark.parametrize("crash_at", ["after_rollback_cas", "after_rollback_wal"])
def test_rollback_crash_restart_repairs_closure(tmp_path: Path, crash_at: str) -> None:
_, adapter, engine, request = harness(tmp_path)
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
request["crash_at"] = crash_at
with pytest.raises(InjectedCrash):
engine.rollback(request, request["operation_id"], planned["plan_id"])
request.pop("crash_at")
assert engine.rollback(request, request["operation_id"], planned["plan_id"])["status"] == "ALREADY_ROLLED_BACK"
assert adapter.read_current() == legacy_row()
assert engine.journal.receipt_path(request["operation_id"]).exists()
assert engine.journal.audit.has_event(request["operation_id"], "ACTIVATION_ROLLED_BACK")
def test_operation_id_payload_mismatch_denied(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
changed = dict(request)
approval_alias = tmp_path / "approval-alias.db"
os.link(request["approval_db_path"], approval_alias)
changed["approval_db_path"] = str(approval_alias)
with pytest.raises(RuntimeError, match="PAYLOAD_MISMATCH"):
engine.activate(changed, planned["plan_id"])
@pytest.mark.parametrize(
("field", "value"),
[("operation_id", "op-live-REPLAY"), ("task_id", "REPLAY-TASK")],
)
def test_approved_execution_binding_replay_denied(tmp_path: Path, field: str, value: str) -> None:
_, _, engine, request = harness(tmp_path)
request[field] = value
with pytest.raises(RuntimeError, match="APPROVED_EXECUTION_BINDING_MISMATCH"):
engine.plan(request)
def test_expiry_must_come_from_approved_manifest(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
request["expires_at"] = (datetime.now(timezone.utc) + timedelta(hours=12)).isoformat().replace("+00:00", "Z")
with pytest.raises(RuntimeError, match="APPROVAL_EXPIRY_BINDING_MISMATCH"):
engine.plan(request)
def test_target_snapshot_must_match_approved_manifest(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
request["target_branch_protection"] = {**owner_row(), "updated_unix": 99}
with pytest.raises(RuntimeError, match="APPROVED_EXECUTION_BINDING_MISMATCH"):
engine.plan(request)
def test_approval_database_must_be_backend_database(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
other_db = tmp_path / "other.db"
other_db.write_bytes(Path(request["approval_db_path"]).read_bytes())
request.update(backend="sqlite", backend_path=str(other_db.resolve()))
with pytest.raises(RuntimeError, match="APPROVAL_DB_BACKEND_DB_IDENTITY_MISMATCH"):
engine.plan(request)
def test_audit_chain_continuity_and_credential_redaction(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
request["api_token"] = "must-not-appear"
planned = engine.plan(request)
engine.activate(request, planned["plan_id"])
assert len(engine.journal.audit.verify()) == 64
raw = (engine.journal.root / "audit.jsonl").read_text(encoding="utf-8")
assert "must-not-appear" not in raw
assert "[REDACTED]" not in raw # only hashes/events are persisted to audit
@pytest.mark.parametrize(
"mutation",
[
lambda request: request.update(requester="owner"),
lambda request: request.update(executor="owner"),
lambda request: request.update(wip=True),
lambda request: request.update(machine_checks={"anything": {"state": "success", "head_oid": request["approval_request_head_oid"]}}),
lambda request: request.update(forbidden_operations={"main": True}),
lambda request: request.update(forbidden_operations={"tags": True}),
lambda request: request.update(forbidden_operations={"force_push": True}),
],
)
def test_negative_governance_acceptance(tmp_path: Path, mutation) -> None:
_, _, engine, request = harness(tmp_path)
mutation(request)
with pytest.raises(RuntimeError):
engine.plan(request)
@pytest.mark.parametrize(
"mutation",
[
lambda target: target.update(enable_approvals_whitelist=0),
lambda target: target.update(enable_merge_whitelist=0),
lambda target: target.update(status_check_contexts="[]"),
],
)
def test_unsafe_policy_enable_fields_denied(tmp_path: Path, mutation) -> None:
_, _, _, request = harness(tmp_path)
mutation(request["target_branch_protection"])
with pytest.raises(UnsafePolicy):
validate_target(legacy_row(), request["target_branch_protection"])
@pytest.mark.parametrize("invalid_owner", ["codex", "gpt"])
def test_owner_review_must_come_from_gitea_database(tmp_path: Path, invalid_owner: str) -> None:
_, _, engine, request = harness(tmp_path)
with sqlite3.connect(request["approval_db_path"]) as connection:
connection.execute("UPDATE user SET lower_name=? WHERE id=2", (invalid_owner,))
with pytest.raises(RuntimeError, match="OWNER_APPROVAL_INVALID"):
engine.plan(request)
def test_stale_owner_review_denied_from_gitea_database(tmp_path: Path) -> None:
_, _, engine, request = harness(tmp_path)
with sqlite3.connect(request["approval_db_path"]) as connection:
connection.execute("UPDATE review SET stale=1 WHERE id=52")
with pytest.raises(RuntimeError, match="OWNER_APPROVAL_INVALID"):
engine.plan(request)
def create_sqlite_fixture(path: Path) -> None:
row = legacy_row()
columns = []
for key, value in row.items():
kind = "INTEGER" if isinstance(value, int) else "TEXT"
columns.append(f"{key} {kind}")
with sqlite3.connect(path) as connection:
connection.execute(f"CREATE TABLE protected_branch ({','.join(columns)})")
connection.execute(
f"INSERT INTO protected_branch ({','.join(row)}) VALUES ({','.join('?' for _ in row)})",
list(row.values()),
)
def test_sqlite_full_row_cas_and_rollback(tmp_path: Path) -> None:
db = tmp_path / "gitea.db"
create_sqlite_fixture(db)
probe = SQLiteProtectedBranchAdapter(db, expected_schema_sha256="0" * 64)
adapter = SQLiteProtectedBranchAdapter(db, expected_schema_sha256=probe.schema_sha256())
previous = adapter.read_current()
applied = adapter.apply_atomic(calculate_snapshot_sha256(previous), owner_row())
assert applied == owner_row()
restored = adapter.rollback_atomic(calculate_snapshot_sha256(applied), previous)
assert restored == previous
@@ -0,0 +1,126 @@
from __future__ import annotations
from pathlib import Path
import tempfile
import unittest
from aios_single_owner_governance.fixture import DisposableGiteaFixture
from aios_single_owner_governance.lock import MaintenanceLock
from aios_single_owner_governance.models import GovernanceState
from aios_single_owner_governance.protection import (
BranchProtectionCASMigrationAdapter,
CASConflict,
MemoryProtectionStore,
snapshot_sha256,
)
from aios_single_owner_governance.rollback import (
ProtectionCompensationSaga,
RollbackConfigurationManager,
)
from aios_single_owner_governance.state_machine import GovernanceStateMachine, InvalidTransition
from .helpers import legacy_protection, single_owner_protection
from .helpers import context
from aios_single_owner_governance.validator import SingleOwnerApprovalValidator, ValidationDenied
class StateCasRollbackTests(unittest.TestCase):
def test_ordinary_owner_approved_merge_simulation(self):
result = SingleOwnerApprovalValidator().validate(context())
self.assertEqual(result.owner_review.reviewer, "owner")
machine = GovernanceStateMachine()
for target in (
GovernanceState.OWNER_APPROVAL_VERIFIED,
GovernanceState.MACHINE_GATES_VERIFIED,
GovernanceState.AUTO_MERGE_READY,
GovernanceState.OWNER_APPROVED_EXECUTION_READY,
):
machine.transition(target)
self.assertEqual(machine.state, GovernanceState.OWNER_APPROVED_EXECUTION_READY)
def test_validator_failure_is_stateless_and_recoverable(self):
validator = SingleOwnerApprovalValidator()
with self.assertRaises(ValidationDenied):
validator.validate(context(wip=True))
self.assertEqual(validator.validate(context()).owner_review.reviewer, "owner")
def test_state_machine_happy_path_and_restart_recovery(self):
machine = GovernanceStateMachine()
for target in (
GovernanceState.OWNER_APPROVAL_VERIFIED,
GovernanceState.MACHINE_GATES_VERIFIED,
GovernanceState.AUTO_MERGE_READY,
GovernanceState.OWNER_APPROVED_EXECUTION_READY,
):
machine.transition(target)
restarted = GovernanceStateMachine(machine.state)
self.assertEqual(restarted.state, GovernanceState.OWNER_APPROVED_EXECUTION_READY)
def test_state_machine_rollback_to_checkpoint(self):
machine = GovernanceStateMachine()
machine.transition(GovernanceState.OWNER_APPROVAL_VERIFIED)
checkpoint = machine.state
machine.transition(GovernanceState.MACHINE_GATES_VERIFIED)
machine.restore(checkpoint)
self.assertEqual(machine.state, GovernanceState.OWNER_APPROVAL_VERIFIED)
def test_service_restart_from_each_checkpoint(self):
with tempfile.TemporaryDirectory(prefix="aios-governance-fixture-") as root:
checkpoint = Path(root) / "state.json"
for state in GovernanceState:
with self.subTest(state=state):
GovernanceStateMachine(state).save_fixture(checkpoint)
restarted = GovernanceStateMachine.load_fixture(checkpoint)
self.assertEqual(restarted.state, state)
def test_invalid_transition_denied(self):
with self.assertRaises(InvalidTransition):
GovernanceStateMachine().transition(GovernanceState.AUTO_MERGE_READY)
def test_branch_protection_cas_success_and_rollback(self):
adapter = BranchProtectionCASMigrationAdapter()
old, new = legacy_protection(), single_owner_protection()
plan = adapter.plan(old, new, snapshot_sha256(old))
store = MemoryProtectionStore(old)
self.assertEqual(adapter.apply_fixture(store, plan), new)
self.assertEqual(adapter.rollback_fixture(store, plan), old)
def test_branch_protection_cas_conflict_fails_closed(self):
adapter = BranchProtectionCASMigrationAdapter()
old, changed, new = legacy_protection(), legacy_protection(7), single_owner_protection()
plan = adapter.plan(old, new, snapshot_sha256(old))
store = MemoryProtectionStore(changed)
with self.assertRaises(CASConflict):
adapter.apply_fixture(store, plan)
def test_partial_activation_recovery_saga(self):
adapter = BranchProtectionCASMigrationAdapter()
old, new = legacy_protection(), single_owner_protection()
plan = adapter.plan(old, new, snapshot_sha256(old))
saga = ProtectionCompensationSaga(adapter, MemoryProtectionStore(old), plan)
saga.execute_fixture()
self.assertEqual(saga.compensate_fixture(), old)
self.assertTrue(saga.applied and saga.rolled_back)
def test_rollback_configuration_fixture_install(self):
adapter = BranchProtectionCASMigrationAdapter()
old, new = legacy_protection(), single_owner_protection()
plan = adapter.plan(old, new, snapshot_sha256(old))
manager = RollbackConfigurationManager()
config = manager.build(plan)
with DisposableGiteaFixture(old) as fixture:
path = manager.install_fixture(fixture.root, config)
self.assertTrue(path.exists())
self.assertTrue(manager.verify(config, plan))
def test_maintenance_lock_fences_concurrent_holder(self):
with tempfile.TemporaryDirectory(prefix="aios-governance-fixture-") as root:
path = Path(root) / "maintenance.lock"
with MaintenanceLock(path):
with self.assertRaises(BlockingIOError):
with MaintenanceLock(path):
pass
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,45 @@
from __future__ import annotations
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[2]
class SystemdContractTests(unittest.TestCase):
def test_units_are_static_dormant_and_credential_free(self):
units = sorted(
unit for unit in (ROOT / "deploy" / "systemd").glob("aios-governance-*.service")
if unit.name != "aios-governance-single-owner-activation.service"
)
self.assertEqual(len(units), 2)
for unit in units:
text = unit.read_text(encoding="utf-8")
with self.subTest(unit=unit.name):
self.assertIn("AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED=false", text)
self.assertIn("AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE=false", text)
self.assertIn("AIOS_GITOPS_WRITES_ENABLED=false", text)
self.assertIn("ProtectSystem=strict", text)
self.assertNotIn("EnvironmentFile=", text)
self.assertNotIn("LoadCredential=", text)
self.assertNotIn("WantedBy=", text)
self.assertNotIn("[Install]", text)
self.assertIn("--checkpoint-dir /var/lib/aios-gitops/runtime/single-owner-governance", text)
self.assertNotIn("/data/aios-gitops/production", text)
def test_activation_verifier_is_static_and_has_no_credential_or_install_hook(self):
unit = ROOT / "deploy" / "systemd" / "aios-governance-single-owner-activation.service"
text = unit.read_text(encoding="utf-8")
self.assertIn("AIOS_GITOPS_WRITES_ENABLED=false", text)
self.assertIn("--verify", text)
self.assertIn("ProtectSystem=strict", text)
self.assertNotIn("EnvironmentFile=", text)
self.assertNotIn("LoadCredential=", text)
self.assertNotIn("WantedBy=", text)
self.assertNotIn("[Install]", text)
self.assertNotIn("--activate", text)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,77 @@
from __future__ import annotations
from dataclasses import replace
from datetime import timedelta
import unittest
from aios_single_owner_governance.models import Review
from aios_single_owner_governance.validator import SingleOwnerApprovalValidator, ValidationDenied
from .helpers import HEAD, NOW, context, gates, owner_review
class ValidatorTests(unittest.TestCase):
def setUp(self):
self.validator = SingleOwnerApprovalValidator()
def denied(self, code, **changes):
with self.assertRaises(ValidationDenied) as caught:
self.validator.validate(context(**changes))
self.assertEqual(caught.exception.code, code)
def test_owner_current_head_and_machine_gates_pass(self):
result = self.validator.validate(context())
self.assertEqual(result.owner_review.reviewer, "owner")
self.assertEqual(len(result.machine_contexts), 3)
def test_requester_self_approval_denied(self):
self.denied("REQUESTER_SELF_APPROVAL", requester="owner")
def test_executor_self_approval_denied(self):
self.denied("EXECUTOR_SELF_APPROVAL", executor="owner")
def test_codex_as_owner_denied(self):
codex = owner_review(reviewer="codex", reviewer_id=42)
self.denied("OWNER_APPROVAL_REQUIRED", reviews=(codex,))
def test_gpt_as_owner_denied(self):
gpt = owner_review(reviewer="gpt", reviewer_id=43)
self.denied("OWNER_APPROVAL_REQUIRED", reviews=(gpt,))
def test_non_owner_approval_denied(self):
other = owner_review(reviewer="other", reviewer_id=77)
self.denied("OWNER_APPROVAL_REQUIRED", reviews=(other,))
def test_stale_owner_approval_denied(self):
self.denied("STALE_OWNER_APPROVAL", reviews=(owner_review(stale=True),))
def test_dismissed_owner_approval_denied(self):
self.denied("DISMISSED_OWNER_APPROVAL", reviews=(owner_review(dismissed=True),))
def test_old_head_owner_approval_denied(self):
self.denied("OWNER_APPROVAL_NOT_LATEST_HEAD", reviews=(owner_review(head_oid="b" * 40),))
def test_wip_denied(self):
self.denied("WIP_BLOCKED", wip=True)
def test_machine_gate_failure_denied(self):
self.denied("MACHINE_GATE_FAILED", machine_gates=gates(state="failure"))
def test_secret_scan_failure_denied(self):
failed = list(gates())
failed[1] = replace(failed[1], state="failure")
self.denied("MACHINE_GATE_FAILED", machine_gates=tuple(failed))
def test_machine_gate_wrong_head_denied(self):
self.denied("MACHINE_GATE_NOT_LATEST_HEAD", machine_gates=gates(head_oid="c" * 40))
def test_expired_request_denied(self):
self.denied("APPROVAL_EXPIRED", now=NOW + timedelta(hours=2))
def test_review_after_expiry_denied(self):
late = owner_review(reviewed_at=NOW + timedelta(hours=2))
self.denied("OWNER_APPROVAL_EXPIRED", reviews=(late,))
if __name__ == "__main__":
unittest.main()