Compare commits

..
Author SHA1 Message Date
AIOS Governed Continuation f7ab821186 feat(governed): protected-main status producer pipeline (OPTION A)
Establish a compliant status producer pipeline to unblock protected-main's
9 required status contexts (py-compile, git-diff-check, secret-scan,
autonomy-unit-tests, autonomy-smoke-tests, reconciliation-contract,
release-state-machine, campaign-approval-gate, signer-validator-contract).

- status_publisher: idempotent (effect_id), SUPPRESS_NOOP, RECONCILE,
  capability-minimal (status_publish only), append-only evidence store.
- status_orchestrator: exact-head-SHA binding, stale-SHA protection,
  merge eligibility gated on producer verdict + confirmed publish
  (existing_state_differs never credited green).
- status_evidence_contract: evidence schema, effect_id, N/A contract binding.
- producer_scope_classifier: campaign/signer applicability, fail-closed.
- status_producers/: 9 thin wrappers reusing existing validators (gitleaks,
  reconciliation, ReleaseStateMachine, campaign gate, signer contract).
- transport/broker: add publish_status_for_sha (exact SHA) + read_statuses.
- status_pipeline_entry: S6 trigger skeleton with optional scope/smoke
  injection (fail-closed when absent; no fabricated success).
- tests: 65 passing (engineer + QA adversarial + regression).

No forgery of success, no blanket green reporter, no weakening of
branch protection. Reuses existing Gitea machine broker + validators.
2026-09-15 15:12:15 +08:00
59 changed files with 2986 additions and 12388 deletions
@@ -1,829 +0,0 @@
#!/usr/bin/env python3
"""Durable online task protocol for external AIOS agents.
The store is deliberately independent from Git/Gitea mutation. It provides a
small durable state machine for publishing work, atomic claim/lease ownership,
heartbeat, idempotent result submission, explicit server-side verification,
lease recovery, and audit events.
No plaintext agent credential or task lease token is persisted.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import hashlib
import hmac
import json
from pathlib import Path
import secrets
import sqlite3
from typing import Any, Callable, Iterable, Mapping
import uuid
from aios_capability_contract import has_capabilities
TERMINAL_STATES = frozenset({"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"})
ACTIVE_LEASE_STATES = frozenset({"CLAIMED", "RUNNING"})
TASK_STATES = frozenset({
"QUEUED", "CLAIMED", "RUNNING", "VERIFYING",
"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED",
})
VERIFY_DECISIONS = frozenset({"SUCCEEDED", "NEEDS_REWORK", "FAILED"})
class TaskProtocolError(RuntimeError):
def __init__(self, code: str, http_status: int = 409) -> None:
super().__init__(code)
self.code = code
self.http_status = http_status
@dataclass(frozen=True, slots=True)
class Principal:
kind: str
identifier: str
def utcnow() -> datetime:
return datetime.now(timezone.utc)
def iso(value: datetime) -> str:
return value.astimezone(timezone.utc).isoformat()
def parse_time(value: str | None) -> datetime | None:
if not value:
return None
parsed = datetime.fromisoformat(value)
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed.astimezone(timezone.utc)
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def _string_list(value: Iterable[object] | None, field: str, *, max_items: int = 128) -> list[str]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, Iterable):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result: list[str] = []
for item in value:
text = str(item).strip()
if not text or len(text) > 160:
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result.append(text)
if len(result) > max_items:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 400)
return sorted(set(result))
def _json_object(value: object | None, field: str, *, max_bytes: int = 128 * 1024) -> dict[str, Any]:
if value is None:
return {}
if not isinstance(value, Mapping):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result = dict(value)
if len(canonical_json(result).encode("utf-8")) > max_bytes:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 413)
return result
def _criteria(value: object | None) -> list[Any]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, list):
raise TaskProtocolError("SUCCESS_CRITERIA_INVALID", 400)
if len(value) > 128:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
if len(canonical_json(value).encode("utf-8")) > 128 * 1024:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
return list(value)
class TaskStore:
"""SQLite-backed durable task store.
SQLite BEGIN IMMEDIATE serializes claims so two agents cannot successfully
claim the same queued task. WAL mode allows concurrent readers.
"""
def __init__(
self,
path: str | Path,
*,
now: Callable[[], datetime] = utcnow,
token_pepper: str = "",
) -> None:
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.now = now
self.token_pepper = token_pepper
self._initialize()
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.path, timeout=10, isolation_level=None)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA journal_mode=WAL")
connection.execute("PRAGMA synchronous=FULL")
connection.execute("PRAGMA busy_timeout=10000")
return connection
def _initialize(self) -> None:
with self._connect() as connection:
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY,
agent_type TEXT NOT NULL,
agent_version TEXT NOT NULL,
capabilities_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
token_hash TEXT NOT NULL UNIQUE,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY,
goal TEXT NOT NULL,
instructions TEXT NOT NULL,
success_criteria_json TEXT NOT NULL,
required_capabilities_json TEXT NOT NULL,
constraints_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
deadline TEXT,
priority INTEGER NOT NULL,
max_attempts INTEGER NOT NULL,
attempt INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL,
claimed_by TEXT,
lease_token_hash TEXT,
lease_expires_at TEXT,
result_json TEXT,
result_digest TEXT,
result_idempotency_key TEXT,
verification_json TEXT,
revision INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(claimed_by) REFERENCES agents(agent_id)
);
CREATE TABLE IF NOT EXISTS idempotency (
scope TEXT NOT NULL,
key TEXT NOT NULL,
payload_digest TEXT NOT NULL,
response_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY(scope, key)
);
CREATE TABLE IF NOT EXISTS events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT,
agent_id TEXT,
event_type TEXT NOT NULL,
payload_json TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_tasks_queue
ON tasks(status, priority DESC, created_at);
CREATE INDEX IF NOT EXISTS idx_tasks_lease
ON tasks(status, lease_expires_at);
CREATE INDEX IF NOT EXISTS idx_events_task
ON events(task_id, event_id);
"""
)
def _token_hash(self, token: str) -> str:
return hashlib.sha256((self.token_pepper + "\0" + token).encode("utf-8")).hexdigest()
@staticmethod
def _row_task(row: sqlite3.Row, *, include_result: bool = True) -> dict[str, Any]:
value: dict[str, Any] = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"deadline": row["deadline"],
"priority": row["priority"],
"max_attempts": row["max_attempts"],
"attempt": row["attempt"],
"status": row["status"],
"claimed_by": row["claimed_by"],
"lease_expires_at": row["lease_expires_at"],
"revision": row["revision"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
}
if include_result:
value["result"] = json.loads(row["result_json"]) if row["result_json"] else None
value["verification"] = (
json.loads(row["verification_json"]) if row["verification_json"] else None
)
return value
def _event(
self,
connection: sqlite3.Connection,
event_type: str,
*,
task_id: str | None = None,
agent_id: str | None = None,
payload: Mapping[str, Any] | None = None,
) -> None:
connection.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at)"
" VALUES(?,?,?,?,?)",
(task_id, agent_id, event_type, canonical_json(dict(payload or {})), iso(self.now())),
)
def register_agent(
self,
*,
agent_id: str,
agent_type: str,
agent_version: str,
capabilities: Iterable[object],
metadata: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
agent_id = agent_id.strip()
agent_type = agent_type.strip()
agent_version = agent_version.strip()
if not agent_id or len(agent_id) > 160 or not agent_type or not agent_version:
raise TaskProtocolError("AGENT_IDENTITY_INVALID", 400)
caps = _string_list(capabilities, "capabilities")
meta = _json_object(metadata, "metadata")
token = "agt_" + secrets.token_urlsafe(32)
now = iso(self.now())
try:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
connection.execute(
"INSERT INTO agents(agent_id,agent_type,agent_version,capabilities_json,"
"metadata_json,token_hash,status,created_at,last_seen_at)"
" VALUES(?,?,?,?,?,?,?,?,?)",
(
agent_id,
agent_type,
agent_version,
canonical_json(caps),
canonical_json(meta),
self._token_hash(token),
"ACTIVE",
now,
now,
),
)
self._event(
connection,
"AGENT_REGISTERED",
agent_id=agent_id,
payload={"agent_type": agent_type, "agent_version": agent_version},
)
connection.commit()
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("AGENT_ALREADY_EXISTS", 409) from exc
return {
"agent_id": agent_id,
"agent_type": agent_type,
"agent_version": agent_version,
"capabilities": caps,
"agent_token": token,
"credential_returned_once": True,
}
def authenticate_agent(self, token: str) -> dict[str, Any]:
if not token:
raise TaskProtocolError("UNAUTHORIZED", 401)
token_hash = self._token_hash(token)
with self._connect() as connection:
row = connection.execute(
"SELECT * FROM agents WHERE token_hash=? AND status='ACTIVE'",
(token_hash,),
).fetchone()
if row is None:
raise TaskProtocolError("UNAUTHORIZED", 401)
connection.execute(
"UPDATE agents SET last_seen_at=? WHERE agent_id=?",
(iso(self.now()), row["agent_id"]),
)
return {
"agent_id": row["agent_id"],
"agent_type": row["agent_type"],
"agent_version": row["agent_version"],
"capabilities": json.loads(row["capabilities_json"]),
"metadata": json.loads(row["metadata_json"]),
}
def _idempotent_lookup(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
) -> dict[str, Any] | None:
row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(scope, key),
).fetchone()
if row is None:
return None
if not hmac.compare_digest(row["payload_digest"], payload_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
return json.loads(row["response_json"])
def _idempotent_store(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
response: Mapping[str, Any],
) -> None:
connection.execute(
"INSERT INTO idempotency(scope,key,payload_digest,response_json,created_at)"
" VALUES(?,?,?,?,?)",
(scope, key, payload_digest, canonical_json(dict(response)), iso(self.now())),
)
def create_task(
self,
*,
task_id: str | None,
goal: str,
instructions: str,
success_criteria: object | None,
required_capabilities: Iterable[object] | None = None,
constraints: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
deadline: str | None = None,
priority: int = 0,
max_attempts: int = 3,
idempotency_key: str,
) -> dict[str, Any]:
task_id = (task_id or ("task-" + uuid.uuid4().hex)).strip()
goal = goal.strip()
instructions = instructions.strip()
idempotency_key = idempotency_key.strip()
if not task_id or len(task_id) > 180 or not goal or not instructions:
raise TaskProtocolError("TASK_BINDING_INVALID", 400)
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
if not isinstance(priority, int) or isinstance(priority, bool) or not -100 <= priority <= 100:
raise TaskProtocolError("PRIORITY_INVALID", 400)
if not isinstance(max_attempts, int) or isinstance(max_attempts, bool) or not 1 <= max_attempts <= 20:
raise TaskProtocolError("MAX_ATTEMPTS_INVALID", 400)
parsed_deadline = parse_time(deadline)
if parsed_deadline and parsed_deadline <= self.now():
raise TaskProtocolError("DEADLINE_INVALID", 400)
criteria = _criteria(success_criteria)
required = _string_list(required_capabilities, "required_capabilities")
constraints_value = _json_object(constraints, "constraints")
metadata_value = _json_object(metadata, "metadata")
payload = {
"task_id": task_id,
"goal": goal,
"instructions": instructions,
"success_criteria": criteria,
"required_capabilities": required,
"constraints": constraints_value,
"metadata": metadata_value,
"deadline": iso(parsed_deadline) if parsed_deadline else None,
"priority": priority,
"max_attempts": max_attempts,
}
payload_digest = digest_json(payload)
now = iso(self.now())
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
replay = self._idempotent_lookup(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
)
if replay is not None:
connection.commit()
return {**replay, "idempotent": True}
try:
connection.execute(
"INSERT INTO tasks(task_id,goal,instructions,success_criteria_json,"
"required_capabilities_json,constraints_json,metadata_json,deadline,priority,"
"max_attempts,attempt,status,revision,created_at,updated_at)"
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
task_id,
goal,
instructions,
canonical_json(criteria),
canonical_json(required),
canonical_json(constraints_value),
canonical_json(metadata_value),
payload["deadline"],
priority,
max_attempts,
0,
"QUEUED",
1,
now,
now,
),
)
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("TASK_ID_CONFLICT", 409) from exc
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
result = self._row_task(row)
self._idempotent_store(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
response=result,
)
self._event(connection, "TASK_CREATED", task_id=task_id, payload={"priority": priority})
connection.commit()
return result
def _expire_locked(self, connection: sqlite3.Connection) -> None:
now_dt = self.now()
now = iso(now_dt)
rows = connection.execute(
"SELECT * FROM tasks WHERE status IN ('QUEUED','CLAIMED','RUNNING')"
).fetchall()
for row in rows:
deadline = parse_time(row["deadline"])
if deadline is not None and deadline <= now_dt:
connection.execute(
"UPDATE tasks SET status='EXPIRED',claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(now, row["task_id"]),
)
self._event(connection, "TASK_EXPIRED", task_id=row["task_id"])
continue
if row["status"] in ACTIVE_LEASE_STATES:
lease_expires = parse_time(row["lease_expires_at"])
if lease_expires is not None and lease_expires <= now_dt:
if row["attempt"] >= row["max_attempts"]:
next_state = "FAILED"
else:
next_state = "QUEUED"
if next_state == "FAILED":
connection.execute(
"UPDATE tasks SET status=?,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
self._event(
connection,
"LEASE_EXPIRED",
task_id=row["task_id"],
agent_id=row["claimed_by"],
payload={"next_state": next_state, "attempt": row["attempt"]},
)
def expire_leases(self) -> int:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
before = connection.total_changes
self._expire_locked(connection)
changed = connection.total_changes - before
connection.commit()
return changed
def poll_tasks(self, *, agent_id: str, limit: int = 20) -> list[dict[str, Any]]:
if not 1 <= int(limit) <= 100:
raise TaskProtocolError("LIMIT_INVALID", 400)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._expire_locked(connection)
agent = connection.execute(
"SELECT capabilities_json FROM agents WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if agent is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
capabilities = json.loads(agent["capabilities_json"])
rows = connection.execute(
"SELECT * FROM tasks WHERE status='QUEUED' ORDER BY priority DESC,created_at ASC"
).fetchall()
result = []
for row in rows:
required = json.loads(row["required_capabilities_json"])
if has_capabilities(capabilities, required):
result.append(self._row_task(row, include_result=False))
if len(result) >= int(limit):
break
connection.commit()
return result
def _agent_capabilities(self, connection: sqlite3.Connection, agent_id: str) -> set[str]:
row = connection.execute(
"SELECT capabilities_json FROM agents WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if row is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
return set(json.loads(row["capabilities_json"]))
def claim_task(
self,
*,
task_id: str,
agent_id: str,
lease_seconds: int = 300,
) -> dict[str, Any]:
if not isinstance(lease_seconds, int) or isinstance(lease_seconds, bool) or not 30 <= lease_seconds <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
lease_token = "lease_" + secrets.token_urlsafe(32)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] != "QUEUED":
raise TaskProtocolError("TASK_NOT_CLAIMABLE", 409)
capabilities = self._agent_capabilities(connection, agent_id)
required = json.loads(row["required_capabilities_json"])
if not has_capabilities(capabilities, required):
raise TaskProtocolError("AGENT_CAPABILITY_MISMATCH", 403)
attempt = int(row["attempt"]) + 1
if attempt > int(row["max_attempts"]):
raise TaskProtocolError("TASK_ATTEMPTS_EXHAUSTED", 409)
expires = self.now() + timedelta(seconds=lease_seconds)
now = iso(self.now())
updated = connection.execute(
"UPDATE tasks SET status='CLAIMED',claimed_by=?,lease_token_hash=?,"
"lease_expires_at=?,attempt=?,revision=revision+1,updated_at=? "
"WHERE task_id=? AND status='QUEUED'",
(
agent_id,
self._token_hash(lease_token),
iso(expires),
attempt,
now,
task_id,
),
)
if updated.rowcount != 1:
raise TaskProtocolError("TASK_CLAIM_CONFLICT", 409)
self._event(
connection,
"TASK_CLAIMED",
task_id=task_id,
agent_id=agent_id,
payload={"attempt": attempt, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return {
**self._row_task(row, include_result=False),
"lease_token": lease_token,
"lease_token_returned_once": True,
}
def _validate_lease(
self,
connection: sqlite3.Connection,
*,
task_id: str,
agent_id: str,
lease_token: str,
allowed_states: frozenset[str] = ACTIVE_LEASE_STATES,
) -> sqlite3.Row:
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] not in allowed_states:
raise TaskProtocolError("TASK_LEASE_NOT_ACTIVE", 409)
if row["claimed_by"] != agent_id:
raise TaskProtocolError("TASK_LEASE_OWNER_MISMATCH", 403)
if not row["lease_token_hash"] or not hmac.compare_digest(
row["lease_token_hash"], self._token_hash(lease_token)
):
raise TaskProtocolError("TASK_LEASE_TOKEN_INVALID", 403)
expires = parse_time(row["lease_expires_at"])
if expires is None or expires <= self.now():
raise TaskProtocolError("TASK_LEASE_EXPIRED", 409)
return row
def heartbeat(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
progress: Mapping[str, Any] | None = None,
lease_seconds: int = 300,
) -> dict[str, Any]:
if not 30 <= int(lease_seconds) <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
progress_value = _json_object(progress, "progress", max_bytes=64 * 1024)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
expires = self.now() + timedelta(seconds=int(lease_seconds))
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='RUNNING',lease_expires_at=?,revision=revision+1,"
"updated_at=? WHERE task_id=?",
(iso(expires), now, task_id),
)
self._event(
connection,
"TASK_HEARTBEAT",
task_id=task_id,
agent_id=agent_id,
payload={"progress": progress_value, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row, include_result=False)
def submit_result(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
idempotency_key: str,
result: Mapping[str, Any],
) -> dict[str, Any]:
idempotency_key = idempotency_key.strip()
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
result_value = _json_object(result, "result", max_bytes=1024 * 1024)
result_digest = digest_json(result_value)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["result_idempotency_key"] == idempotency_key:
if not row["result_digest"] or not hmac.compare_digest(row["result_digest"], result_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
connection.commit()
return {**self._row_task(row), "idempotent": True}
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='VERIFYING',result_json=?,result_digest=?,"
"result_idempotency_key=?,lease_token_hash=NULL,lease_expires_at=NULL,"
"revision=revision+1,updated_at=? WHERE task_id=?",
(
canonical_json(result_value),
result_digest,
idempotency_key,
now,
task_id,
),
)
self._event(
connection,
"RESULT_SUBMITTED",
task_id=task_id,
agent_id=agent_id,
payload={"result_digest": result_digest, "idempotency_key": idempotency_key},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row)
def verify_task(
self,
*,
task_id: str,
decision: str,
verification: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
decision = decision.strip().upper()
if decision not in VERIFY_DECISIONS:
raise TaskProtocolError("VERIFICATION_DECISION_INVALID", 400)
verification_value = _json_object(verification, "verification")
verification_value = {"decision": decision, **verification_value}
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] != "VERIFYING":
raise TaskProtocolError("TASK_NOT_VERIFYING", 409)
if decision == "SUCCEEDED":
next_state = "SUCCEEDED"
elif decision == "FAILED":
next_state = "FAILED"
elif int(row["attempt"]) >= int(row["max_attempts"]):
next_state = "FAILED"
verification_value["reason"] = verification_value.get(
"reason", "MAX_ATTEMPTS_EXHAUSTED"
)
else:
next_state = "QUEUED"
now = iso(self.now())
if next_state in {"SUCCEEDED", "FAILED"}:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
self._event(
connection,
"TASK_VERIFIED",
task_id=task_id,
agent_id=row["claimed_by"],
payload={"decision": decision, "next_state": next_state},
)
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def cancel_task(self, *, task_id: str, reason: str = "") -> dict[str, Any]:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] in TERMINAL_STATES:
connection.commit()
return self._row_task(row)
connection.execute(
"UPDATE tasks SET status='CANCELLED',lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(iso(self.now()), task_id),
)
self._event(connection, "TASK_CANCELLED", task_id=task_id, payload={"reason": reason})
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def get_task(self, task_id: str) -> dict[str, Any]:
self.expire_leases()
with self._connect() as connection:
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
return self._row_task(row)
def get_events(self, task_id: str, *, limit: int = 200) -> list[dict[str, Any]]:
with self._connect() as connection:
rows = connection.execute(
"SELECT * FROM events WHERE task_id=? ORDER BY event_id DESC LIMIT ?",
(task_id, max(1, min(int(limit), 1000))),
).fetchall()
return [
{
"event_id": row["event_id"],
"task_id": row["task_id"],
"agent_id": row["agent_id"],
"event_type": row["event_type"],
"payload": json.loads(row["payload_json"]),
"created_at": row["created_at"],
}
for row in reversed(rows)
]
@@ -13,7 +13,9 @@ class MachineBrokerError(RuntimeError):
class GiteaTransport(Protocol):
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str: ...
def create_or_update_pr(self, branch: str, target: str, title: str, body: str) -> Mapping[str, Any]: ...
def publish_status(self, branch: str, state: str, description: str) -> Mapping[str, Any]: ...
def publish_status(self, branch: str, state: str, description: str, context: str) -> Mapping[str, Any]: ...
def publish_status_for_sha(self, sha: str, state: str, description: str, context: str) -> Mapping[str, Any]: ...
def read_statuses(self, sha: str, context: str | None = None) -> list[Mapping[str, Any]]: ...
def protected_merge(self, pr_number: int) -> Mapping[str, Any]: ...
def read_branch(self, branch: str) -> str | None: ...
def whoami(self) -> Mapping[str, Any]: ...
@@ -89,6 +91,15 @@ class GiteaMachineBroker:
self.receipt.require("status_publish")
return self.transport.publish_status(self.receipt.branch, state, description, context)
def status_for_sha(self, sha: str, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
"""Publish a status against an explicit commit SHA (protected-PR exact binding)."""
self.receipt.require("status_publish")
return self.transport.publish_status_for_sha(sha, state, description, context)
def read_statuses(self, sha: str, context: str | None = None) -> list[Mapping[str, Any]]:
self.receipt.require("status_publish")
return self.transport.read_statuses(sha, context=context)
def merge(self, pr_number: int) -> Mapping[str, Any]:
self.receipt.require("protected_merge")
return self.transport.protected_merge(pr_number)
@@ -302,6 +302,28 @@ class GiteaMachineTransport:
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/statuses/{oid}", {"state": state, "context": context, "description": description})[1]
def publish_status_for_sha(self, sha: str, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
"""Publish a commit status against an explicit 40-hex commit SHA.
Unlike :meth:`publish_status` (which binds to the current tip of a branch),
this variant binds immutably to the supplied object ID. It is the correct
primitive for protected-PR reporting where the PR head SHA is authoritative
and must not drift with a moving branch tip.
"""
if state not in {"pending", "success", "error", "failure", "warning"}:
raise GiteaTransportError("STATUS_STATE_INVALID")
oid = _safe_oid(sha, "status_oid")
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/statuses/{oid}", {"state": state, "context": context, "description": description})[1]
def read_statuses(self, sha: str, context: str | None = None) -> list[Mapping[str, Any]]:
"""Read commit statuses for an explicit SHA (used for RECONCILE)."""
oid = _safe_oid(sha, "status_read_oid")
owner, name = self.repository_id.split("/", 1)
query = f"?context={quote(context)}" if context else ""
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/statuses/{oid}{query}")[1]
return value if isinstance(value, list) else []
def protected_merge(self, pr_number: int) -> Mapping[str, Any]:
if not isinstance(pr_number, int) or pr_number < 1:
raise GiteaTransportError("PR_NUMBER_INVALID")
@@ -1,446 +0,0 @@
"""aios_owner_based_executor — Owner 基于授权的可执行 CLI 入口。
把 "owner 基于授权 → Agent 自主完成受治理写链" 从测试/装配层提升为**可直接执行的命令**。
集成 V2R2C3 现场组合根 `field_governed_writer.py`,装配到生产入口
`GovernedWriterAutomation.acquire_verified()`。
子命令:
register <authorization_ref> 登记一条 Owner 授权到 authority(可 --policy-json 指定,
默认用内置参考授权物 azn-owner-gw-v1)。
acquire <authorization_ref> 装配 FieldGovernedWriterFactory,运行 acquire_verified(),
输出 GovernedWriterSession JSON。
行为契约:
- 生产入口唯一授权来源是 authority 已登记的 authorization_ref;未登记即 fail-closed。
- authority 未装配(--no-authority 或构造时 owner_authority=None)→ AuthorizationDenied。
- EXPIRED 任务无预签 recovery → OwnerActionRequired(OWNER_BOUNDARY,非自动恢复)。
- status==error 输出到 stderr,退出码非 0;成功输出会话 JSON,退出码 0。
- legacy_global_writes_enabled 默认 false = 安全不变式,不是 blocker。
用法示例:
python aios_owner_based_executor.py register azn-owner-gw-v1
python aios_owner_based_executor.py acquire azn-owner-gw-v1
python aios_owner_based_executor.py acquire azn-owner-gw-v1 --branch-mode continue --head-oid <oid>
python aios_owner_based_executor.py acquire azn-owner-gw-v1 --task-status EXPIRED # OwnerBoundary
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import tempfile
import time
from pathlib import Path
from app.task_plane.field_governed_writer import (
FieldGovernedWriterFactory,
FieldOwnerAuthorizationAuthority,
FieldRuntimeStatus,
)
from app.task_plane.governance_control_kernel import (
ActionLedger,
ConsistencyBarrier,
ManifestStore,
DiscoveryObservation,
)
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
GovernedWriterRequest,
GovernedWriterSession,
OwnerActionRequired,
OwnerAuthorizationPolicy,
TaskState,
)
# 版本与示例授权引用
VERSION = "V2R2C3"
DEFAULT_AZN_REF = "azn-owner-gw-v1"
_AZN_CAPS = frozenset(
{
"code.write",
"governed.write.branch",
"governed.write.file",
"test.execute",
"writer.lease",
"task.principal.issue",
}
)
_AZN_OPS = frozenset(
{
"claim",
"epoch_reissue",
"governed_commit",
"heartbeat",
"job_reconcile",
"readback",
"reconcile",
"successor_recovery",
"task_lease",
"task_principal",
"writer_lease",
}
)
# ---------------------------------------------------------------------------
# 授权物 / 请求构造
# ---------------------------------------------------------------------------
def reference_policy(*, expires_at: float | None = None) -> OwnerAuthorizationPolicy:
"""内置参考授权物 azn-owner-gw-v1(现场登记时 Owner 设定当下有效 expires_at)。"""
if expires_at is None:
expires_at = time.time() + 30 * 86400.0
return OwnerAuthorizationPolicy(
authorization_id=DEFAULT_AZN_REF,
subject="aios-autonomous-admin",
repository="aios/aios",
allowed_branch_prefixes=("agent/autonomy-",),
allowed_capabilities=_AZN_CAPS,
risk_ceiling="medium",
trust_domain="aios.owner",
revocation_epoch=1,
not_before=0.0,
expires_at=expires_at,
revoked=False,
allowed_operations=_AZN_OPS,
)
def _default_subject_repo_branch(task: str) -> tuple[str, str, str]:
return ("aios-autonomous-admin", "aios/aios", f"agent/autonomy-{task}")
def build_request(
*,
task_id: str,
manifest_id: str,
runtime_fingerprint: str,
process_start_identity: str,
plan_digest: str,
base_oid: str,
generation: int,
risk: str,
caps: frozenset[str],
files: tuple[str, ...],
overrides: dict | None = None,
) -> GovernedWriterRequest:
subject, repo, branch = _default_subject_repo_branch(task_id)
body = dict(
subject=subject,
task_id=task_id,
repository=repo,
target_branch=branch,
risk=risk,
generation=generation,
base_oid=base_oid,
required_capabilities=caps,
file_allowlist=files,
plan_digest=plan_digest,
manifest_id=manifest_id,
runtime_fingerprint=runtime_fingerprint,
process_start_identity=process_start_identity,
)
if overrides:
body.update(overrides)
return GovernedWriterRequest(**body)
# ---------------------------------------------------------------------------
# 依据配置构造 authority(含 --no-authority fail-closed 场景)
# ---------------------------------------------------------------------------
def build_authority(
*, policy_json_path: str | None, use_reference: bool, expires_at: float | None
) -> FieldOwnerAuthorizationAuthority | None:
if policy_json_path:
data = json.loads(Path(policy_json_path).read_text(encoding="utf-8"))
pol = OwnerAuthorizationPolicy(**data["policy"])
auth = FieldOwnerAuthorizationAuthority()
auth.register(data["authorization_ref"], pol)
return auth
if use_reference:
auth = FieldOwnerAuthorizationAuthority()
auth.register(DEFAULT_AZN_REF, reference_policy(expires_at=expires_at))
return auth
return None # authority 未装配 -> acquire_verified 直接 fail-closed
# ---------------------------------------------------------------------------
# 子命令实现
# ---------------------------------------------------------------------------
def cmd_register(args: argparse.Namespace) -> int:
expires_at = float(args.expires_at) if args.expires_at else None
if args.policy_json:
data = json.loads(Path(args.policy_json).read_text(encoding="utf-8"))
pol = OwnerAuthorizationPolicy(**data["policy"])
ref = data.get("authorization_ref", pol.authorization_id)
else:
pol = reference_policy(expires_at=expires_at)
ref = DEFAULT_AZN_REF
auth = FieldOwnerAuthorizationAuthority()
auth.register(ref, pol)
print(
json.dumps(
{
"status": "registered",
"authorization_ref": ref,
"policy_fingerprint": pol.policy_fingerprint,
"subject": pol.subject,
"repository": pol.repository,
"allowed_branch_prefixes": list(pol.allowed_branch_prefixes),
"risk_ceiling": pol.risk_ceiling,
"expires_at": pol.expires_at,
"note": "registered in-memory authority; field authority must persist",
},
ensure_ascii=False,
indent=2,
)
)
return 0
def cmd_acquire(args: argparse.Namespace) -> int:
from app.task_plane.governance_control_kernel import StaleManifest
# 使用独立工作目录构造 manifest + ledger,避免污染运行目录
workdir = Path(args.workdir) if args.workdir else None
if workdir is None:
workdir = Path(tempfile.mkdtemp(prefix="gw_exec_"))
store = ManifestStore(str(workdir / "manifest.db"))
ledger = ActionLedger(str(workdir / "ledger.db"))
barrier = ConsistencyBarrier(["W1"])
barrier.submit(
DiscoveryObservation.build(
worker="W1",
runtime_fingerprint=args.runtime,
process_start_identity=args.process_start,
activation_digest="activation-exec",
epoch_id="epoch-exec-prov",
policy_digest="policy-exec",
plan_provider_root="/plans/exec",
task_id=args.task,
grant_id="grant-exec",
facts={},
evidence_refs=(),
observed_at=100.0,
)
)
manifest_id = store.freeze(barrier.freeze())
auth = build_authority(
policy_json_path=args.policy_json,
use_reference=args.reference,
expires_at=float(args.expires_at) if args.expires_at else None,
)
task_status = args.task_status.upper()
task = TaskState(task_id=args.task, status=task_status, revision=1)
if args.branch_mode == "create":
branch_exists, head_oid = False, None
elif args.branch_mode == "continue":
branch_exists, head_oid = True, args.head_oid
else: # auto: 由 read 决定(默认新分支)
branch_exists, head_oid = (True, args.head_oid) if args.head_oid else (False, None)
runtime_status = FieldRuntimeStatus(
legacy_global_writes_enabled=args.legacy_global,
task_principal_writes_enabled=args.principal_writes,
task_principal_verifier_ready=args.verifier_ready,
governed_write_blockers=tuple(args.blocker or ()),
)
# fail-closed authority 场景:--no-authority 显式不装配
if args.no_authority:
auth = None
adapters = _make_adapters(
authority=auth,
manifest_store=store,
action_ledger=ledger,
task=task,
branch_exists=branch_exists,
head_oid=head_oid,
repo=args.repo if args.repo else "aios/aios",
branch=args.branch if args.branch else f"agent/autonomy-{args.task}",
generation=args.generation,
runtime_status=runtime_status,
)
if auth is None:
adapters.owner_authority = None
automation = _automation(store, ledger)
request = build_request(
task_id=args.task,
manifest_id=manifest_id,
runtime_fingerprint=args.runtime,
process_start_identity=args.process_start,
plan_digest=args.plan_digest,
base_oid=args.base_oid,
generation=args.generation,
risk=args.risk,
caps=set(_AZN_CAPS),
files=tuple(args.files),
)
try:
session = automation.acquire_verified(
request=request,
authorization_ref=args.authorization_ref,
readiness=runtime_status_to_readiness(runtime_status),
epoch=None,
adapters=adapters,
now=time.time(),
)
print(json.dumps({"status": "ok", "version": VERSION, "session": session.to_dict()},
ensure_ascii=False, indent=2))
return 0
except OwnerActionRequired as exc:
print(json.dumps({"status": "owner_boundary", "version": VERSION, "reason": str(exc)},
ensure_ascii=False, indent=2), file=sys.stderr)
return 3
except AuthorizationDenied as exc:
print(json.dumps({"status": "auth_denied", "version": VERSION, "reason": str(exc)},
ensure_ascii=False, indent=2), file=sys.stderr)
return 2
except Exception as exc: # noqa: BLE001 - CLI 边界统一收口
print(json.dumps({"status": "error", "version": VERSION,
"error": f"{type(exc).__name__}: {exc}"},
ensure_ascii=False, indent=2), file=sys.stderr)
return 1
finally:
_cleanup_workdir(workdir)
def _make_adapters(
*,
authority,
manifest_store,
action_ledger,
task,
branch_exists,
head_oid,
repo,
branch,
generation,
runtime_status,
):
from app.task_plane.field_governed_writer import _DefaultExecutionPlane, _DefaultGit, \
_DefaultJobs, _DefaultPrincipalIssuer, _DefaultTrustedSigner, _DefaultWriterLease
from app.task_plane.governed_writer_automation import GovernedWriterAdapters
from app.task_plane.governed_writer_automation import GovernedWriteReadiness
return GovernedWriterAdapters(
signer=_DefaultTrustedSigner(),
execution=_DefaultExecutionPlane(task),
principal_issuer=_DefaultPrincipalIssuer(),
jobs=_DefaultJobs(repository=repo, target_branch=branch, generation=generation),
git=_DefaultGit(exists=branch_exists, head_oid=head_oid),
writer_lease=_DefaultWriterLease(),
owner_authority=authority,
)
def runtime_status_to_readiness(rs: FieldRuntimeStatus):
from app.task_plane.governed_writer_automation import GovernedWriteReadiness
return GovernedWriteReadiness(
legacy_global_writes_enabled=rs.legacy_global_writes_enabled,
task_principal_writes_enabled=rs.task_principal_writes_enabled,
task_principal_verifier_ready=rs.task_principal_verifier_ready,
governed_write_blockers=tuple(rs.governed_write_blockers),
)
def _automation(store, ledger):
from app.task_plane.governed_writer_automation import GovernedWriterAutomation
return GovernedWriterAutomation(manifest_store=store, action_ledger=ledger)
def _cleanup_workdir(workdir: Path) -> None:
import gc
import shutil
try:
gc.collect()
shutil.rmtree(workdir, ignore_errors=True)
except Exception: # pragma: no cover - 清理失败不影响结果
pass
# ---------------------------------------------------------------------------
# 参数与入口
# ---------------------------------------------------------------------------
def build_parser() -> argparse.ArgumentParser:
import tempfile # noqa: F401 (确保模块可见)
parser = argparse.ArgumentParser(
description="AIOS Owner 基于授权执行器 (V2R2C3)",
prog="aios_owner_based_executor",
)
sub = parser.add_subparsers(dest="command", required=True)
p_register = sub.add_parser("register", help="登记 Owner 授权到 authority")
p_register.add_argument("authorization_ref", nargs="?", default=DEFAULT_AZN_REF)
p_register.add_argument("--policy-json", help="policy JSON 文件(含 authorization_ref + policy)")
p_register.add_argument("--expires-at", help="override expires_at (unix)")
p_register.set_defaults(func=cmd_register)
p_acquire = sub.add_parser("acquire", help="运行 acquire_verified() 完成受治理写链")
p_acquire.add_argument("authorization_ref", nargs="?", default=DEFAULT_AZN_REF)
p_acquire.add_argument("--no-authority", action="store_true",
help="显式不装配 authority -> fail-closed")
p_acquire.add_argument("--policy-json", help="从文件装载授权物(替代内置参考)")
p_acquire.add_argument("--reference", action="store_true",
help="使用内置参考授权物 azn-owner-gw-v1")
p_acquire.add_argument("--expires-at", help="override expires_at (unix)")
p_acquire.add_argument("--task", default="task-gw-exec-0001")
p_acquire.add_argument("--task-status", default="QUEUED",
choices=["QUEUED", "CLAIMED", "RUNNING", "EXPIRED"])
p_acquire.add_argument("--branch-mode", default="create", choices=["create", "continue", "auto"])
p_acquire.add_argument("--head-oid", help="继续分支的 live HEAD oid")
p_acquire.add_argument("--base-oid", default="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3")
p_acquire.add_argument("--repo", help="仓库(默认 aios/aios)")
p_acquire.add_argument("--branch", help="目标分支(默认 agent/autonomy-<task>)")
p_acquire.add_argument("--generation", type=int, default=1)
p_acquire.add_argument("--risk", default="medium")
p_acquire.add_argument("--runtime", default="runtime-gw-exec")
p_acquire.add_argument("--process-start", default="pid:gw:exec:1")
p_acquire.add_argument("--plan-digest", default="sha256:plan-exec")
p_acquire.add_argument("--files", nargs="*", default=[
"active-runtime/app/task_plane/governance_control_kernel.py",
"active-runtime/app/task_plane/governed_writer_automation.py",
"active-runtime/app/task_plane/field_governed_writer.py",
])
p_acquire.add_argument("--workdir", help="manifest/ledger 工作目录(默认临时)")
# 运行时状态(W-RUNTIME 只读线)
p_acquire.add_argument("--legacy-global", action="store_true",
help="legacy_global_writes_enabled(默认 false=不变式)")
p_acquire.add_argument("--principal-writes", action="store_true", default=True,
help="task_principal_writes_enabled(默认 true)")
p_acquire.add_argument("--verifier-ready", action="store_true", default=True,
help="task_principal_verifier_ready(默认 true)")
p_acquire.add_argument("--blocker", action="append",
help="governed_write_blockers 追加")
p_acquire.set_defaults(func=cmd_acquire)
parser.add_argument("--version", action="version", version=VERSION)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
return int(args.func(args))
if __name__ == "__main__":
import tempfile # noqa: F401
try:
raise SystemExit(main())
except KeyboardInterrupt:
raise SystemExit(130)
@@ -0,0 +1,113 @@
"""Pure scope classifier for N/A-capable status contexts.
Architecture §2.6: campaign-approval-gate and signer-validator-contract may be
FORMALLY_NOT_APPLICABLE for a given PR only when an explicit applicability
contract reference exists. This module is pure (no I/O) so it can be tested on
any platform.
Policy:
- UNKNOWN/ambiguous scope facts -> NEVER success. The classifier raises or
returns a FAIL/UNKNOWN verdict rather than guessing APPLICABLE and later
publishing PASS, or guessing N/A without a contract ref.
- FORMALLY_NOT_APPLICABLE always carries a na_contract_ref so the applicability
decision is auditable and traceable (architecture §2.7 evidence field).
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping
# Campaign N/A contract references (governance clause defining N/A as a legal pass).
CAMPAIGN_NA_CONTRACT_REF = "aios.campaign-approval-gate.v1#formally-not-applicable"
SIGNER_NA_CONTRACT_REF = "aios.signer-validator-contract.v1#formally-not-applicable"
class ScopeClassificationError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class ScopeVerdict:
context: str
scope: str # "APPLICABLE" | "FORMALLY_NOT_APPLICABLE"
na_contract_ref: str | None
reason: str
facts: Mapping[str, Any]
@property
def applicable(self) -> bool:
return self.scope == "APPLICABLE"
@property
def formally_not_applicable(self) -> bool:
return self.scope == "FORMALLY_NOT_APPLICABLE"
# ---------------------------------------------------------------------------
# Context-specific applicability rules
# ---------------------------------------------------------------------------
def _campaign_append_tuples():
return ("campaign_authorization_present", "campaign_active", "in_campaign_lineage")
def _classify_campaign(facts: Mapping[str, Any]) -> ScopeVerdict:
authorization_present = facts.get("campaign_authorization_present")
campaign_active = facts.get("campaign_active")
in_lineage = facts.get("in_campaign_lineage")
if authorization_present is None or campaign_active is None or in_lineage is None:
raise ScopeClassificationError("CAMPAIGN_SCOPE_FACTS_INCOMPLETE")
if authorization_present is True and campaign_active is True and in_lineage is True:
return ScopeVerdict(
"campaign-approval-gate",
"APPLICABLE",
None,
"active_campaign_governs_pr",
dict(facts),
)
if authorization_present is False and in_lineage is False:
return ScopeVerdict(
"campaign-approval-gate",
"FORMALLY_NOT_APPLICABLE",
CAMPAIGN_NA_CONTRACT_REF,
"no_active_campaign_lineage",
dict(facts),
)
# Ambiguous (e.g. authorization present but inactive, or lineage unknown):
# an N/A verdict without a clean contract basis is forbidden - fail closed.
raise ScopeClassificationError("CAMPAIGN_SCOPE_AMBIGUOUS_FAIL_CLOSED")
def _classify_signer(facts: Mapping[str, Any]) -> ScopeVerdict:
touches_signer = facts.get("touches_signer_bound_change")
if touches_signer is None:
raise ScopeClassificationError("SIGNER_SCOPE_FACTS_INCOMPLETE")
if touches_signer is True:
return ScopeVerdict(
"signer-validator-contract",
"APPLICABLE",
None,
"signer_bound_change_present",
dict(facts),
)
return ScopeVerdict(
"signer-validator-contract",
"FORMALLY_NOT_APPLICABLE",
SIGNER_NA_CONTRACT_REF,
"no_signer_bound_change",
dict(facts),
)
def classify_na(context: str, pr_scope_facts: Mapping[str, Any]) -> ScopeVerdict:
"""Classify whether a context is APPLICABLE or FORMALLY_NOT_APPLICABLE.
Raises ScopeClassificationError (never returns a usable success) when scope
facts are incomplete or ambiguous. UNKNOWN never becomes APPLICABLE_PASS
or a bare FORMALLY_NOT_APPLICABLE success without a contract ref.
"""
if context == "campaign-approval-gate":
return _classify_campaign(pr_scope_facts)
if context == "signer-validator-contract":
return _classify_signer(pr_scope_facts)
# Any other context has no N/A semantics in this pipeline.
raise ScopeClassificationError(f"NO_NA_SEMANTICS_FOR_CONTEXT:{context}")
@@ -0,0 +1,200 @@
"""Protected-main status evidence contract (pure, no external side effects).
This module defines the canonical evidence record that a status producer emits
and the Status Publisher persists. It deliberately has zero I/O: everything
here is deterministic derivation from an in-memory record, so the schema and
identity rules can be unit-tested on any platform.
Identity model (architecture §2.4/§2.7):
effect_id = sha256(repo + "\\0" + commit_sha + "\\0" + context + "\\0" + evidence_revision)
The evidence_revision is a canonical sha256 over the evidence record *except*
the volatile fields (observed_at / effect_id / evidence_ref), so a re-run that
produces the identical result/inputs/version hashes to the same revision and is
idempotently suppressed, while any change in result/input/producer version
yields a new revision that legitimately re-publishes.
"""
from __future__ import annotations
import hashlib
import json
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Mapping
STATUS_EVIDENCE_SCHEMA = "aios.status-evidence.v1"
# The authoritative set of status contexts governed by this pipeline.
REQUIRED_CONTEXTS: tuple[str, ...] = (
"py-compile",
"git-diff-check",
"secret-scan",
"autonomy-unit-tests",
"autonomy-smoke-tests",
"reconciliation-contract",
"release-state-machine",
"campaign-approval-gate",
"signer-validator-contract",
)
ALLOWED_CONTEXTS: frozenset[str] = frozenset(REQUIRED_CONTEXTS)
# Results that may map to a Gitea success status. Everything else is never
# published as success (architecture §2.6 red line).
# APPLICABLE_PASS -> success
# FORMALLY_NOT_APPLICABLE -> success ONLY when a na_contract_ref is present.
# FAIL / UNKNOWN -> never success.
SUCCESS_RESULTS: frozenset[str] = frozenset({"APPLICABLE_PASS", "FORMALLY_NOT_APPLICABLE"})
def _canonical_json_bytes(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def sha256_hex(value: Any) -> str:
return hashlib.sha256(_canonical_json_bytes(value)).hexdigest()
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def validate_context(context: str) -> str:
context = str(context or "").strip()
if context not in ALLOWED_CONTEXTS:
raise ValueError(f"UNKNOWN_STATUS_CONTEXT:{context}")
return context
def validate_repository(repository: str, allowed_repository: str = "aios/aios") -> str:
repo = str(repository or "").strip()
if repo != allowed_repository:
raise ValueError(f"WRONG_REPOSITORY:{repo}")
return repo
def validate_commit_sha(commit_sha: str) -> str:
sha = str(commit_sha or "").strip().lower()
if len(sha) != 40 or any(ch not in "0123456789abcdef" for ch in sha):
raise ValueError("WRONG_COMMIT_SHA")
return sha
def compute_evidence_revision(record: Mapping[str, Any]) -> str:
"""Canonical sha256 over the deterministic evidence fields.
Volatile fields (observed_at / effect_id / evidence_ref / na_contract_ref
legality is fixed at record time) are excluded so that equal results and
inputs hash equal across runs.
"""
volatile = {"observed_at", "effect_id", "evidence_ref"}
core = {k: v for k, v in dict(record).items() if k not in volatile}
return "sha256:" + sha256_hex(core)
def compute_effect_id(repository: str, commit_sha: str, context: str, evidence_revision: str) -> str:
return "sha256:" + sha256_hex(f"{repository}\0{commit_sha}\0{context}\0{evidence_revision}")
@dataclass(frozen=True, slots=True)
class StatusEvidence:
schema: str = STATUS_EVIDENCE_SCHEMA
repository: str = "aios/aios"
commit_sha: str = ""
context: str = ""
result: str = ""
producer_identity: str = ""
producer_revision: str = ""
evidence_revision: str = ""
observed_at: str = ""
evidence_ref: str = ""
na_contract_ref: str | None = None
effect_id: str = ""
detail: Mapping[str, Any] = field(default_factory=dict)
def validated(self, *, observed_at: str | None = None) -> "StatusEvidence":
repo = validate_repository(self.repository)
sha = validate_commit_sha(self.commit_sha)
ctx = validate_context(self.context)
if self.result not in {"APPLICABLE_PASS", "FAIL", "UNKNOWN", "FORMALLY_NOT_APPLICABLE"}:
raise ValueError(f"INVALID_EVIDENCE_RESULT:{self.result}")
if self.result == "FORMALLY_NOT_APPLICABLE" and not self.na_contract_ref:
raise ValueError("NA_WITHOUT_CONTRACT_REF")
if not self.producer_identity or not self.producer_revision:
raise ValueError("PRODUCER_IDENTITY_INCOMPLETE")
ts = observed_at or utc_now()
return StatusEvidence(
schema=STATUS_EVIDENCE_SCHEMA,
repository=repo,
commit_sha=sha,
context=ctx,
result=self.result,
producer_identity=self.producer_identity,
producer_revision=self.producer_revision,
evidence_revision=self.evidence_revision,
observed_at=ts,
evidence_ref=self.evidence_ref,
na_contract_ref=self.na_contract_ref,
effect_id=self.effect_id,
detail=dict(self.detail),
)
def finalize(self, *, observed_at: str | None = None, evidence_ref: str = "") -> "StatusEvidence":
"""Compute evidence_revision + effect_id and return a validated record."""
if self.result == "FORMALLY_NOT_APPLICABLE" and not self.na_contract_ref:
raise ValueError("NA_WITHOUT_CONTRACT_REF")
base = dict(
repository=self.repository,
commit_sha=self.commit_sha,
context=self.context,
result=self.result,
producer_identity=self.producer_identity,
producer_revision=self.producer_revision,
na_contract_ref=self.na_contract_ref,
detail=dict(self.detail),
evidence_ref=evidence_ref or self.evidence_ref,
)
rev = compute_evidence_revision(base)
effect = compute_effect_id(self.repository, self.commit_sha, self.context, rev)
return StatusEvidence(
schema=STATUS_EVIDENCE_SCHEMA,
repository=self.repository,
commit_sha=validate_commit_sha(self.commit_sha),
context=validate_context(self.context),
result=self.result,
producer_identity=self.producer_identity,
producer_revision=self.producer_revision,
evidence_revision=rev,
observed_at=observed_at or utc_now(),
evidence_ref=evidence_ref or self.evidence_ref,
na_contract_ref=self.na_contract_ref,
effect_id=effect,
detail=dict(self.detail),
)
def as_dict(self) -> dict[str, Any]:
return {
"schema": self.schema,
"repository": self.repository,
"commit_sha": self.commit_sha,
"context": self.context,
"result": self.result,
"producer_identity": self.producer_identity,
"producer_revision": self.producer_revision,
"evidence_revision": self.evidence_revision,
"observed_at": self.observed_at,
"evidence_ref": self.evidence_ref,
"na_contract_ref": self.na_contract_ref,
"effect_id": self.effect_id,
"detail": dict(self.detail),
}
def maps_to_success(result: str) -> bool:
"""Whether a result may be published as a Gitea success status.
FAIL / UNKNOWN never map to success. FORMALLY_NOT_APPLICABLE only maps when
a contract ref is present, which is enforced at record validation time.
"""
return result in SUCCESS_RESULTS
+186
View File
@@ -0,0 +1,186 @@
"""Protected-main status orchestrator.
Responsibilities (architecture §2.2 / §2.5):
1. Resolve the PR's exact head SHA (never guessed locally).
2. Schedule all 9 required producers, each owning exactly one context.
3. Convert each ProducerOutput into a StatusEvidence and hand it to the
Status Publisher (the only writer of Gitea statuses).
4. Enforce stale-SHA safety: every run keys effects on the *current* head SHA;
evidence is always regenerated for that head economy — old SHA statuses are
never copied onto a new head. A head change requires a full re-run.
5. Report merge eligibility: protected main may merge only when every required
context resolved to a genuine ``success`` status on the current head.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Mapping
from status_evidence_contract import (
REQUIRED_CONTEXTS,
StatusEvidence,
validate_context,
validate_commit_sha,
validate_repository,
)
from status_publisher import AppendOnlyEvidenceStore, StatusPublisher
class OrchestratorError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class ContextOutcome:
context: str
producer_output: Any
evidence: StatusEvidence
publish: Any # PublishOutcome
@dataclass(frozen=True, slots=True)
class RunResult:
head_sha: str
context_outcomes: Mapping[str, ContextOutcome]
merge_eligible: bool
def resolve_pr_head_sha(pr: Mapping[str, Any], *, expected_repository: str = "aios/aios") -> str:
"""Resolve the exact head SHA from a Gitea PR object (authoritative)."""
repo = (pr.get("base") or {}).get("repo") or {}
full_name = repo.get("full_name") or repo.get("fullname") or ""
if full_name and full_name != expected_repository:
raise OrchestratorError(f"WRONG_REPOSITORY:{full_name}")
head = (pr.get("head") or {}).get("sha")
if not head:
raise OrchestratorError("PR_HEAD_SHA_MISSING")
return validate_commit_sha(head)
# The 9 producers, keyed by their governed context name.
def _default_producers() -> Mapping[str, Callable[[Any], Any]]:
from status_producers import (
py_compile,
git_diff_check,
secret_scan,
autonomy_unit_tests,
autonomy_smoke_tests,
reconciliation_contract,
release_state_machine,
campaign_approval_gate,
signer_validator_contract,
)
return {
"py-compile": py_compile.run,
"git-diff-check": git_diff_check.run,
"secret-scan": secret_scan.run,
"autonomy-unit-tests": autonomy_unit_tests.run,
"autonomy-smoke-tests": autonomy_smoke_tests.run,
"reconciliation-contract": reconciliation_contract.run,
"release-state-machine": release_state_machine.run,
"campaign-approval-gate": campaign_approval_gate.run,
"signer-validator-contract": signer_validator_contract.run,
}
class StatusOrchestrator:
def __init__(
self,
publisher: StatusPublisher,
*,
producers: Mapping[str, Callable[[Any], Any]] | None = None,
checkout: Path | None = None,
base_sha: str | None = None,
extra_facts: Mapping[str, Any] | None = None,
) -> None:
self.publisher = publisher
self.producers = _default_producers() if producers is None else producers
if set(self.producers) != set(REQUIRED_CONTEXTS):
raise OrchestratorError("PRODUCER_CONTEXT_SET_MISALIGNED")
self.checkout = checkout
self.base_sha = base_sha
self.extra_facts = dict(extra_facts or {})
def run_for_pr(self, pr: Mapping[str, Any]) -> RunResult:
head_sha = resolve_pr_head_sha(pr)
return self._run_head(head_sha)
def run_for_head(self, head_sha: str) -> RunResult:
return self._run_head(validate_commit_sha(head_sha))
def _run_head(self, head_sha: str) -> RunResult:
outcomes: dict[str, ContextOutcome] = {}
for context in REQUIRED_CONTEXTS:
validate_context(context)
producer = self.producers[context]
try:
producer_output = producer(
self._make_ctx(context, head_sha)
)
except Exception as exc: # noqa: BLE001
from status_producers import unknown_output
producer_output = unknown_output(
context, f"aios.{context}", f"producer_error:{exc}"
)
evidence = StatusEvidence(
repository=validate_repository("aios/aios"),
commit_sha=head_sha,
context=context,
result=producer_output.result,
producer_identity=producer_output.producer_identity,
producer_revision=producer_output.producer_revision,
na_contract_ref=producer_output.na_contract_ref,
detail=dict(producer_output.detail),
)
publish = self.publisher.publish(evidence)
outcomes[context] = ContextOutcome(context, producer_output, evidence, publish)
merge_eligible = all(
self._context_green(outcomes[ctx]) for ctx in REQUIRED_CONTEXTS
)
return RunResult(head_sha, outcomes, merge_eligible)
@staticmethod
def _context_green(outcome: ContextOutcome) -> bool:
"""A context is merge-green IFF its *verdict* genuinely passed AND its
status publish was *confirmed*.
The verdict (evidence.result) is authoritative (architecture §5): a
FAIL/UNKNOWN context can never be green no matter what a conflicting
leftover Gitea status says. A green verdict additionally requires a
confirmed publish (PUBLISHED / RECONCILED matched_expected / SUPPRESS_NOOP
with a verified success readback) — never an unconfirmed
existing_state_differs result.
"""
evidence = outcome.evidence
verdict_green = (
evidence.result == "APPLICABLE_PASS"
or (
evidence.result == "FORMALLY_NOT_APPLICABLE"
and bool(evidence.na_contract_ref)
)
)
if not verdict_green:
return False
publish = outcome.publish
if not publish.confirmed:
return False
# Confirmation must carry the eligible action forms only.
if publish.action not in {"PUBLISHED", "RECONCILED", "SUPPRESS_NOOP"}:
return False
return True
def _make_ctx(self, context: str, head_sha: str) -> Any:
from status_producers import ProducerContext
facts = dict(self.extra_facts)
facts.setdefault("commit_sha", head_sha)
return ProducerContext(
context=context,
repository="aios/aios",
commit_sha=head_sha,
checkout=self.checkout,
base_sha=self.base_sha,
scope_facts=facts,
)
+197
View File
@@ -0,0 +1,197 @@
"""Protected-main status pipeline entry point (S6 trigger integration skeleton).
This module wires the real, locally-validated pipeline to a production Gitea
broker using environment-provided configuration. It is intentionally a *thin*
adapter: it constructs a capability-minimal broker (only ``status_publish`` plus
the readback needed for RECONCILE), the idempotent publisher, the append-only
evidence store, and an orchestrator, then runs one PR.
It does NOT fake or shortcut production connectivity: any missing environment
(exact PR head, checkout, gitleaks, signer, campaign authorization) is surfaced
as an explicit error/UNKNOWN through the pipeline's fail-closed semantics. This
file is the integration point a production trigger (webhook / durable job /
poll loop) calls; the trigger mechanism itself is owned by the orchestrating
service and is out of scope here.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
from aios_gitea_machine_broker import (
CampaignCapabilityReceipt,
GiteaMachineBroker,
MachineCredentialProjection,
)
from status_evidence_contract import REQUIRED_CONTEXTS
from status_orchestrator import StatusOrchestrator
from status_publisher import (
AppendOnlyEvidenceStore,
DANGEROUS_CAPABILITIES,
StatusPublisher,
)
REPOSITORY = os.environ.get("AIOS_GITEA_REPOSITORY", "aios/aios")
def _json_env(name: str) -> dict | None:
"""Parse a JSON object from an environment variable; None if absent/unparsable.
Unparsable input is ignored (fail-closed) so a malformed injection never
silently degrades a check to green — it simply behaves as if not provided,
which keeps the pipeline's UNKNOWN fail-closed semantics intact.
"""
raw = os.environ.get(name, "").strip()
if not raw:
return None
try:
value = json.loads(raw)
except json.JSONDecodeError:
return None
return value if isinstance(value, dict) else None
def build_orchestrator(
*,
scope_facts: dict | None = None,
smoke_results: dict | None = None,
) -> StatusOrchestrator:
"""Build the orchestrator from environment (capability-minimal broker).
``scope_facts`` (campaign_authorization_present / campaign_active /
in_campaign_lineage / touches_signer_bound_change) and ``smoke_results`` are
*optional injection channels* that let a production trigger supply real
observed applicability / smoke findings so the 9 contexts can genuinely
reach 9/9. They are never defaulted to fake pass values: when absent or
malformed the pipeline stays UNKNOWN fail-closed. Env vars are merged
first; explicit caller args take precedence.
"""
from aios_gitea_machine_transport import GiteaMachineTransport
account = os.environ.get("AIOS_GITEA_MACHINE_ACCOUNT", "aios-autonomy-agent")
api = os.environ.get(
"AIOS_GITEA_API", "https://g.cnnetmkt.top/git/api/v1"
)
remote = os.environ.get(
"AIOS_GITEA_REMOTE", "https://g.cnnetmkt.top/git/aios/aios.git"
)
authority_dir = Path(
os.environ.get("AIOS_GITOPS_AUTHORITY_GIT_DIR", "/data/aios-gitops/repo.git")
)
checkout = Path(os.environ.get("AIOS_STATUS_CHECKOUT", "")) or None
evidence_root = Path(
os.environ.get("AIOS_STATUS_EVIDENCE_ROOT", "/var/lib/aios-gitops/status-evidence")
)
transport = GiteaMachineTransport(api, REPOSITORY, remote, authority_dir)
credential = MachineCredentialProjection.from_mapping(
{"scheme": "systemd-credential", "name": "gitea-machine-token", "account": account}
)
# Capability-minimal: only status_publish (+ readback for RECONCILE), and an
# explicit denied set covering dangerous capabilities (aligned with the
# gitops denied set). The publisher additionally rejects any dangerous cap
# that leaks through here.
receipt = CampaignCapabilityReceipt(
os.environ.get("AIOS_CAMPAIGN_ID", "status-pipeline"),
os.environ.get("AIOS_TASK_ID", "status-pipeline-task"),
REPOSITORY,
os.environ.get("AIOS_STATUS_BRANCH", "agent/status-pipeline"),
os.environ.get("AIOS_EXPECTED_BRANCH_OID", ""),
frozenset({"status_publish"}),
frozenset(DANGEROUS_CAPABILITIES),
)
broker = GiteaMachineBroker(transport, credential, receipt)
store = AppendOnlyEvidenceStore(evidence_root)
publisher = StatusPublisher(
broker,
store,
receipt_capabilities={
"capabilities": set(receipt.capabilities),
"denied": set(receipt.denied),
},
)
# Assemble optional injection channels (env first, caller args override).
extra_facts: dict = {
"pr_number": int(os.environ.get("AIOS_STATUS_PR_NUMBER", "0") or 0),
}
env_scope = _json_env("AIOS_STATUS_SCOPE_FACTS")
if env_scope:
extra_facts.update(env_scope)
if scope_facts:
extra_facts.update(scope_facts)
env_smoke = _json_env("AIOS_STATUS_SMOKE_RESULTS")
smoke: dict = {}
if env_smoke:
smoke.update(env_smoke)
if smoke_results:
smoke.update(smoke_results)
if smoke:
extra_facts["smoke_results"] = smoke
return StatusOrchestrator(
publisher,
checkout=checkout,
base_sha=os.environ.get("AIOS_STATUS_BASE_SHA"),
extra_facts=extra_facts,
)
def _json_arg(value: str | None, name: str) -> dict | None:
if value is None or not value.strip():
return None
try:
parsed = json.loads(value)
except json.JSONDecodeError as exc:
raise SystemExit(f"{name} must be a JSON object") from exc
if not isinstance(parsed, dict):
raise SystemExit(f"{name} must be a JSON object")
return parsed
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Run the protected-main status pipeline for a PR")
parser.add_argument("--pr-json", help="Gitea PR object JSON (must include base.repo and head.sha)")
parser.add_argument("--head-sha", help="exact head SHA (alternative to --pr-json)")
parser.add_argument("--scope-facts", help="JSON object of applicability scope facts (campaign/signer)")
parser.add_argument("--smoke-results", help="JSON object of observed smoke check results")
args = parser.parse_args(argv)
orchestrator = build_orchestrator(
scope_facts=_json_arg(args.scope_facts, "--scope-facts"),
smoke_results=_json_arg(args.smoke_results, "--smoke-results"),
)
try:
if args.head_sha:
result = orchestrator.run_for_head(args.head_sha)
elif args.pr_json:
result = orchestrator.run_for_pr(json.loads(args.pr_json))
else:
raise SystemExit("require --pr-json or --head-sha")
except Exception as exc: # noqa: BLE001
print(json.dumps({"status": "ERROR", "reason": str(exc)}, sort_keys=True))
return 2
summary = {
"head_sha": result.head_sha,
"required_contexts": len(REQUIRED_CONTEXTS),
"merge_eligible": result.merge_eligible,
"contexts": {
ctx: {
"action": o.publish.action,
"state": o.publish.gitea_state,
"result": o.producer_output.result,
}
for ctx, o in result.context_outcomes.items()
},
}
print(json.dumps(summary, sort_keys=True))
return 0 if result.merge_eligible else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,79 @@
"""Protected-main status producers (thin, real-execution wrappers).
Each producer owns exactly one required context and derives a deterministic
verdict from *real* tool output or a real validator — never a manufactured
``success``. Producers never write Gitea statuses; they emit a
:class:`ProducerOutput` that the Status Orchestrator converts into a
:class:`~status_evidence_contract.StatusEvidence` for the Status Publisher.
Testability
-----------
The real-execution subprocess helper ``run_command`` is injectable via
``set_command_runner`` so unit tests can resolve failures without invoking the
real toolchain (there is no Gitea / gitleaks / signer binary on non-Linux dev
hosts). Production loans the real runner.
"""
from __future__ import annotations
import subprocess
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Mapping, Sequence
# Every producer has a fixed producer_identity (audit attribution).
PRODUCER_REVISION = "producer.2026-09-15.1"
CommandRunner = Callable[[Sequence[str], Path | None], subprocess.CompletedProcess[str]]
def _default_command_runner(
command: Sequence[str], cwd: Path | None = None, *, timeout: int = 300
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
list(command),
cwd=cwd,
text=True,
capture_output=True,
timeout=timeout,
check=False,
)
_ACTIVE_RUNNER: CommandRunner = _default_command_runner
def set_command_runner(runner: CommandRunner) -> None:
"""Override the subprocess runner (used by tests to avoid real toolchains)."""
global _ACTIVE_RUNNER
_ACTIVE_RUNNER = runner
@dataclass(frozen=True, slots=True)
class ProducerContext:
context: str = "" # governed context name this run serves
repository: str = "aios/aios"
commit_sha: str = ""
checkout: Path | None = None # exact-head checkout (real exec target)
base_sha: str | None = None # for base...head checks
scope_facts: Mapping[str, Any] = field(default_factory=dict)
runner_timeout: int = 300
@dataclass(frozen=True, slots=True)
class ProducerOutput:
context: str
result: str # APPLICABLE_PASS | FAIL | UNKNOWN | FORMALLY_NOT_APPLICABLE
producer_identity: str
producer_revision: str
detail: Mapping[str, Any] = field(default_factory=dict)
na_contract_ref: str | None = None
def fail_output(context: str, identity: str, reason: str, detail: Mapping[str, Any] | None = None) -> ProducerOutput:
merged = dict(detail or {})
merged["reason"] = reason
return ProducerOutput(context, "FAIL", identity, PRODUCER_REVISION, merged)
def unknown_output(context: str, identity: str, reason: str) -> ProducerOutput:
return ProducerOutput(context, "UNKNOWN", identity, PRODUCER_REVISION, {"reason": reason})
@@ -0,0 +1,57 @@
"""autonomy-smoke-tests producer: real staging/smoke checklist.
This must never be fulfilled by rerunning the unit suite. Smoke checks are a
separate, environment-dependent checklist; where a real staging environment is
unavailable the producer reports UNKNOWN rather than an invented pass.
"""
from __future__ import annotations
from pathlib import Path
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
# Smoke checklist entries as (label). Each must map to a real check at runtime;
# the checklist itself is the contract, not the unit suite.
SMOKE_CHECKS: tuple[str, ...] = (
"imports_load",
"core_services_respond",
)
def run(
ctx: ProducerContext,
*,
results: dict[str, bool] | None = None,
) -> ProducerOutput:
identity = "aios.autonomy-smoke-tests"
checkout = ctx.checkout
if checkout is None or not checkout.is_dir():
return unknown_output(ctx.context, identity, "checkout_unavailable")
# Production executes real staging probes and reports a results map via the
# injected smoke_results (contract layer); fall back to scope_facts so an
# orchestrator/entry can pass concrete observed results. If no real results
# were observed, this is UNKNOWN — never an auto-pass.
if results is None:
injected = ctx.scope_facts.get("smoke_results")
results = dict(injected) if isinstance(injected, dict) else None
if results is None:
return unknown_output(ctx.context, identity, "smoke_environment_unavailable")
missing = [label for label in SMOKE_CHECKS if label not in results]
if missing:
return fail_output(ctx.context, identity, "smoke_results_incomplete", {"missing": missing})
all_passed = all(bool(results[label]) for label in SMOKE_CHECKS)
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if all_passed else "FAIL",
identity,
PRODUCER_REVISION,
{"smoke_checks": {label: bool(results[label]) for label in SMOKE_CHECKS}},
)
@@ -0,0 +1,60 @@
"""autonomy-unit-tests producer: real pytest suite on a stable subset."""
from __future__ import annotations
import os
from pathlib import Path
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
# Stable pure-logic subset that does not drag in fcntl/systemd/signer binaries,
# so it can run on non-Linux hosts and in CI. Production may extend this list,
# but a producer pass must always result from a real suite that ran green.
STABLE_UNIT_TESTS: tuple[str, ...] = (
"tests/test_convergence_contracts.py",
"tests/test_gitea_transport_consistency_provider.py",
)
def run(ctx: ProducerContext) -> ProducerOutput:
identity = "aios.autonomy-unit-tests"
checkout = ctx.checkout
if checkout is None or not checkout.is_dir():
return unknown_output(ctx.context, identity, "checkout_unavailable")
missing = [t for t in STABLE_UNIT_TESTS if not (checkout / t).is_file()]
if missing:
return fail_output(ctx.context, identity, "test_targets_missing", {"missing": missing})
from . import _ACTIVE_RUNNER
app = checkout / "active-runtime" / "app"
env_paths = [str(checkout), str(app)]
env = dict(os.environ)
env["PYTHONPATH"] = os.pathsep.join(env_paths)
result = _ACTIVE_RUNNER(
["python", "-m", "pytest", "-q", "-p", "no:cacheprovider", *STABLE_UNIT_TESTS],
checkout,
)
return _from_pytest(ctx, result, identity)
def _from_pytest(ctx: ProducerContext, result, identity: str) -> ProducerOutput:
# A pass requires a real, green run: exit 0 and no failures. The runner
# returns a string-yield CompletedProcess; tests may attach a mock.
passed = result.returncode == 0
detail = {
"exit_code": result.returncode,
"tests_run": [t for t in STABLE_UNIT_TESTS],
"summary": (result.stdout or "")[:2000],
}
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if passed else "FAIL",
identity,
PRODUCER_REVISION,
detail,
)
@@ -0,0 +1,92 @@
"""campaign-approval-gate producer.
Scope is classified via ``producer_scope_classifier.classify_na``:
- APPLICABLE -> run the real ``campaign_approval_gate.evaluate``; a missing
CampaignAuthorization or unavailable toolchain is UNKNOWN, never success.
- FORMALLY_NOT_APPLICABLE -> emit an NA verdict bound to the applicability
contract ref. An N/A verdict with no contract ref is forbidden.
"""
from __future__ import annotations
from pathlib import Path
from producer_scope_classifier import (
ScopeClassificationError,
classify_na,
)
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
def run(
ctx: ProducerContext,
*,
campaign_facts: dict | None = None,
campaign_path: Path | None = None,
) -> ProducerOutput:
identity = "aios.campaign-approval-gate"
facts = campaign_facts if campaign_facts is not None else dict(ctx.scope_facts)
try:
verdict = classify_na(ctx.context, facts)
except ScopeClassificationError as exc:
return unknown_output(ctx.context, identity, f"scope_unknown:{exc}")
if verdict.formally_not_applicable:
return ProducerOutput(
ctx.context,
"FORMALLY_NOT_APPLICABLE",
identity,
PRODUCER_REVISION,
{"reason": verdict.reason, "scope_facts": dict(facts), "applicable": False},
na_contract_ref=verdict.na_contract_ref,
)
# APPLICABLE: real gate requires a CampaignAuthorization artifact.
if campaign_path is None or not campaign_path.is_file():
return unknown_output(ctx.context, identity, "campaign_authorization_unavailable")
try:
from release_campaign_authorization import CampaignAuthorization
from campaign_approval_gate import evaluate
except Exception as exc: # noqa: BLE001
return unknown_output(ctx.context, identity, f"gate_module_unavailable:{exc}")
try:
campaign = CampaignAuthorization.load(campaign_path, require_active=True)
gate_facts = {
"repository": ctx.repository,
"pr_number": int(facts.get("pr_number", 0)),
"base_ref": "refs/heads/main",
"base_oid": facts.get("base_oid", ctx.base_sha or ""),
"head_oid": ctx.commit_sha,
"required_checks_all_success": True,
"required_checks_same_head": True,
"unresolved_conversations": 0,
"mergeable": True,
"protected_target": "main",
}
if ctx.checkout is not None:
from campaign_approval_gate import git_is_ancestor
lineage_ok = (
git_is_ancestor(ctx.checkout, campaign.anchor_oid, gate_facts["base_oid"])
and git_is_ancestor(ctx.checkout, gate_facts["base_oid"], ctx.commit_sha)
)
else:
lineage_ok = True
if not lineage_ok:
return fail_output(ctx.context, identity, "campaign_lineage_mismatch")
result = evaluate(campaign, gate_facts, ctx.checkout)
except Exception as exc: # noqa: BLE001
return fail_output(ctx.context, identity, f"campaign_evaluate_failed:{exc}")
passed = result.get("status") == "SUCCESS"
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if passed else "FAIL",
identity,
PRODUCER_REVISION,
{"gate_status": result.get("status"), "campaign_id": result.get("campaign_id"), "applicable": True},
)
@@ -0,0 +1,42 @@
"""git-diff-check producer: genuine ``git diff --check`` on base...head."""
from __future__ import annotations
from pathlib import Path
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
def run(ctx: ProducerContext) -> ProducerOutput:
identity = "aios.git-diff-check"
checkout = ctx.checkout
if checkout is None or not checkout.is_dir():
return unknown_output(ctx.context, identity, "checkout_unavailable")
base = ctx.base_sha
if not base:
return fail_output(ctx.context, identity, "base_sha_missing")
from . import _ACTIVE_RUNNER
# Real whitespace/conflict-marker check over the PR range.
result = _ACTIVE_RUNNER(
["git", "diff", "--check", f"{base}...{ctx.commit_sha}"], checkout
)
passed = result.returncode == 0
detail = {
"base": base,
"head": ctx.commit_sha,
"exit_code": result.returncode,
"whitespace_errors": (result.stdout + result.stderr).strip(),
}
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if passed else "FAIL",
identity,
PRODUCER_REVISION,
detail,
)
@@ -0,0 +1,40 @@
"""py-compile producer: real byte-compilation of the PR head checkout."""
from __future__ import annotations
from pathlib import Path
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
def run(ctx: ProducerContext) -> ProducerOutput:
identity = "aios.py-compile"
checkout = ctx.checkout
if checkout is None or not checkout.is_dir():
return unknown_output(ctx.context, identity, "checkout_unavailable")
app = checkout / "active-runtime" / "app"
targets = sorted(str(p) for p in app.glob("*.py")) if app.is_dir() else []
if not targets:
return fail_output(ctx.context, identity, "no_python_targets")
from . import _ACTIVE_RUNNER
result = _ACTIVE_RUNNER(["python", "-m", "py_compile", *targets], checkout)
passed = result.returncode == 0
detail = {
"compiled_count": len(targets),
"exit_code": result.returncode,
"checked_abs": False,
}
producer = ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if passed else "FAIL",
identity,
PRODUCER_REVISION,
detail,
)
return producer
@@ -0,0 +1,99 @@
"""reconciliation-contract producer: pure convergence-contract self-consistency.
Orchestrates the existing pure validators
(``aios_gitops_reconciliation_core.decide_reconciliation``,
``reconciliation_controller.ReconciliationController.reconcile`` and
``aios_convergence_contracts.plan_reconciliation``) and asserts the contract is
self-consistent (no drift / converged / no unknown observation). Pure and fully
local-runnable.
"""
from __future__ import annotations
from pathlib import Path
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
def run(
ctx: ProducerContext,
*,
observation: dict | None = None,
controller_state: dict | None = None,
) -> ProducerOutput:
identity = "aios.reconciliation-contract"
from aios_gitops_reconciliation_core import RemoteObservation, decide_reconciliation
from reconciliation_controller import ReconciliationController
from aios_convergence_contracts import (
DesiredState,
ObservedState,
ObservedHealth,
plan_reconciliation,
)
# apply decide_reconciliation on a healthy observation
obs = observation or {
"repository_identity": ctx.repository,
"branch": "agent/status-pipeline-check",
"desired_oid": ctx.commit_sha,
"authority_oid": ctx.commit_sha,
"remote_git_oid": ctx.commit_sha,
"remote_api_oid": ctx.commit_sha,
"commit_visible": True,
"observed_at": "1970-01-01T00:00:00Z",
"expected_oid": ctx.commit_sha,
}
decision = decide_reconciliation(RemoteObservation(**obs))
# apply plan_reconciliation on healthy observed state
desired = DesiredState("/status-pipeline", "git_status", 1, {"head": ctx.commit_sha})
observed = ObservedState(
"/status-pipeline",
1,
{"head": ctx.commit_sha},
ObservedHealth.HEALTHY,
"reconciliation-contract",
"1970-01-01T00:00:00Z",
)
plan = plan_reconciliation(desired, observed)
# apply controller reconcile on a no-drift state set
controller = ReconciliationController()
state = controller_state or {
"authority_main": ctx.commit_sha,
"gitea_main": ctx.commit_sha,
"local_tag_oid": "t",
"remote_tag_oid": "t",
"remote_peeled_target": ctx.commit_sha,
"approved_target_oid": ctx.commit_sha,
"registry_version": "1",
"baseline_version": "1",
"approved_artifact_digest": "d",
"deployed_artifact_digest": "d",
"declared_production_state": True,
"service_healthy": True,
}
report = controller.reconcile(state)
consistent = (
decision.state == "REMOTE_VERIFIED"
and plan.state.value == "CONVERGED"
and report.healthy is True
)
detail = {
"decide_reconciliation": decision.state,
"plan_reconciliation": plan.state.value,
"controller_healthy": report.healthy,
}
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if consistent else "FAIL",
identity,
PRODUCER_REVISION,
detail,
)
@@ -0,0 +1,46 @@
"""release-state-machine producer: fail-closed reachability of a legal path.
Walks the explicit ``ReleaseStateMachine`` transitions and asserts a legal,
non-fail-closed path exists from SOURCE_READY to PR_APPROVED. A gate that
demands missing evidence fails closed and yields FAIL, never success.
"""
from __future__ import annotations
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
)
def run(ctx: ProducerContext) -> ProducerOutput:
identity = "aios.release-state-machine"
from release_state_machine import (
ReleaseState,
ReleaseStateMachine,
InvalidTransition,
)
legal_path = (
ReleaseState.SOURCE_READY,
ReleaseState.TESTED,
ReleaseState.PR_OPEN,
ReleaseState.PR_APPROVED,
)
sm = ReleaseStateMachine()
try:
for target in legal_path[1:]:
sm.transition(target)
except InvalidTransition as exc:
return fail_output(ctx.context, identity, f"illegal_transition:{exc}")
reached = sm.state == ReleaseState.PR_APPROVED
detail = {"reached": sm.state.value, "legal_path": [s.value for s in legal_path]}
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if reached else "FAIL",
identity,
PRODUCER_REVISION,
detail,
)
@@ -0,0 +1,98 @@
"""secret-scan producer: real gitleaks scanner on the head checkout.
Reuses the governed ``run_secret_scan_provider`` (gitleaks ``--no-git``) and the
repository root ``.gitleaks.toml`` allowlist. A scanner that is unavailable or
crashes is UNKNOWN/FAIL — never an automatic pass.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
_DEFAULT_GITLEAKS_BIN = "/usr/bin/gitleaks"
def _resolve_gitleaks_bin(gitleaks_bin: str | None) -> str:
"""Resolve the scanner binary from (in order): caller arg, env, config, default.
The explicit ``.gitleaks.toml``-style config in
``config/gitops/status-pipeline.json`` is consumed as a fallback so the
operator's declared path is honored. Returns the path string; presence on
disk is validated separately so an unusable binary fails closed to UNKNOWN.
"""
if gitleaks_bin:
return gitleaks_bin
env_path = os.environ.get("AIOS_GITLEAKS_BIN", "").strip()
if env_path:
return env_path
cfg = Path(__file__).resolve().parents[3] / "config" / "gitops" / "status-pipeline.json"
if cfg.is_file():
try:
decoded = json.loads(cfg.read_text(encoding="utf-8"))
configured = (decoded.get("secret_scan") or {}).get("gitleaks_bin")
if isinstance(configured, str) and configured.strip():
return configured.strip()
except (OSError, json.JSONDecodeError):
pass
return _DEFAULT_GITLEAKS_BIN
def run(
ctx: ProducerContext,
*,
gitleaks_bin: str | None = None,
allowlist_toml: str = ".gitleaks.toml",
) -> ProducerOutput:
identity = "aios.secret-scanner"
checkout = ctx.checkout
if checkout is None or not checkout.is_dir():
return unknown_output(ctx.context, identity, "checkout_unavailable")
# Resolve the scanner binary and validate it really exists on disk.
# Absent -> UNKNOWN (fail-closed, never success).
resolved_bin = _resolve_gitleaks_bin(gitleaks_bin)
if not Path(resolved_bin).is_file():
return unknown_output(ctx.context, identity, "secret_scanner_unavailable")
allowlist = checkout / allowlist_toml
if not allowlist.is_file():
# The governed repo is expected to carry an allowlist; a manifest-proof
# absence is fail-closed, not silently accepted.
return fail_output(ctx.context, identity, "gitleaks_allowlist_missing")
try:
from trusted_evidence_providers import run_secret_scan_provider
except Exception as exc: # noqa: BLE001
return unknown_output(ctx.context, identity, f"provider_unavailable:{exc}")
try:
result, ref = run_secret_scan_provider(
gitleaks_bin=resolved_bin,
target=checkout,
source_oid=ctx.commit_sha,
tree_oid="",
provider_identity=identity,
)
except Exception as exc: # noqa: BLE001
return unknown_output(ctx.context, identity, f"secret_scan_error:{exc}")
passed = bool(result.get("passed"))
detail = dict(result)
detail["allowlist_toml"] = allowlist_toml
detail["receipt_ref"] = ref
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if passed else "FAIL",
identity,
PRODUCER_REVISION,
detail,
)
@@ -0,0 +1,75 @@
"""signer-validator-contract producer.
Scope is classified via ``producer_scope_classifier.classify_na``:
- APPLICABLE -> require a real signer/validator run
(``signer_contract_compatibility.run_compatible_validator``); an unavailable
toolchain is UNKNOWN, never an invented PASS.
- FORMALLY_NOT_APPLICABLE -> emit an NA verdict bound to the applicability
contract ref.
"""
from __future__ import annotations
from pathlib import Path
from producer_scope_classifier import (
ScopeClassificationError,
classify_na,
)
from . import (
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
fail_output,
unknown_output,
)
def run(
ctx: ProducerContext,
*,
signer_facts: dict | None = None,
grant_dir: Path | None = None,
) -> ProducerOutput:
identity = "aios.signer-validator-contract"
facts = signer_facts if signer_facts is not None else dict(ctx.scope_facts)
try:
verdict = classify_na(ctx.context, facts)
except ScopeClassificationError as exc:
return unknown_output(ctx.context, identity, f"scope_unknown:{exc}")
if verdict.formally_not_applicable:
return ProducerOutput(
ctx.context,
"FORMALLY_NOT_APPLICABLE",
identity,
PRODUCER_REVISION,
{"reason": verdict.reason, "scope_facts": dict(facts), "applicable": False},
na_contract_ref=verdict.na_contract_ref,
)
# APPLICABLE: requires a real signed grant to validate.
if grant_dir is None or not grant_dir.is_dir():
return unknown_output(ctx.context, identity, "signed_grant_unavailable")
try:
from signer_contract_compatibility import run_compatible_validator
except Exception as exc: # noqa: BLE001
return unknown_output(ctx.context, identity, f"signer_module_unavailable:{exc}")
try:
result_path = grant_dir / "compat-validator-result.json"
run_compatible_validator(
["aios-compatible-validator"],
grant_dir,
result_path,
)
payload = (grant_dir / "grant-payload.json").is_file()
except Exception as exc: # noqa: BLE001
return fail_output(ctx.context, identity, f"validator_failed:{exc}")
passed = payload
return ProducerOutput(
ctx.context,
"APPLICABLE_PASS" if passed else "FAIL",
identity,
PRODUCER_REVISION,
{"grant_validated": bool(payload), "applicable": True},
)
+311
View File
@@ -0,0 +1,311 @@
"""Idempotent, capability-minimal status publisher for protected main.
This is the *only* component licensed to write Gitea commit statuses for the
protected-main pipeline (architecture §2.4). It:
- binds every status to an explicit exact commit SHA via
``GiteaMachineBroker.status_for_sha`` (never a moving branch tip);
- derives the Gitea state purely from the evidence ``result`` (never accepts a
caller-supplied ``state=success``);
- is idempotent: the same effect_id (repo\0sha\0context\0evidence_revision) is a
NOOP and is not re-posted;
- on an unknown POST outcome performs a bounded RECONCILE (GET statuses) and
never blind-posts success after a timeout;
- persists every evidence record to an append-only store (O_EXCL + fsync) and
never deletes history.
Only the ``status_publish`` capability is required (enforced inside the broker's
``status_for_sha``); the publisher additionally refuses a receipt that grants
any dangerous capability that the gitops denied-set should have excluded.
"""
from __future__ import annotations
import hashlib
import json
import os
import stat
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Mapping
from status_evidence_contract import (
StatusEvidence,
maps_to_success,
sha256_hex,
validate_commit_sha,
validate_context,
validate_repository,
)
# Dangerous capabilities that must never be co-granted with status_publish on
# the publisher identity (aligned with aios_gitops_job_executor.py:502 denied set).
DANGEROUS_CAPABILITIES: frozenset[str] = frozenset(
{
"direct_main_push",
"force_push",
"tag_update",
"tag_delete",
"baseline_write",
"production_deploy",
"protected_merge",
}
)
class StatusPublishError(RuntimeError):
pass
def gitea_state_for_result(result: str, *, has_na_contract_ref: bool) -> str:
"""Map a deterministic evidence result to a Gitea status state.
success is only ever produced for APPLICABLE_PASS or FORMALLY_NOT_APPLICABLE
(the latter only when an applicability contract ref is present - enforced by
StatusEvidence validation). FAIL/UNKNOWN yield failure/error and are never
success. This is the single authorized projection point.
"""
if result == "APPLICABLE_PASS":
return "success"
if result == "FORMALLY_NOT_APPLICABLE":
if not has_na_contract_ref:
raise StatusPublishError("NA_WITHOUT_CONTRACT_REF")
return "success"
if result == "FAIL":
return "failure"
if result == "UNKNOWN":
return "error"
raise StatusPublishError(f"INVALID_EVIDENCE_RESULT:{result}")
class StatusPublisherError(RuntimeError):
pass
class AppendOnlyEvidenceStore:
"""Append-only evidence store (O_EXCL + fsync), never deletes history.
In production this directory is root-owned and non-group-writable (mode
0700). The vendor style mirrors ``trusted_evidence_providers._write_receipt_exclusive`` /
``gitea_transport_consistency_provider._write_receipt_exclusive``; no root
elevate is asserted here so the store remains testable on any platform,
but the deployment must place it under a root-owned path.
"""
def __init__(self, root: Path) -> None:
self.root = Path(root)
def _path_for(self, effect_id: str) -> Path:
digest = str(effect_id or "").strip()
if not digest.startswith("sha256:") or len(digest) != 7 + 64:
raise StatusPublisherError("EFFECT_ID_INVALID")
# derive a safe relative ref with no traversal
leaf = digest.split(":", 1)[1]
return self.root / f"{leaf[:2]}" / f"{leaf}.json"
def has(self, effect_id: str) -> bool:
return self._path_for(effect_id).is_file()
def append(self, effect_id: str, record: Mapping[str, Any]) -> str:
target = self._path_for(effect_id)
data = json.dumps(
record, sort_keys=True, separators=(",", ":"), ensure_ascii=False
).encode("utf-8")
if target.exists() or target.is_symlink():
raise StatusPublisherError("EFFECT_ALREADY_PERSISTED")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
flags |= getattr(os, "O_NOFOLLOW", 0)
flags |= getattr(os, "O_BINARY", 0)
try:
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
fd = os.open(target, flags, 0o600)
except OSError as exc:
raise StatusPublisherError("EVIDENCE_WRITE_DENIED") from exc
try:
view = memoryview(data)
while view:
written = os.write(fd, view)
if written <= 0:
raise StatusPublisherError("EVIDENCE_WRITE_SHORT")
view = view[written:]
os.fsync(fd)
except OSError as exc:
raise StatusPublisherError("EVIDENCE_WRITE_DENIED") from exc
finally:
os.close(fd)
os.chmod(target, 0o600)
return str(target)
@dataclass(frozen=True, slots=True)
class PublishOutcome:
action: str # PUBLISHED | SUPPRESS_NOOP | RECONCILED | UNKNOWN
effect_id: str
gitea_state: str | None
reason: str
# Whether this context's status is *confirmed* as the derived success state.
# False for UNKNOWN and for a RECONCILE that hit a conflicting leftover state
# (existing_state_differs): such results must NEVER be credited as green.
confirmed: bool = False
reconciled_from_status: Mapping[str, Any] | None = None
class StatusPublisher:
def __init__(
self,
broker: Any,
store: AppendOnlyEvidenceStore,
*,
receipt_capabilities: Mapping[str, Any] | None = None,
retry_reconcile: bool = True,
) -> None:
"""broker must expose ``status_for_sha(sha, state, description, context)`` and
``read_statuses(sha, context)`` (duck-typed for testability)."""
self.broker = broker
self.store = store
self._receipt_caps = receipt_capabilities
self.retry_reconcile = retry_reconcile
def verify_minimal_capability(self) -> None:
caps = self._receipt_caps
if caps is None:
# Nothing to introspect (tests pass a fake); skip.
return
capabilities = caps.get("capabilities") or ()
granted = set(capabilities)
overlap = granted & DANGEROUS_CAPABILITIES
if overlap:
raise StatusPublisherError(
"CAPABILITY_MINIMIZATION_VIOLATED:" + ",".join(sorted(overlap))
)
if "status_publish" not in granted:
raise StatusPublisherError("CAPABILITY_STATUS_PUBLISH_MISSING")
def publish(self, evidence: StatusEvidence) -> PublishOutcome:
self.verify_minimal_capability()
record = evidence.finalize()
effect_id = record.effect_id
# Idempotency: identical effect already persisted -> SUPPRESS_NOOP.
# A replay is only *confirmed* as green when a readback verifies the
# derived state already exists; otherwise it stays unconfirmed (a prior
# run may have persisted evidence but never confirmed the POST).
if self.store.has(effect_id):
confirmed = self._readback_confirmed(record)
return PublishOutcome(
"SUPPRESS_NOOP", effect_id, None, "duplicate_effect", confirmed=confirmed
)
gitea_state = gitea_state_for_result(
record.result, has_na_contract_ref=bool(record.na_contract_ref)
)
# Persist the evidence record *before* any POST so a lost response never
# loses the evidence itself. Ordering is explicit and not overwritten.
try:
evidence_ref = self.store.append(effect_id, record.as_dict())
except StatusPublisherError as exc:
# A concurrent writer persisted the same effect first (EEXIST). This
# is an idempotent replay, not an ERROR: never abort the round and
# never count green unless a readback confirms the success state.
if "EFFECT_ALREADY_PERSISTED" in str(exc):
confirmed = self._readback_confirmed(record)
return PublishOutcome(
"SUPPRESS_NOOP",
effect_id,
None,
"concurrent_duplicate",
confirmed=confirmed,
)
raise
description = f"{record.context} -> {record.result}"
try:
self.broker.status_for_sha(
record.commit_sha, gitea_state, description, record.context
)
except Exception as exc: # noqa: BLE001 - any POST uncertainty -> RECONCILE
resolved = self._reconcile(record, gitea_state, effect_id)
if resolved is not None:
return resolved
# Fail closed: an unconfirmed POST that could not be reconciled is
# reported as error (never success) and never confirmed.
return PublishOutcome(
"UNKNOWN",
effect_id,
"error",
f"post_unknown:{exc}",
confirmed=False,
)
return PublishOutcome(
"PUBLISHED", effect_id, gitea_state, "posted", confirmed=True, reconciled_from_status=None
)
def _readback_confirmed(self, record: StatusEvidence) -> bool:
"""Confirm a persisted effect by reading Gitea's real status.
A replay is only a confirmed success when the readback shows the derived
state (e.g. success) already present for this SHA+context. Absent,
errored, or conflicting (e.g. leftover) reads are fail-closed -> False.
"""
expected = gitea_state_for_result(
record.result, has_na_contract_ref=bool(record.na_contract_ref)
)
if expected != "success":
return False
try:
statuses = self.broker.read_statuses(record.commit_sha, record.context)
except Exception: # noqa: BLE001
return False
for row in statuses:
if not isinstance(row, Mapping):
continue
if row.get("context") == record.context and row.get("state") == "success":
return True
return False
def _reconcile(
self, record: StatusEvidence, gitea_state: str, effect_id: str
) -> PublishOutcome | None:
"""Bounded RECONCILE: query Gitea statuses for the exact SHA+context.
Returns a resolved outcome or None when still unknown. Never posts
success blindly; never overwrites a conflicting existing status.
"""
try:
statuses = self.broker.read_statuses(record.commit_sha, record.context)
except Exception: # noqa: BLE001
if self.retry_reconcile:
try:
statuses = self.broker.read_statuses(record.commit_sha, record.context)
except Exception: # noqa: BLE001
return None
else:
return None
for row in statuses:
if not isinstance(row, Mapping):
continue
if row.get("context") != record.context:
continue
actual_state = row.get("state")
if actual_state == gitea_state:
# The Gitea state matches exactly what the verdict projected:
# this is a genuine confirmation of the derived state.
return PublishOutcome(
"RECONCILED",
effect_id,
actual_state,
"matched_expected",
confirmed=True,
reconciled_from_status=dict(row),
)
# Conflicting pre-existing / leftover state (e.g. a stray success while
# the verdict projects failure). Report the real state but mark it
# NOT confirmed so the orchestrator never credits it as green.
return PublishOutcome(
"RECONCILED",
effect_id,
actual_state,
"existing_state_differs",
confirmed=False,
reconciled_from_status=dict(row),
)
return None
@@ -1,972 +0,0 @@
"""AIOS-native binding layer for the unattended resume controller.
This module intentionally contains no HTTP client, Unix-socket client, database,
systemd integration, scheduler loop, secret handling, or privileged identity
mutation.
It converts existing AIOS authorities into the generic callbacks defined by
``autonomous_resume_integration``. Field code supplies the current authoritative
implementations for:
- durable Owner authorization/grant projection;
- canonical governed-worker activation/status;
- governed task admission;
- task/claim/TaskLease/TaskPrincipal/WriterLease/CAS ownership;
- bounded source-lane execution and integration;
- Gate0;
- ReleaseReady/deployability;
- PromotionController;
- final production acceptance;
- mission resume lease/checkpoint/effect reconciliation.
The adapter is deterministic and fail-closed. It never fabricates any credential,
principal, lease, fence, task, commit, receipt, approval, or deployment result.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import hashlib
import json
from typing import Mapping, Protocol, Sequence
from .autonomous_resume_controller import (
EffectState,
MissionCheckpoint,
MissionObservation,
MissionPhase,
ResumeLease,
StepResult,
StepStatus,
)
from .autonomous_resume_integration import (
AuthorityCallbacks,
CallableAutonomousExecutionPort,
PhaseCallbacks,
)
def _canonical(value) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _digest(value) -> str:
return hashlib.sha256(_canonical(value)).hexdigest()
class WorkerState(str, Enum):
ACTIVE = "ACTIVE"
STARTING = "STARTING"
INACTIVE = "INACTIVE"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class WorkState(str, Enum):
PENDING = "PENDING"
RUNNING = "RUNNING"
COMPLETE = "COMPLETE"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class GateState(str, Enum):
PASS = "PASS"
HOLD = "HOLD"
UNKNOWN = "UNKNOWN"
class ReadyState(str, Enum):
READY = "READY"
NOT_READY = "NOT_READY"
UNKNOWN = "UNKNOWN"
class PromotionState(str, Enum):
IN_PROGRESS = "IN_PROGRESS"
VERIFIED = "VERIFIED"
ROLLED_BACK = "ROLLED_BACK"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class AcceptanceState(str, Enum):
PASS = "PASS"
FAIL_ROLLED_BACK = "FAIL_ROLLED_BACK"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
@dataclass(frozen=True)
class DurableGrantView:
grant_id: str
active: bool
repository: str
allowed_target_prefixes: tuple[str, ...]
max_task_risk: str
expires_at: float | None = None
owner_action_required: bool = False
evidence_refs: tuple[str, ...] = ()
def permits(self, *, repository: str, branch: str, risk: str) -> bool:
if not self.active:
return False
if self.repository != repository:
return False
if not any(branch.startswith(prefix) for prefix in self.allowed_target_prefixes):
return False
rank = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
if risk not in rank or self.max_task_risk not in rank:
return False
return rank[risk] <= rank[self.max_task_risk]
@dataclass(frozen=True)
class TaskLaneSpec:
lane: str
task_id: str
repository: str
target_branch: str
risk: str
description: str
write_set: tuple[str, ...]
def __post_init__(self) -> None:
if not self.lane or not self.task_id or not self.repository:
raise ValueError("lane_task_repository_required")
if not self.target_branch.startswith("agent/"):
raise ValueError("target_branch_must_use_agent_prefix")
if not self.write_set:
raise ValueError("write_set_required")
@dataclass(frozen=True)
class TaskAdmissionRequest:
task_id: str
idempotency_key: str
repository: str
base_oid: str
target_branch: str
expected_branch_oid: str
risk: str
description: str
@dataclass(frozen=True)
class TaskAdmissionReceipt:
task_id: str
generation: int
target_branch: str
persisted: bool
claimable: bool
receipt_ref: str
def __post_init__(self) -> None:
if self.generation < 1:
raise ValueError("task_generation_must_be_positive")
if not self.receipt_ref:
raise ValueError("task_admission_receipt_required")
@dataclass(frozen=True)
class WorkerActivationReceipt:
state: WorkerState
receipt_ref: str | None = None
retry_after_seconds: int | None = None
owner_action_required: bool = False
code: str = "WORKER_STATE"
@dataclass(frozen=True)
class LaneExecutionView:
lane: str
state: WorkState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "LANE_STATE"
@dataclass(frozen=True)
class IntegrationView:
state: WorkState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "INTEGRATION_STATE"
@dataclass(frozen=True)
class GateView:
state: GateState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "GATE_STATE"
@dataclass(frozen=True)
class ReleaseReadyView:
state: ReadyState
condition_fingerprint: str
material_digest: str | None
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "READY_STATE"
@dataclass(frozen=True)
class PromotionView:
state: PromotionState
condition_fingerprint: str
material_digest: str | None
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "PROMOTION_STATE"
@dataclass(frozen=True)
class AcceptanceView:
state: AcceptanceState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
code: str = "ACCEPTANCE_STATE"
@dataclass(frozen=True)
class MissionEffectView:
effect_state: EffectState
condition_fingerprint: str
requires_reconcile: bool
receipt_refs: tuple[str, ...] = ()
class AiosMissionAuthorities(Protocol):
"""Existing AIOS authority surface consumed by this adapter."""
# Live truth.
def read_head_oid(self, repository: str) -> str: ...
def read_material_digest(self, mission_id: str) -> str: ...
def read_generation(self, mission_id: str) -> int: ...
def read_effect_view(self, mission_id: str) -> MissionEffectView: ...
def owner_action_resolved(self, mission_id: str) -> bool: ...
# Durable mission state / one-winner resume authority.
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None: ...
def acquire_resume_lease(
self,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None: ...
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> None: ...
def persist_owner_action_required(
self,
payload: Mapping[str, object],
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str: ...
def release_resume_lease(
self,
lease: ResumeLease,
outcome_code: str,
) -> None: ...
def reconcile_effects(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
# Admission.
def read_durable_grant(self, mission_id: str) -> DurableGrantView: ...
def ensure_canonical_worker(
self,
mission_id: str,
generation: int,
idempotency_key: str,
) -> WorkerActivationReceipt: ...
def create_or_resume_task(
self,
request: TaskAdmissionRequest,
) -> TaskAdmissionReceipt: ...
def read_task_admission(
self,
lane: TaskLaneSpec,
) -> TaskAdmissionReceipt | None: ...
# Phase execution.
def read_lane_execution(
self,
lane: TaskLaneSpec,
task: TaskAdmissionReceipt,
) -> LaneExecutionView: ...
def drive_integration(
self,
lanes: Sequence[TaskLaneSpec],
tasks: Sequence[TaskAdmissionReceipt],
) -> IntegrationView: ...
def run_gate0(self, mission_id: str) -> GateView: ...
def build_release_ready(self, mission_id: str) -> ReleaseReadyView: ...
def run_promotion(self, mission_id: str) -> PromotionView: ...
def run_final_acceptance(self, mission_id: str) -> AcceptanceView: ...
@dataclass(frozen=True)
class AiosUnattendedMissionConfig:
mission_id: str
repository: str
lanes: tuple[TaskLaneSpec, ...]
admission_risk: str = "medium"
def __post_init__(self) -> None:
if not self.mission_id or not self.repository:
raise ValueError("mission_and_repository_required")
if not self.lanes:
raise ValueError("at_least_one_lane_required")
names = [lane.lane for lane in self.lanes]
task_ids = [lane.task_id for lane in self.lanes]
branches = [lane.target_branch for lane in self.lanes]
if len(names) != len(set(names)):
raise ValueError("duplicate_lane")
if len(task_ids) != len(set(task_ids)):
raise ValueError("duplicate_task_id")
if len(branches) != len(set(branches)):
raise ValueError("duplicate_target_branch")
owners: dict[str, str] = {}
for lane in self.lanes:
if lane.repository != self.repository:
raise ValueError("lane_repository_mismatch")
for resource in lane.write_set:
if resource in owners:
raise ValueError(
f"overlapping_lane_resource:{resource}:"
f"{owners[resource]}:{lane.lane}"
)
owners[resource] = lane.lane
class AiosUnattendedCallbackFactory:
"""Construct generic controller callbacks from current AIOS authorities."""
def __init__(
self,
authority: AiosMissionAuthorities,
config: AiosUnattendedMissionConfig,
) -> None:
self.authority = authority
self.config = config
self._admissions: dict[str, TaskAdmissionReceipt] = {}
def build_port(self) -> CallableAutonomousExecutionPort:
return CallableAutonomousExecutionPort(
AuthorityCallbacks(
read_observation=self.read_observation,
load_checkpoint=self.authority.load_checkpoint,
acquire_resume_lease=self.authority.acquire_resume_lease,
persist_checkpoint=self.authority.persist_checkpoint,
persist_owner_action_required=self.authority.persist_owner_action_required,
release_resume_lease=self.authority.release_resume_lease,
reconcile=self.authority.reconcile_effects,
),
PhaseCallbacks(
admission=self.admission,
source_execution=self.source_execution,
gate0=self.gate0,
release_ready=self.release_ready,
promotion=self.promotion,
final_acceptance=self.final_acceptance,
),
)
def read_observation(self, mission_id: str) -> MissionObservation:
if mission_id != self.config.mission_id:
raise ValueError("mission_id_mismatch")
generation = self.authority.read_generation(mission_id)
head_oid = self.authority.read_head_oid(self.config.repository)
material = self.authority.read_material_digest(mission_id)
effect = self.authority.read_effect_view(mission_id)
fingerprint = _digest({
"schema": "aios.unattended-condition.v1",
"mission_id": mission_id,
"generation": generation,
"head_oid": head_oid,
"material_digest": material,
"effect_state": effect.effect_state.value,
"effect_condition": effect.condition_fingerprint,
"requires_reconcile": effect.requires_reconcile,
})
return MissionObservation(
mission_id=mission_id,
generation=generation,
head_oid=head_oid,
material_digest=material,
condition_fingerprint=fingerprint,
effect_state=effect.effect_state,
requires_reconcile=effect.requires_reconcile,
owner_action_resolved=self.authority.owner_action_resolved(mission_id),
evidence_refs=effect.receipt_refs,
)
def admission(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
grant = self.authority.read_durable_grant(observation.mission_id)
grant_refs = tuple(grant.evidence_refs)
if not grant.active:
return StepResult(
StepStatus.BLOCKED_OWNER if grant.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
"DURABLE_GRANT_NOT_ACTIVE",
self._phase_fingerprint(
"admission", observation, {"grant_id": grant.grant_id, "active": False}
),
receipt_refs=grant_refs,
owner_action_payload=(
{
"boundary": "DURABLE_GRANT_NOT_ACTIVE",
"grant_id": grant.grant_id,
}
if grant.owner_action_required else None
),
)
for lane in self.config.lanes:
if not grant.permits(
repository=lane.repository,
branch=lane.target_branch,
risk=lane.risk,
):
return StepResult(
StepStatus.BLOCKED_OWNER if grant.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
"DURABLE_GRANT_SCOPE_MISMATCH",
self._phase_fingerprint(
"admission",
observation,
{
"grant_id": grant.grant_id,
"lane": lane.lane,
"branch": lane.target_branch,
"risk": lane.risk,
},
),
receipt_refs=grant_refs,
owner_action_payload=(
{
"boundary": "DURABLE_GRANT_SCOPE_MISMATCH",
"grant_id": grant.grant_id,
"lane": lane.lane,
}
if grant.owner_action_required else None
),
)
worker = self.authority.ensure_canonical_worker(
observation.mission_id,
observation.generation,
self._idempotency(
"worker",
observation.mission_id,
observation.generation,
),
)
worker_refs = self._refs(grant_refs, (worker.receipt_ref or "",))
if worker.state is WorkerState.UNKNOWN:
return StepResult(
StepStatus.RECONCILE,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
)
if worker.state is WorkerState.BLOCKED:
return StepResult(
StepStatus.BLOCKED_OWNER if worker.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
owner_action_payload=(
{"boundary": worker.code}
if worker.owner_action_required else None
),
)
if worker.state in {WorkerState.STARTING, WorkerState.INACTIVE}:
return StepResult(
StepStatus.IN_PROGRESS,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
retry_after_seconds=worker.retry_after_seconds,
)
if worker.state is WorkerState.ACTIVE and not worker.receipt_ref:
return StepResult(
StepStatus.RECONCILE,
"WORKER_ACTIVE_WITHOUT_RECEIPT",
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
)
task_refs: list[str] = list(worker_refs)
admissions: dict[str, TaskAdmissionReceipt] = {}
for lane in self.config.lanes:
request = TaskAdmissionRequest(
task_id=lane.task_id,
idempotency_key=self._idempotency(
"task",
observation.generation,
lane.task_id,
observation.head_oid,
),
repository=lane.repository,
base_oid=observation.head_oid,
target_branch=lane.target_branch,
expected_branch_oid=observation.head_oid,
risk=lane.risk,
description=lane.description,
)
receipt = self.authority.create_or_resume_task(request)
task_refs.append(receipt.receipt_ref)
receipt_error = self._admission_receipt_error(
lane,
receipt,
observation=observation,
)
if receipt_error is not None:
return StepResult(
StepStatus.RECONCILE,
receipt_error,
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
if not receipt.persisted:
return StepResult(
StepStatus.RECONCILE,
"TASK_PERSISTENCE_UNKNOWN",
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
if not receipt.claimable:
return StepResult(
StepStatus.IN_PROGRESS,
"TASK_NOT_CLAIMABLE_YET",
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
admissions[lane.lane] = receipt
self._admissions = admissions
return StepResult(
StepStatus.SUCCEEDED,
"GOVERNED_TASKS_ADMITTED",
self._phase_fingerprint(
"admission",
observation,
{"tasks": sorted(r.task_id for r in admissions.values())},
),
receipt_refs=tuple(task_refs),
next_phase=MissionPhase.S2_EXECUTION,
)
def source_execution(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
recovery_refs: list[str] = []
for lane in self.config.lanes:
if lane.lane in self._admissions:
continue
receipt = self.authority.read_task_admission(lane)
if receipt is None:
return StepResult(
StepStatus.RECONCILE,
"TASK_ADMISSION_NOT_RESOLVED",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
recovery_refs.append(receipt.receipt_ref)
receipt_error = self._admission_receipt_error(
lane,
receipt,
observation=observation,
)
if receipt_error is not None:
return StepResult(
StepStatus.RECONCILE,
receipt_error,
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
if not receipt.persisted:
return StepResult(
StepStatus.RECONCILE,
"TASK_PERSISTENCE_UNKNOWN",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
if not receipt.claimable:
return StepResult(
StepStatus.IN_PROGRESS,
"TASK_NOT_CLAIMABLE_YET",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
self._admissions[lane.lane] = receipt
views: list[LaneExecutionView] = []
refs: list[str] = list(recovery_refs)
for lane in self.config.lanes:
view = self.authority.read_lane_execution(
lane,
self._admissions[lane.lane],
)
if view.lane != lane.lane:
return StepResult(
StepStatus.RECONCILE,
"LANE_IDENTITY_MISMATCH",
self._phase_fingerprint(
"source",
observation,
{"expected": lane.lane, "actual": view.lane},
),
receipt_refs=tuple(refs),
)
if view.state is WorkState.COMPLETE and not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"LANE_COMPLETE_WITHOUT_RECEIPT",
view.condition_fingerprint,
receipt_refs=tuple(refs),
)
views.append(view)
refs.extend(view.receipt_refs)
unknown = [v for v in views if v.state is WorkState.UNKNOWN]
if unknown:
return StepResult(
StepStatus.RECONCILE,
unknown[0].code,
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
)
blocked = [v for v in views if v.state is WorkState.BLOCKED]
if blocked:
return StepResult(
StepStatus.BLOCKED_DETERMINISTIC,
blocked[0].code,
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
)
pending = [
v for v in views
if v.state in {WorkState.PENDING, WorkState.RUNNING}
]
if pending:
retry_values = [
v.retry_after_seconds
for v in pending
if v.retry_after_seconds is not None
]
return StepResult(
StepStatus.IN_PROGRESS,
"SOURCE_LANES_IN_PROGRESS",
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
retry_after_seconds=min(retry_values) if retry_values else None,
)
integration = self.authority.drive_integration(
self.config.lanes,
tuple(self._admissions[lane.lane] for lane in self.config.lanes),
)
refs.extend(integration.receipt_refs)
if integration.state is WorkState.UNKNOWN:
return StepResult(
StepStatus.RECONCILE,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
if integration.state is WorkState.BLOCKED:
return StepResult(
StepStatus.BLOCKED_DETERMINISTIC,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
if integration.state in {WorkState.PENDING, WorkState.RUNNING}:
return StepResult(
StepStatus.IN_PROGRESS,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
retry_after_seconds=integration.retry_after_seconds,
)
if integration.state is WorkState.COMPLETE and not integration.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"INTEGRATION_COMPLETE_WITHOUT_RECEIPT",
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
return StepResult(
StepStatus.SUCCEEDED,
"SOURCE_INTEGRATION_COMPLETE",
integration.condition_fingerprint,
receipt_refs=tuple(refs),
next_phase=MissionPhase.GATE0,
)
def gate0(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_gate0(observation.mission_id)
if view.state is GateState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is GateState.HOLD:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"GATE0_PASS_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.RELEASE_READY
if status is StepStatus.SUCCEEDED else None
),
)
def release_ready(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.build_release_ready(observation.mission_id)
if view.state is ReadyState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is ReadyState.NOT_READY:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
if not view.material_digest:
return StepResult(
StepStatus.RECONCILE,
"READY_WITHOUT_MATERIAL_DIGEST",
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
)
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"READY_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.PROMOTION
if status is StepStatus.SUCCEEDED else None
),
material_digest=view.material_digest,
)
def promotion(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_promotion(observation.mission_id)
if view.state is PromotionState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is PromotionState.IN_PROGRESS:
status = StepStatus.IN_PROGRESS
elif view.state is PromotionState.VERIFIED:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"PROMOTION_VERIFIED_WITHOUT_RECEIPT",
view.condition_fingerprint,
material_digest=view.material_digest,
)
status = StepStatus.SUCCEEDED
elif view.state is PromotionState.ROLLED_BACK:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
status = StepStatus.BLOCKED_DETERMINISTIC
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.FINAL_ACCEPTANCE
if status is StepStatus.SUCCEEDED else None
),
material_digest=view.material_digest,
)
def final_acceptance(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_final_acceptance(observation.mission_id)
if view.state is AcceptanceState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is AcceptanceState.PASS:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"FINAL_ACCEPTANCE_PASS_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
else:
status = StepStatus.BLOCKED_DETERMINISTIC
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
next_phase=(
MissionPhase.COMPLETE
if status is StepStatus.SUCCEEDED else None
),
)
@staticmethod
def _admission_receipt_error(
lane: TaskLaneSpec,
receipt: TaskAdmissionReceipt,
*,
observation: MissionObservation,
) -> str | None:
if receipt.task_id != lane.task_id:
return "TASK_IDENTITY_MISMATCH"
if receipt.target_branch != lane.target_branch:
return "TASK_BRANCH_MISMATCH"
if receipt.generation != observation.generation:
return "TASK_GENERATION_MISMATCH"
return None
@staticmethod
def _refs(*groups: Sequence[str]) -> tuple[str, ...]:
seen: set[str] = set()
values: list[str] = []
for group in groups:
for item in group:
if item and item not in seen:
seen.add(item)
values.append(item)
return tuple(values)
@staticmethod
def _idempotency(*parts: object) -> str:
return "aios:auto:" + _digest(
{"schema": "aios.unattended-idempotency.v1", "parts": list(parts)}
)
@staticmethod
def _phase_fingerprint(
phase: str,
observation: MissionObservation,
details: Mapping[str, object],
) -> str:
return _digest({
"schema": "aios.unattended-phase-condition.v1",
"phase": phase,
"mission_id": observation.mission_id,
"generation": observation.generation,
"head_oid": observation.head_oid,
"material_digest": observation.material_digest,
"details": details,
})
@@ -1,877 +0,0 @@
"""Deterministic unattended mission-resume controller for AIOS.
The controller is deliberately small and does not create a second scheduler,
database, identity system, or release control plane.
It executes exactly one reconciliation/progress cycle per ``run_once`` call.
An existing AIOS scheduler/timer/worker loop should invoke it periodically.
All consequential effects are delegated through ``AutonomousExecutionPort`` and
must use the existing AIOS authorities: durable Owner authorization, task
admission, claim/TaskLease/TaskPrincipal, WriterLease/CAS/fencing, Gate0,
ReleaseReady, PromotionController and governed rollback.
The controller's responsibilities are limited to:
- one-winner resume lease acquisition;
- durable checkpoint progression;
- bounded retry/backoff;
- unchanged deterministic blocker suppression;
- UNKNOWN/ambiguous-effect reconciliation before retry;
- non-interactive owner-boundary recording;
- crash-safe phase resumption.
It never manufactures credentials, group membership, principals, leases,
signatures, branch OIDs, or release receipts.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field, replace
from enum import Enum
import hashlib
import json
import time
from typing import Any, Callable, Mapping, Protocol, Sequence
class MissionPhase(str, Enum):
S2_ADMISSION = "S2_ADMISSION"
S2_EXECUTION = "S2_EXECUTION"
GATE0 = "GATE0"
RELEASE_READY = "RELEASE_READY"
PROMOTION = "PROMOTION"
FINAL_ACCEPTANCE = "FINAL_ACCEPTANCE"
OWNER_ACTION_REQUIRED = "OWNER_ACTION_REQUIRED"
COMPLETE = "COMPLETE"
class EffectState(str, Enum):
ABSENT = "ABSENT"
ACTIVE = "ACTIVE"
COMPLETED = "COMPLETED"
UNKNOWN = "UNKNOWN"
class StepStatus(str, Enum):
SUCCEEDED = "SUCCEEDED"
IN_PROGRESS = "IN_PROGRESS"
RETRYABLE = "RETRYABLE"
RECONCILE = "RECONCILE"
BLOCKED_DETERMINISTIC = "BLOCKED_DETERMINISTIC"
BLOCKED_OWNER = "BLOCKED_OWNER"
class OutcomeStatus(str, Enum):
COMPLETE = "COMPLETE"
PROGRESSED = "PROGRESSED"
IN_PROGRESS = "IN_PROGRESS"
RETRY_SCHEDULED = "RETRY_SCHEDULED"
WAIT_CONDITION_CHANGE = "WAIT_CONDITION_CHANGE"
ACTIVE_ELSEWHERE = "ACTIVE_ELSEWHERE"
OWNER_ACTION_REQUIRED = "OWNER_ACTION_REQUIRED"
_PHASE_NEXT: dict[MissionPhase, MissionPhase] = {
MissionPhase.S2_ADMISSION: MissionPhase.S2_EXECUTION,
MissionPhase.S2_EXECUTION: MissionPhase.GATE0,
MissionPhase.GATE0: MissionPhase.RELEASE_READY,
MissionPhase.RELEASE_READY: MissionPhase.PROMOTION,
MissionPhase.PROMOTION: MissionPhase.FINAL_ACCEPTANCE,
MissionPhase.FINAL_ACCEPTANCE: MissionPhase.COMPLETE,
}
def _canonical(value: Any) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _sha256(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
@dataclass(frozen=True)
class ResumePolicy:
resume_lease_seconds: int = 120
default_poll_seconds: int = 30
deterministic_block_poll_seconds: int = 300
transient_max_attempts: int = 3
transient_base_delay_seconds: int = 2
transient_max_delay_seconds: int = 60
def __post_init__(self) -> None:
for name in (
"resume_lease_seconds",
"default_poll_seconds",
"deterministic_block_poll_seconds",
"transient_max_attempts",
"transient_base_delay_seconds",
"transient_max_delay_seconds",
):
if getattr(self, name) < 1:
raise ValueError(f"{name}_must_be_positive")
if self.transient_max_delay_seconds < self.transient_base_delay_seconds:
raise ValueError("transient_max_delay_seconds_too_small")
@dataclass(frozen=True)
class MissionObservation:
mission_id: str
generation: int
head_oid: str
material_digest: str
condition_fingerprint: str
effect_state: EffectState = EffectState.ABSENT
requires_reconcile: bool = False
owner_action_resolved: bool = False
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if not self.head_oid:
raise ValueError("head_oid_required")
if not self.material_digest:
raise ValueError("material_digest_required")
if not self.condition_fingerprint:
raise ValueError("condition_fingerprint_required")
@dataclass(frozen=True)
class ResumeLease:
lease_id: str
mission_id: str
generation: int
fencing_token: str
expires_at: float
def __post_init__(self) -> None:
if not self.lease_id:
raise ValueError("lease_id_required")
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if not self.fencing_token:
raise ValueError("fencing_token_required")
@dataclass(frozen=True)
class StepResult:
status: StepStatus
code: str
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
next_phase: MissionPhase | None = None
material_digest: str | None = None
owner_action_payload: Mapping[str, Any] | None = None
def __post_init__(self) -> None:
if not self.code:
raise ValueError("step_code_required")
if not self.condition_fingerprint:
raise ValueError("condition_fingerprint_required")
if self.retry_after_seconds is not None and self.retry_after_seconds < 0:
raise ValueError("retry_after_seconds_must_be_non_negative")
@dataclass(frozen=True)
class MissionCheckpoint:
schema: str
mission_id: str
generation: int
phase: MissionPhase
head_oid: str
material_digest: str
last_condition_fingerprint: str
attempt_count: int
blocked_condition_fingerprint: str | None
reconcile_required: bool
frozen_material_digest: str | None
resume_phase_after_owner: MissionPhase | None
receipt_refs: tuple[str, ...]
updated_at: float
revision: int = 1
@classmethod
def initial(
cls,
observation: MissionObservation,
*,
observed_at: float,
) -> "MissionCheckpoint":
return cls(
schema="aios.autonomous-resume-checkpoint.v1",
mission_id=observation.mission_id,
generation=observation.generation,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
last_condition_fingerprint=observation.condition_fingerprint,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=None,
resume_phase_after_owner=None,
receipt_refs=tuple(observation.evidence_refs),
updated_at=observed_at,
revision=1,
)
def __post_init__(self) -> None:
if self.schema != "aios.autonomous-resume-checkpoint.v1":
raise ValueError("unsupported_checkpoint_schema")
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if self.attempt_count < 0:
raise ValueError("attempt_count_must_be_non_negative")
if self.revision < 1:
raise ValueError("revision_must_be_positive")
@property
def digest(self) -> str:
payload = asdict(self)
payload["phase"] = self.phase.value
payload["resume_phase_after_owner"] = (
None
if self.resume_phase_after_owner is None
else self.resume_phase_after_owner.value
)
payload["receipt_refs"] = list(self.receipt_refs)
return _sha256(_canonical(payload))
@dataclass(frozen=True)
class ControllerOutcome:
status: OutcomeStatus
mission_id: str
phase: MissionPhase
code: str
next_wake_after_seconds: int | None
checkpoint_digest: str | None
receipt_refs: tuple[str, ...] = ()
class AutonomousExecutionPort(Protocol):
"""Adapter boundary to existing AIOS authorities and executors."""
def read_observation(self, mission_id: str) -> MissionObservation: ...
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None: ...
def acquire_resume_lease(
self,
*,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None: ...
def step(
self,
*,
phase: MissionPhase,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
def reconcile(
self,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
*,
lease: ResumeLease,
) -> None: ...
def persist_owner_action_required(
self,
payload: Mapping[str, Any],
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str: ...
def release_resume_lease(
self,
lease: ResumeLease,
*,
outcome_code: str,
) -> None: ...
class AutonomousResumeController:
"""One-cycle deterministic unattended mission controller."""
def __init__(
self,
port: AutonomousExecutionPort,
*,
policy: ResumePolicy | None = None,
clock: Callable[[], float] | None = None,
) -> None:
self.port = port
self.policy = policy or ResumePolicy()
self.clock = clock or time.time
def run_once(self, mission_id: str) -> ControllerOutcome:
if not mission_id:
raise ValueError("mission_id_required")
now = float(self.clock())
observation = self.port.read_observation(mission_id)
checkpoint = self.port.load_checkpoint(mission_id)
if checkpoint is None:
checkpoint = MissionCheckpoint.initial(observation, observed_at=now)
forced_reconcile = False
else:
checkpoint, forced_reconcile = self._validate_and_refresh_checkpoint(
checkpoint,
observation,
now=now,
)
if checkpoint.phase is MissionPhase.COMPLETE:
return self._outcome(
OutcomeStatus.COMPLETE,
checkpoint,
"MISSION_ALREADY_COMPLETE",
next_wake=None,
)
if checkpoint.phase is MissionPhase.OWNER_ACTION_REQUIRED:
if not observation.owner_action_resolved:
return self._outcome(
OutcomeStatus.OWNER_ACTION_REQUIRED,
checkpoint,
"OWNER_ACTION_STILL_REQUIRED",
next_wake=None,
)
resume_phase = (
checkpoint.resume_phase_after_owner
or MissionPhase.S2_ADMISSION
)
checkpoint = replace(
checkpoint,
phase=resume_phase,
resume_phase_after_owner=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
checkpoint.blocked_condition_fingerprint is not None
and checkpoint.blocked_condition_fingerprint
== observation.condition_fingerprint
):
return self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
checkpoint,
"UNCHANGED_DETERMINISTIC_BLOCKER",
next_wake=self.policy.deterministic_block_poll_seconds,
)
if (
checkpoint.blocked_condition_fingerprint is not None
and checkpoint.blocked_condition_fingerprint
!= observation.condition_fingerprint
):
checkpoint = replace(
checkpoint,
blocked_condition_fingerprint=None,
attempt_count=0,
updated_at=now,
revision=checkpoint.revision + 1,
)
lease = self.port.acquire_resume_lease(
mission_id=observation.mission_id,
generation=observation.generation,
idempotency_key=self._resume_idempotency_key(observation),
lease_seconds=self.policy.resume_lease_seconds,
)
if lease is None:
return self._outcome(
OutcomeStatus.ACTIVE_ELSEWHERE,
checkpoint,
"RESUME_LEASE_HELD_ELSEWHERE",
next_wake=self.policy.default_poll_seconds,
)
try:
self._validate_resume_lease(
lease,
observation=observation,
now=now,
)
except Exception:
self.port.release_resume_lease(
lease,
outcome_code="INVALID_RESUME_LEASE",
)
raise
outcome_code = "UNSET"
try:
reconciling = (
forced_reconcile
or checkpoint.reconcile_required
or observation.requires_reconcile
or observation.effect_state is EffectState.UNKNOWN
)
if reconciling:
result = self.port.reconcile(
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
else:
result = self.port.step(
phase=checkpoint.phase,
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
outcome, new_checkpoint = self._apply_result(
result=result,
observation=observation,
checkpoint=checkpoint,
now=now,
lease=lease,
reconciling=reconciling,
)
outcome_code = outcome.code
self.port.persist_checkpoint(new_checkpoint, lease=lease)
return replace(
outcome,
checkpoint_digest=new_checkpoint.digest,
)
finally:
self.port.release_resume_lease(
lease,
outcome_code=outcome_code,
)
def _validate_and_refresh_checkpoint(
self,
checkpoint: MissionCheckpoint,
observation: MissionObservation,
*,
now: float,
) -> tuple[MissionCheckpoint, bool]:
if checkpoint.mission_id != observation.mission_id:
raise ValueError("checkpoint_mission_mismatch")
if checkpoint.generation > observation.generation:
raise ValueError("checkpoint_generation_ahead_of_authority")
refreshed = checkpoint
forced_reconcile = False
if checkpoint.generation < observation.generation:
refreshed = replace(
checkpoint,
generation=observation.generation,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
last_condition_fingerprint=observation.condition_fingerprint,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=None,
resume_phase_after_owner=None,
receipt_refs=self._merge_refs(
checkpoint.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
refreshed.phase in {
MissionPhase.RELEASE_READY,
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and refreshed.frozen_material_digest is not None
and refreshed.frozen_material_digest
!= observation.material_digest
):
# Material changed after READY. Promotion/final may already have
# observable side effects, so reconcile before rebuilding PREPARE.
previous_phase = refreshed.phase
force_for_effect = (
previous_phase in {
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and observation.effect_state in {
EffectState.ACTIVE,
EffectState.UNKNOWN,
}
)
refreshed = replace(
refreshed,
phase=MissionPhase.RELEASE_READY,
material_digest=observation.material_digest,
head_oid=observation.head_oid,
frozen_material_digest=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=force_for_effect,
last_condition_fingerprint=observation.condition_fingerprint,
receipt_refs=self._merge_refs(
refreshed.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=refreshed.revision + 1,
)
forced_reconcile = forced_reconcile or force_for_effect
elif (
refreshed.phase in {
MissionPhase.S2_ADMISSION,
MissionPhase.S2_EXECUTION,
MissionPhase.GATE0,
}
and refreshed.head_oid != observation.head_oid
):
# Source moved before READY. Re-enter S2 admission/replan.
refreshed = replace(
refreshed,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
frozen_material_digest=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
last_condition_fingerprint=observation.condition_fingerprint,
receipt_refs=self._merge_refs(
refreshed.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=refreshed.revision + 1,
)
return refreshed, forced_reconcile
def _apply_result(
self,
*,
result: StepResult,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
now: float,
lease: ResumeLease,
reconciling: bool,
) -> tuple[ControllerOutcome, MissionCheckpoint]:
refs = self._merge_refs(
checkpoint.receipt_refs,
observation.evidence_refs,
result.receipt_refs,
)
base = replace(
checkpoint,
head_oid=observation.head_oid,
material_digest=(
result.material_digest
or observation.material_digest
),
last_condition_fingerprint=result.condition_fingerprint,
receipt_refs=refs,
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
result.status is StepStatus.SUCCEEDED
and checkpoint.phase in {
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and checkpoint.frozen_material_digest is not None
and result.material_digest is not None
and result.material_digest != checkpoint.frozen_material_digest
):
new_checkpoint = replace(
base,
phase=MissionPhase.RELEASE_READY,
material_digest=result.material_digest,
frozen_material_digest=None,
reconcile_required=True,
attempt_count=0,
blocked_condition_fingerprint=None,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
"RECONCILE_REQUIRED:MATERIAL_CHANGED_DURING_PROMOTION",
next_wake=0,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.SUCCEEDED:
next_phase = (
result.next_phase
or _PHASE_NEXT.get(checkpoint.phase, checkpoint.phase)
)
frozen = base.frozen_material_digest
if checkpoint.phase is MissionPhase.RELEASE_READY:
frozen = result.material_digest or observation.material_digest
new_checkpoint = replace(
base,
phase=next_phase,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=frozen,
)
status = (
OutcomeStatus.COMPLETE
if next_phase is MissionPhase.COMPLETE
else OutcomeStatus.PROGRESSED
)
code = (
"MISSION_COMPLETE"
if next_phase is MissionPhase.COMPLETE
else result.code
)
return (
self._outcome(
status,
new_checkpoint,
code,
next_wake=(
None
if status is OutcomeStatus.COMPLETE
else self.policy.default_poll_seconds
),
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.IN_PROGRESS:
new_checkpoint = replace(
base,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
result.code,
next_wake=(
result.retry_after_seconds
if result.retry_after_seconds is not None
else self.policy.default_poll_seconds
),
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.RETRYABLE:
attempts = checkpoint.attempt_count + 1
if attempts > self.policy.transient_max_attempts:
new_checkpoint = replace(
base,
attempt_count=attempts,
blocked_condition_fingerprint=result.condition_fingerprint,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
new_checkpoint,
"RETRY_BUDGET_EXHAUSTED",
next_wake=self.policy.deterministic_block_poll_seconds,
refs=result.receipt_refs,
),
new_checkpoint,
)
delay = (
result.retry_after_seconds
if result.retry_after_seconds is not None
else min(
self.policy.transient_base_delay_seconds
* (2 ** (attempts - 1)),
self.policy.transient_max_delay_seconds,
)
)
new_checkpoint = replace(
base,
attempt_count=attempts,
blocked_condition_fingerprint=None,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.RETRY_SCHEDULED,
new_checkpoint,
result.code,
next_wake=delay,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.BLOCKED_DETERMINISTIC:
new_checkpoint = replace(
base,
attempt_count=checkpoint.attempt_count,
blocked_condition_fingerprint=result.condition_fingerprint,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
new_checkpoint,
result.code,
next_wake=self.policy.deterministic_block_poll_seconds,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.BLOCKED_OWNER:
payload = dict(result.owner_action_payload or {})
payload.setdefault("mission_id", observation.mission_id)
payload.setdefault("generation", observation.generation)
payload.setdefault("phase", checkpoint.phase.value)
payload.setdefault("code", result.code)
record_ref = self.port.persist_owner_action_required(
payload,
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
if not record_ref:
raise ValueError("owner_action_record_ref_required")
new_checkpoint = replace(
base,
phase=MissionPhase.OWNER_ACTION_REQUIRED,
resume_phase_after_owner=checkpoint.phase,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
receipt_refs=self._merge_refs(refs, (record_ref,)),
)
return (
self._outcome(
OutcomeStatus.OWNER_ACTION_REQUIRED,
new_checkpoint,
result.code,
next_wake=None,
refs=(record_ref,),
),
new_checkpoint,
)
if result.status is StepStatus.RECONCILE:
# The next scheduler cycle must reconcile before any retry.
new_checkpoint = replace(
base,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=True,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
"RECONCILE_REQUIRED:" + result.code,
next_wake=0,
refs=result.receipt_refs,
),
new_checkpoint,
)
raise AssertionError(f"unsupported_step_status:{result.status}")
@staticmethod
def _validate_resume_lease(
lease: ResumeLease,
*,
observation: MissionObservation,
now: float,
) -> None:
if lease.mission_id != observation.mission_id:
raise ValueError("resume_lease_mission_mismatch")
if lease.generation != observation.generation:
raise ValueError("resume_lease_generation_mismatch")
if lease.expires_at <= now:
raise ValueError("resume_lease_expired")
def _resume_idempotency_key(
self,
observation: MissionObservation,
) -> str:
payload = {
"schema": "aios.resume-effect.v1",
"mission_id": observation.mission_id,
"generation": observation.generation,
}
return "resume:sha256:" + _sha256(_canonical(payload))
@staticmethod
def _merge_refs(*groups: Sequence[str]) -> tuple[str, ...]:
merged: list[str] = []
seen: set[str] = set()
for group in groups:
for item in group:
value = str(item)
if value and value not in seen:
seen.add(value)
merged.append(value)
return tuple(merged)
@staticmethod
def _outcome(
status: OutcomeStatus,
checkpoint: MissionCheckpoint,
code: str,
*,
next_wake: int | None,
refs: Sequence[str] = (),
) -> ControllerOutcome:
return ControllerOutcome(
status=status,
mission_id=checkpoint.mission_id,
phase=checkpoint.phase,
code=code,
next_wake_after_seconds=next_wake,
checkpoint_digest=None,
receipt_refs=tuple(refs),
)
@@ -1,209 +0,0 @@
"""Thin callable adapter for :mod:`autonomous_resume_controller`.
This module contains no transport, daemon, database or scheduler loop. It only
binds current AIOS runtime functions to the stable ``AutonomousExecutionPort``
contract and exposes one scheduler tick.
A field integrator should:
- resolve the live current APIs;
- supply callables that use existing governed authorities;
- register ``AutonomousSchedulerTick.run`` with the existing scheduler/timer.
No additional control plane is created here.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from .autonomous_resume_controller import (
AutonomousExecutionPort,
AutonomousResumeController,
ControllerOutcome,
MissionCheckpoint,
MissionObservation,
MissionPhase,
ResumeLease,
StepResult,
)
PhaseHandler = Callable[
[MissionObservation, MissionCheckpoint, ResumeLease],
StepResult,
]
ReconcileHandler = PhaseHandler
@dataclass(frozen=True)
class PhaseCallbacks:
admission: PhaseHandler
source_execution: PhaseHandler
gate0: PhaseHandler
release_ready: PhaseHandler
promotion: PhaseHandler
final_acceptance: PhaseHandler
def for_phase(self, phase: MissionPhase) -> PhaseHandler:
mapping = {
MissionPhase.S2_ADMISSION: self.admission,
MissionPhase.S2_EXECUTION: self.source_execution,
MissionPhase.GATE0: self.gate0,
MissionPhase.RELEASE_READY: self.release_ready,
MissionPhase.PROMOTION: self.promotion,
MissionPhase.FINAL_ACCEPTANCE: self.final_acceptance,
}
try:
return mapping[phase]
except KeyError as exc:
raise ValueError(f"phase_has_no_executor:{phase.value}") from exc
@dataclass(frozen=True)
class AuthorityCallbacks:
read_observation: Callable[[str], MissionObservation]
load_checkpoint: Callable[[str], MissionCheckpoint | None]
acquire_resume_lease: Callable[[str, int, str, int], ResumeLease | None]
persist_checkpoint: Callable[[MissionCheckpoint, ResumeLease], None]
persist_owner_action_required: Callable[
[dict, MissionObservation, MissionCheckpoint, ResumeLease],
str,
]
release_resume_lease: Callable[[ResumeLease, str], None]
reconcile: ReconcileHandler
class CallableAutonomousExecutionPort(AutonomousExecutionPort):
"""Runtime adapter composed from already-governed AIOS callables."""
def __init__(
self,
authority: AuthorityCallbacks,
phases: PhaseCallbacks,
) -> None:
self.authority = authority
self.phases = phases
def read_observation(self, mission_id: str) -> MissionObservation:
value = self.authority.read_observation(mission_id)
if not isinstance(value, MissionObservation):
raise TypeError("read_observation_must_return_MissionObservation")
return value
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None:
value = self.authority.load_checkpoint(mission_id)
if value is not None and not isinstance(value, MissionCheckpoint):
raise TypeError("load_checkpoint_must_return_checkpoint_or_none")
return value
def acquire_resume_lease(
self,
*,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None:
value = self.authority.acquire_resume_lease(
mission_id,
generation,
idempotency_key,
lease_seconds,
)
if value is not None and not isinstance(value, ResumeLease):
raise TypeError("acquire_resume_lease_must_return_lease_or_none")
return value
def step(
self,
*,
phase: MissionPhase,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
handler = self.phases.for_phase(phase)
value = handler(observation, checkpoint, lease)
if not isinstance(value, StepResult):
raise TypeError("phase_handler_must_return_StepResult")
return value
def reconcile(
self,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
value = self.authority.reconcile(
observation,
checkpoint,
lease,
)
if not isinstance(value, StepResult):
raise TypeError("reconcile_must_return_StepResult")
return value
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
*,
lease: ResumeLease,
) -> None:
self.authority.persist_checkpoint(checkpoint, lease)
def persist_owner_action_required(
self,
payload,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str:
value = self.authority.persist_owner_action_required(
dict(payload),
observation,
checkpoint,
lease,
)
if not isinstance(value, str) or not value:
raise ValueError("owner_action_record_ref_required")
return value
def release_resume_lease(
self,
lease: ResumeLease,
*,
outcome_code: str,
) -> None:
self.authority.release_resume_lease(lease, outcome_code)
@dataclass(frozen=True)
class SchedulerTickResult:
outcome: ControllerOutcome
next_wake_after_seconds: int | None
class AutonomousSchedulerTick:
"""One invocation for an existing scheduler/timer.
This object never sleeps or loops. The host scheduler owns cadence.
"""
def __init__(
self,
controller: AutonomousResumeController,
mission_id: str,
) -> None:
if not mission_id:
raise ValueError("mission_id_required")
self.controller = controller
self.mission_id = mission_id
def run(self) -> SchedulerTickResult:
outcome = self.controller.run_once(self.mission_id)
return SchedulerTickResult(
outcome=outcome,
next_wake_after_seconds=outcome.next_wake_after_seconds,
)
@@ -1,401 +0,0 @@
"""现场(field)组合根:让"Owner 基于授权"真正可接线、可执行。
本模块是 V2 governed-writer 机制的**现场装配层**,职责:
- 实现正式的 `OwnerAuthorizationAuthority`:持有 Owner 登记的授权记录
(authorization_ref -> OwnerAuthorizationPolicy),`verify()` 解析引用、
核验策略、签发 verification receipt;未知引用 / 策略失效一律 fail-closed。
- 提供 `FieldGovernedWriterFactory`(组合根):把 7 个协议适配器(signer /
execution / principal_issuer / jobs / git / writer_lease / owner_authority)
装配到 `GovernedWriterAutomation.acquire_verified()` 生产入口上。
- 提供默认的生产适配器参考实现(`_DefaultOwnerAuthority`),供现场在真实
端点未就绪时先闭环;真实端点就绪后替换对应 adapter 即可,组合根接口不变。
设计原则(与 OWNER_AUTHORIZATION_MODEL.md 的 `aios.owner-authorization.v1` 一致):
1. 生产入口唯一授权来源是 Owner 登记的 `authorization_ref`;Agent 本地自造的
`OwnerAuthorizationPolicy` 不构成 proof(fail-closed)。
2. 长期业务授权(Policy)与短时执行凭证(epoch/lease/principal)分离。
3. `legacy_global_writes_enabled=false` 是安全不变式,不是 governed-writer blocker。
本模块不修改 S6 frozen 9 文件,不触碰 legacy/global 写;只做 governed 写路径的
现场装配。适配器接口全部复用 `governed_writer_automation.py` 的 Protocol。
"""
from __future__ import annotations
import time
from dataclasses import dataclass, field
from typing import Any, Mapping
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
EpochStatus,
GovernedWriterAdapters,
GovernedWriterAutomation,
GovernedWriterRequest,
GovernedWriterSession,
GovernedWriteReadiness,
JobBindingReceipt,
OwnerAuthorizationPolicy,
RecoveryAuthorization,
TaskLeaseReceipt,
TaskPrincipalReceipt,
TaskState,
VerifiedOwnerAuthorization,
WriterLeaseReceipt,
)
# ---------------------------------------------------------------------------
# 正式 OwnerAuthorizationAuthority 现场实现
# ---------------------------------------------------------------------------
@dataclass
class FieldOwnerAuthorizationAuthority:
"""现场 authority:登记 Owner 授权记录并按引用验证,签发核验回执。
生产语义:
- `register(ref, policy)` 由 Owner/管理员在受信侧登记;登记即"一次授权"。
- `verify(authorization_ref, request)` 解析引用 -> 核验 -> 返回
`VerifiedOwnerAuthorization`(含非空 receipt)。
- 任何未知引用、未登记、策略与请求越界,都抛 `AuthorizationDenied`
(fail-closed:不猜测、不降级)。
"""
_registry: dict[str, OwnerAuthorizationPolicy] = field(default_factory=dict)
_receipt_counter: int = 0
def register(self, authorization_ref: str, policy: OwnerAuthorizationPolicy) -> None:
if authorization_ref != policy.authorization_id:
raise AuthorizationDenied(
"authorization_ref must equal policy.authorization_id"
)
self._registry[authorization_ref] = policy
def contains(self, authorization_ref: str) -> bool:
return authorization_ref in self._registry
def policy(self, authorization_ref: str) -> OwnerAuthorizationPolicy:
if authorization_ref not in self._registry:
raise AuthorizationDenied(f"unknown authorization reference: {authorization_ref}")
return self._registry[authorization_ref]
def verify(
self,
*,
authorization_ref: str,
request: GovernedWriterRequest,
) -> VerifiedOwnerAuthorization:
if authorization_ref not in self._registry:
raise AuthorizationDenied(
f"owner authorization reference not registered: {authorization_ref}"
)
policy = self._registry[authorization_ref]
policy.validate(request, now=time.time())
self._receipt_counter += 1
receipt = f"receipt:owner-{authorization_ref}:{self._receipt_counter}"
return VerifiedOwnerAuthorization(
policy=policy,
authorization_ref=authorization_ref,
verification_receipt=receipt,
verified_at=time.time(),
)
# ---------------------------------------------------------------------------
# 默认生产适配器参考实现(真实端点未就绪时先闭环,接口与协议一一对应)
# ---------------------------------------------------------------------------
class _DefaultTrustedSigner:
"""TrustedSignerAdapter:JIT 重签同 scope 的 epoch(幂等由 ledger 守护)。"""
def __init__(self) -> None:
self._counter = 0
self.calls: list[dict] = []
def reissue_epoch(
self,
*,
owner_policy: OwnerAuthorizationPolicy,
request: GovernedWriterRequest,
prior_epoch: EpochStatus | None,
minimum_remaining_ttl: float,
idempotency_key: str,
) -> EpochStatus:
self._counter += 1
self.calls.append(
{
"key": idempotency_key,
"scope": request.scope_fingerprint,
"policy": owner_policy.policy_fingerprint,
"prior": None if prior_epoch is None else prior_epoch.epoch_id,
}
)
return EpochStatus(
epoch_id=f"epoch-gw-{self._counter}",
scope_fingerprint=request.scope_fingerprint,
expires_at=time.time() + 7200.0,
policy_digest=owner_policy.policy_fingerprint,
)
class _DefaultExecutionPlane:
"""ExecutionPlane:QUEUED 自动 claim + lease;heartbeat 记录。"""
def __init__(self, initial: TaskState) -> None:
self.task = initial
self._lease_counter = 0
self.claims: list = []
self.heartbeats: list = []
def read_task(self, task_id: str) -> TaskState:
if task_id != self.task.task_id:
raise AuthorizationDenied(f"unknown task: {task_id}")
return self.task
def apply_recovery(
self,
*,
task: TaskState,
authorization: RecoveryAuthorization,
idempotency_key: str,
) -> TaskState:
raise AuthorizationDenied("terminal recovery requires a real recovery endpoint")
def claim(
self,
*,
task: TaskState,
actor: str,
idempotency_key: str,
):
from app.task_plane.governed_writer_automation import ClaimReceipt
self.claims.append({"task": task.task_id, "actor": actor, "key": idempotency_key})
return ClaimReceipt(
claim_id=f"claim-gw-{len(self.claims)}",
task_id=task.task_id,
actor=actor,
fencing=len(self.claims),
)
def heartbeat(self, *, claim) -> None:
self.heartbeats.append(claim.claim_id)
def issue_task_lease(
self,
*,
claim,
idempotency_key: str,
) -> TaskLeaseReceipt:
self._lease_counter += 1
return TaskLeaseReceipt(
task_lease_id=f"tasklease-gw-{self._lease_counter}",
task_id=claim.task_id,
token_ref=f"token-gw-{self._lease_counter}",
expires_at=time.time() + 3600.0,
fencing=claim.fencing,
)
class _DefaultPrincipalIssuer:
"""TaskPrincipalIssuer:按 lease 派发 scoped TaskPrincipal。"""
def __init__(self) -> None:
self._counter = 0
def issue(
self,
*,
lease: TaskLeaseReceipt,
requested_capabilities: frozenset[str],
idempotency_key: str,
) -> TaskPrincipalReceipt:
self._counter += 1
return TaskPrincipalReceipt(
principal_id=f"principal-gw-{self._counter}",
task_id=lease.task_id,
capabilities=requested_capabilities,
expires_at=time.time() + 1800.0,
)
class _DefaultJobs:
"""JobAuthority:reconcile 到与请求一致的 job 绑定。"""
def __init__(self, *, repository: str, target_branch: str, generation: int) -> None:
self._repo = repository
self._branch = target_branch
self._gen = generation
self._counter = 0
def read_or_reconcile(
self,
*,
task_id: str,
principal: TaskPrincipalReceipt,
idempotency_key: str,
) -> JobBindingReceipt:
self._counter += 1
return JobBindingReceipt(
job_id=f"job-gw-{self._counter}",
task_id=task_id,
repository=self._repo,
target_branch=self._branch,
generation=self._gen,
)
class _DefaultGit:
"""GitAuthority:只读 branch 读回,决定 CAS 模式。"""
def __init__(self, *, exists: bool, head_oid: str | None = None) -> None:
from app.task_plane.governed_writer_automation import BranchReadback
self._exists = exists
self._head = head_oid
self.calls: list = []
def read_branch(self, *, repository: str, target_branch: str):
from app.task_plane.governed_writer_automation import BranchReadback
self.calls.append({"repo": repository, "branch": target_branch})
return BranchReadback(exists=self._exists, head_oid=self._head)
class _DefaultWriterLease:
"""WriterLeaseAuthority:签发短时 writer lease(CAS 与 base 一致)。"""
def __init__(self) -> None:
self._counter = 0
def acquire(
self,
*,
task_id: str,
job: JobBindingReceipt,
principal: TaskPrincipalReceipt,
repository: str,
target_branch: str,
branch_mode: str,
base_oid: str,
expected_branch_oid: str,
generation: int,
idempotency_key: str,
) -> WriterLeaseReceipt:
self._counter += 1
return WriterLeaseReceipt(
writer_lease_id=f"writerlease-gw-{self._counter}",
task_id=task_id,
job_id=job.job_id,
repository=repository,
target_branch=target_branch,
branch_mode=branch_mode,
expected_branch_oid=expected_branch_oid,
generation=generation,
expires_at=time.time() + 900.0,
fencing=self._counter,
)
# ---------------------------------------------------------------------------
# 组合根:装配 7 适配器 -> 生产入口
# ---------------------------------------------------------------------------
@dataclass
class FieldRuntimeStatus:
"""现场运行时状态:由 W-RUNTIME 只读线提供,用于派生 governed 写就绪。"""
legacy_global_writes_enabled: bool = False
task_principal_writes_enabled: bool = True
task_principal_verifier_ready: bool = True
governed_write_blockers: tuple[str, ...] = ()
@dataclass
class FieldGovernedWriterFactory:
"""组合根:把现场配置装配成 `acquire_verified()` 生产入口可直接执行的对象。
用法(现场接线):
factory = FieldGovernedWriterFactory.build(
authority=owner_authority,
manifest_store=...,
action_ledger=...,
task_initial=TaskState(...),
branch_exists=...,
head_oid=...,
repo=...,
branch=...,
generation=...,
runtime_status=...,
)
session = factory.acquire(request, authorization_ref)
"""
automation: GovernedWriterAutomation
adapters: GovernedWriterAdapters
readiness: GovernedWriteReadiness
owner_authority: FieldOwnerAuthorizationAuthority
@classmethod
def build(
cls,
*,
authority: FieldOwnerAuthorizationAuthority,
manifest_store,
action_ledger,
task_initial: TaskState,
branch_exists: bool,
head_oid: str | None,
repository: str,
target_branch: str,
generation: int,
runtime_status: FieldRuntimeStatus | None = None,
decision_engine=None,
) -> "FieldGovernedWriterFactory":
runtime_status = runtime_status or FieldRuntimeStatus()
adapters = GovernedWriterAdapters(
signer=_DefaultTrustedSigner(),
execution=_DefaultExecutionPlane(task_initial),
principal_issuer=_DefaultPrincipalIssuer(),
jobs=_DefaultJobs(
repository=repository, target_branch=target_branch, generation=generation
),
git=_DefaultGit(exists=branch_exists, head_oid=head_oid),
writer_lease=_DefaultWriterLease(),
owner_authority=authority,
)
automation = GovernedWriterAutomation(
manifest_store=manifest_store,
action_ledger=action_ledger,
decision_engine=decision_engine,
)
readiness = GovernedWriteReadiness(
legacy_global_writes_enabled=runtime_status.legacy_global_writes_enabled,
task_principal_writes_enabled=runtime_status.task_principal_writes_enabled,
task_principal_verifier_ready=runtime_status.task_principal_verifier_ready,
governed_write_blockers=tuple(runtime_status.governed_write_blockers),
)
return cls(
automation=automation,
adapters=adapters,
readiness=readiness,
owner_authority=authority,
)
def acquire(
self,
request: GovernedWriterRequest,
authorization_ref: str,
*,
epoch: EpochStatus | None = None,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
"""生产入口:先验 Owner 授权,再自动完成受治理写链。"""
return self.automation.acquire_verified(
request=request,
authorization_ref=authorization_ref,
readiness=self.readiness,
epoch=epoch,
adapters=self.adapters,
recovery_authorization=recovery_authorization,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
@@ -1,147 +0,0 @@
"""Fail-closed candidate-specific governed admission preflight.
Ordinary/global writes are deliberately NOT a prerequisite. This evaluates the
governed Bridge/TaskPrincipal path only.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Sequence
from .governance_resolver import ResolutionResult, ResolutionState
class AdmissionOperation(str, Enum):
GOVERNED_COMMIT = "GOVERNED_COMMIT"
RECOVER_EXISTING_CHILD = "RECOVER_EXISTING_CHILD"
@dataclass(frozen=True)
class GovernanceFacts:
governed_writes_enabled: bool
task_principal_writes_enabled: bool
recovery_execution_enabled: bool
recovery_shadow_mode: bool
authenticated_principal_id: str | None
target_branch: str | None
branch_protection_known: bool
expected_branch_oid: str | None
generation: int | None
task_lease_id: str | None
task_principal_id: str | None
writer_lease_id: str | None
accepted_file_allowlist: tuple[str, ...] = ()
acceptance_revision: str | None = None
@dataclass(frozen=True)
class AdmissionBlocker:
code: str
detail: str
@dataclass(frozen=True)
class AdmissionResult:
ready: bool
operation: AdmissionOperation
blockers: tuple[AdmissionBlocker, ...]
class AdmissionPreflight:
def evaluate(
self,
*,
resolution: ResolutionResult,
facts: GovernanceFacts,
operation: AdmissionOperation,
) -> AdmissionResult:
blockers: list[AdmissionBlocker] = []
if resolution.state is ResolutionState.NOT_FOUND:
blockers.append(AdmissionBlocker(
"TASK_NOT_RESOLVED", "exact candidate task not found"
))
elif resolution.state is ResolutionState.AMBIGUOUS:
blockers.append(AdmissionBlocker(
"TASK_RESOLUTION_AMBIGUOUS", "multiple exact candidate tasks"
))
elif resolution.state is not ResolutionState.RESOLVED:
blockers.append(AdmissionBlocker(
"TASK_RESOLUTION_INVALID", resolution.detail or "resolver invalid"
))
if not facts.governed_writes_enabled:
blockers.append(AdmissionBlocker(
"GOVERNED_WRITES_DISABLED",
"governed Bridge write path is disabled",
))
if not facts.task_principal_writes_enabled:
blockers.append(AdmissionBlocker(
"TASK_PRINCIPAL_WRITES_DISABLED",
"TaskPrincipal governed writes are disabled",
))
if not facts.authenticated_principal_id:
blockers.append(AdmissionBlocker(
"CALLER_PRINCIPAL_UNRESOLVED",
"authenticated governed caller principal is missing",
))
if not facts.target_branch:
blockers.append(AdmissionBlocker(
"TARGET_BRANCH_UNRESOLVED", "target branch is missing"
))
if not facts.branch_protection_known:
blockers.append(AdmissionBlocker(
"BRANCH_PROTECTION_UNKNOWN",
"branch protection has not been read through authority",
))
if not facts.expected_branch_oid:
blockers.append(AdmissionBlocker(
"EXPECTED_BRANCH_OID_UNRESOLVED",
"expected_branch_oid/CAS preimage is missing",
))
if facts.generation is None:
blockers.append(AdmissionBlocker(
"GENERATION_UNRESOLVED", "governed generation is missing"
))
if not facts.task_lease_id:
blockers.append(AdmissionBlocker(
"TASK_LEASE_MISSING", "TaskLease not established"
))
if not facts.task_principal_id:
blockers.append(AdmissionBlocker(
"TASK_PRINCIPAL_MISSING", "TaskPrincipal not established"
))
if not facts.writer_lease_id:
blockers.append(AdmissionBlocker(
"WRITER_LEASE_MISSING", "WriterLease not established"
))
if not facts.accepted_file_allowlist:
blockers.append(AdmissionBlocker(
"FILE_ALLOWLIST_MISSING", "accepted file allowlist is empty"
))
if not facts.acceptance_revision:
blockers.append(AdmissionBlocker(
"ACCEPTANCE_REVISION_MISSING",
"candidate acceptance revision is missing",
))
if operation is AdmissionOperation.RECOVER_EXISTING_CHILD:
if not facts.recovery_execution_enabled:
blockers.append(AdmissionBlocker(
"RECOVERY_EXECUTION_DISABLED",
"recovery execution path is disabled",
))
if facts.recovery_shadow_mode:
blockers.append(AdmissionBlocker(
"RECOVERY_SHADOW_MODE",
"recovery is still shadow-only",
))
# Ordinary/global write state is intentionally absent from the model.
return AdmissionResult(
ready=not blockers,
operation=operation,
blockers=tuple(blockers),
)
@@ -1,132 +0,0 @@
"""Canonical recovery observation checkpoint and unchanged-blocker budget.
The checkpoint is NOT an authority. It records the last reconciled observation
so agents do not repeatedly prove the same unchanged blocker.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, replace
import hashlib
import json
import os
from pathlib import Path
from typing import Protocol, Sequence
def _canonical(value) -> str:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
def _digest(value) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class RecoveryCheckpoint:
schema: str
goal_id: str
repository: str
source_oid: str
candidate_digest: str
task_id: str | None
mission_id: str | None
root_task_id: str | None
parent_task_id: str | None
principal_id: str | None
blocker_code: str | None
blocker_fingerprint: str | None
next_legal_action: str
last_verified_at: str
evidence_refs: tuple[str, ...]
unchanged_cycles: int
mechanism_repair_required: bool
recovery_idempotency_key: str | None = None
recovery_receipt_ref: str | None = None
recovery_context_digest: str | None = None
def to_dict(self) -> dict:
value = asdict(self)
value["evidence_refs"] = list(self.evidence_refs)
return value
@property
def digest(self) -> str:
return _digest(self.to_dict())
class CheckpointStore(Protocol):
def load(self) -> RecoveryCheckpoint | None: ...
def save(self, checkpoint: RecoveryCheckpoint) -> None: ...
class JsonCheckpointStore:
"""Atomic local observation cache; never used as authorization evidence."""
def __init__(self, path: str | Path) -> None:
self.path = Path(path)
def load(self) -> RecoveryCheckpoint | None:
if not self.path.exists():
return None
raw = json.loads(self.path.read_text(encoding="utf-8"))
raw["evidence_refs"] = tuple(raw.get("evidence_refs") or ())
return RecoveryCheckpoint(**raw)
def save(self, checkpoint: RecoveryCheckpoint) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
temp = self.path.with_suffix(self.path.suffix + ".tmp")
temp.write_text(
json.dumps(
checkpoint.to_dict(),
ensure_ascii=False,
indent=2,
sort_keys=True,
) + "\n",
encoding="utf-8",
)
os.replace(temp, self.path)
@dataclass(frozen=True)
class RetryBudget:
unchanged_threshold: int = 3
escalation_eligible_codes: frozenset[str] = frozenset({
"TASK_NOT_RESOLVED",
"TASK_RESOLUTION_AMBIGUOUS",
"CALLER_PRINCIPAL_UNRESOLVED",
"CANONICAL_CHANNEL_MISSING",
"RESOLVER_UNAVAILABLE",
})
def __post_init__(self):
if self.unchanged_threshold < 2 or self.unchanged_threshold > 100:
raise ValueError("unchanged_threshold must be in [2,100]")
@staticmethod
def blocker_fingerprint(code: str, details: Sequence[str]) -> str:
return _digest({
"code": code,
"details": sorted(str(item) for item in details),
})
def observe(
self,
*,
previous: RecoveryCheckpoint | None,
blocker_code: str,
blocker_fingerprint: str,
) -> tuple[int, bool]:
same = (
previous is not None
and previous.blocker_code == blocker_code
and previous.blocker_fingerprint == blocker_fingerprint
)
cycles = (previous.unchanged_cycles + 1) if same else 1
repair = (
blocker_code in self.escalation_eligible_codes
and cycles >= self.unchanged_threshold
)
return cycles, repair
@@ -1,871 +0,0 @@
from __future__ import annotations
import hashlib
import json
import sqlite3
import threading
import time
import uuid
from concurrent.futures import ThreadPoolExecutor, as_completed
from contextlib import closing
from dataclasses import asdict, dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Iterable, Mapping, Sequence
SCHEMA_RUNTIME_MANIFEST = "aios.runtime-manifest.v1"
SCHEMA_DECISION = "aios.governance-decision.v1"
def canonical_json_bytes(value: Any) -> bytes:
"""Private control-kernel encoding; not a replacement for published AIOS digests."""
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def sha256_hex(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
class GovernanceControlError(RuntimeError):
pass
class PreflightNotReady(GovernanceControlError):
pass
class StaleManifest(GovernanceControlError):
pass
class IdempotencyConflict(GovernanceControlError):
pass
class RetryBudgetExhausted(GovernanceControlError):
pass
class ConcurrentWriterDenied(GovernanceControlError):
pass
class InvalidStateTransition(GovernanceControlError):
pass
@dataclass(frozen=True)
class DiscoveryObservation:
worker: str
observed_at: float
runtime_fingerprint: str
process_start_identity: str
activation_digest: str
epoch_id: str | None
policy_digest: str | None
plan_provider_root: str | None
task_id: str | None
grant_id: str | None
facts: Mapping[str, Any] = field(default_factory=dict)
evidence_refs: tuple[str, ...] = ()
@classmethod
def build(
cls,
*,
worker: str,
runtime_fingerprint: str,
process_start_identity: str,
activation_digest: str,
epoch_id: str | None,
policy_digest: str | None,
plan_provider_root: str | None,
task_id: str | None,
grant_id: str | None,
facts: Mapping[str, Any] | None = None,
evidence_refs: Sequence[str] = (),
observed_at: float | None = None,
) -> "DiscoveryObservation":
return cls(
worker=str(worker),
observed_at=float(time.time() if observed_at is None else observed_at),
runtime_fingerprint=str(runtime_fingerprint),
process_start_identity=str(process_start_identity),
activation_digest=str(activation_digest),
epoch_id=None if epoch_id is None else str(epoch_id),
policy_digest=None if policy_digest is None else str(policy_digest),
plan_provider_root=None if plan_provider_root is None else str(plan_provider_root),
task_id=None if task_id is None else str(task_id),
grant_id=None if grant_id is None else str(grant_id),
facts=dict(facts or {}),
evidence_refs=tuple(str(x) for x in evidence_refs),
)
@dataclass(frozen=True)
class RuntimeManifest:
observed_at: float
runtime_fingerprint: str
process_start_identity: str
activation_digest: str
epoch_id: str | None
policy_digest: str | None
plan_provider_root: str | None
task_id: str | None
grant_id: str | None
worker_facts: Mapping[str, Mapping[str, Any]]
evidence_refs: tuple[str, ...]
schema: str = SCHEMA_RUNTIME_MANIFEST
def payload(self) -> dict[str, Any]:
return {
"schema": self.schema,
"observed_at": self.observed_at,
"runtime_fingerprint": self.runtime_fingerprint,
"process_start_identity": self.process_start_identity,
"activation_digest": self.activation_digest,
"epoch_id": self.epoch_id,
"policy_digest": self.policy_digest,
"plan_provider_root": self.plan_provider_root,
"task_id": self.task_id,
"grant_id": self.grant_id,
"worker_facts": {k: dict(v) for k, v in sorted(self.worker_facts.items())},
"evidence_refs": list(self.evidence_refs),
}
@property
def manifest_id(self) -> str:
return "sha256:" + sha256_hex(canonical_json_bytes(self.payload()))
class ConsistencyBarrier:
_IDENTITY_FIELDS = (
"runtime_fingerprint",
"process_start_identity",
"activation_digest",
"epoch_id",
"policy_digest",
"plan_provider_root",
"task_id",
"grant_id",
)
def __init__(self, expected_workers: Iterable[str]):
self.expected_workers = frozenset(str(x) for x in expected_workers)
if not self.expected_workers:
raise ValueError("expected_workers must not be empty")
self._observations: dict[str, DiscoveryObservation] = {}
def submit(self, observation: DiscoveryObservation) -> None:
if observation.worker not in self.expected_workers:
raise PreflightNotReady(f"unexpected discovery worker: {observation.worker}")
if observation.worker in self._observations:
raise PreflightNotReady(f"duplicate discovery worker: {observation.worker}")
self._observations[observation.worker] = observation
def freeze(self) -> RuntimeManifest:
missing = sorted(self.expected_workers - self._observations.keys())
if missing:
raise PreflightNotReady("missing discovery workers: " + ",".join(missing))
ordered = [self._observations[k] for k in sorted(self._observations)]
anchor = ordered[0]
for other in ordered[1:]:
for name in self._IDENTITY_FIELDS:
if getattr(anchor, name) != getattr(other, name):
raise StaleManifest(
f"consistency barrier mismatch field={name} "
f"anchor={getattr(anchor, name)!r} "
f"worker={other.worker} value={getattr(other, name)!r}"
)
evidence: list[str] = []
facts: dict[str, Mapping[str, Any]] = {}
observed_at = 0.0
for item in ordered:
observed_at = max(observed_at, item.observed_at)
evidence.extend(item.evidence_refs)
facts[item.worker] = dict(item.facts)
return RuntimeManifest(
observed_at=observed_at,
runtime_fingerprint=anchor.runtime_fingerprint,
process_start_identity=anchor.process_start_identity,
activation_digest=anchor.activation_digest,
epoch_id=anchor.epoch_id,
policy_digest=anchor.policy_digest,
plan_provider_root=anchor.plan_provider_root,
task_id=anchor.task_id,
grant_id=anchor.grant_id,
worker_facts=facts,
evidence_refs=tuple(dict.fromkeys(evidence)),
)
class ParallelDiscoveryCoordinator:
def __init__(self, workers: Mapping[str, Callable[[], DiscoveryObservation]], *, max_workers: int | None = None):
if not workers:
raise ValueError("workers must not be empty")
self.workers = dict(workers)
self.max_workers = max_workers or min(8, len(self.workers))
def discover(self) -> RuntimeManifest:
barrier = ConsistencyBarrier(self.workers.keys())
errors: dict[str, str] = {}
with ThreadPoolExecutor(max_workers=self.max_workers, thread_name_prefix="aios-discovery") as pool:
futures = {pool.submit(fn): name for name, fn in self.workers.items()}
for future in as_completed(futures):
name = futures[future]
try:
observation = future.result()
if observation.worker != name:
raise PreflightNotReady(
f"worker name mismatch expected={name!r} got={observation.worker!r}"
)
barrier.submit(observation)
except Exception as exc:
errors[name] = f"{type(exc).__name__}:{exc}"
if errors:
details = ",".join(f"{k}={v}" for k, v in sorted(errors.items()))
raise PreflightNotReady(f"discovery failed: {details}")
return barrier.freeze()
class ManifestStore:
def __init__(self, db_path: str | Path):
self.db_path = str(db_path)
self._lock = threading.RLock()
self._init_schema()
def _connect(self) -> sqlite3.Connection:
conn = sqlite3.connect(self.db_path, timeout=5, isolation_level=None)
conn.row_factory = sqlite3.Row
return conn
def _init_schema(self) -> None:
with closing(self._connect()) as conn:
conn.execute("PRAGMA journal_mode=WAL")
conn.execute(
"""
CREATE TABLE IF NOT EXISTS frozen_manifests (
manifest_id TEXT PRIMARY KEY,
runtime_fingerprint TEXT NOT NULL,
process_start_identity TEXT NOT NULL,
canonical_json TEXT NOT NULL,
frozen_at REAL NOT NULL,
superseded_at REAL
)
"""
)
def freeze(self, manifest: RuntimeManifest) -> str:
manifest_id = manifest.manifest_id
canonical = canonical_json_bytes(manifest.payload()).decode("utf-8")
with self._lock, closing(self._connect()) as conn:
conn.execute("BEGIN IMMEDIATE")
row = conn.execute(
"SELECT canonical_json FROM frozen_manifests WHERE manifest_id=?",
(manifest_id,),
).fetchone()
if row is None:
conn.execute(
"""
INSERT INTO frozen_manifests(
manifest_id,runtime_fingerprint,process_start_identity,
canonical_json,frozen_at,superseded_at
) VALUES(?,?,?,?,?,NULL)
""",
(
manifest_id,
manifest.runtime_fingerprint,
manifest.process_start_identity,
canonical,
time.time(),
),
)
elif row["canonical_json"] != canonical:
conn.rollback()
raise IdempotencyConflict("manifest_id collision with different canonical bytes")
conn.commit()
return manifest_id
def assert_current(self, *, manifest_id: str, runtime_fingerprint: str, process_start_identity: str) -> None:
with closing(self._connect()) as conn:
row = conn.execute(
"""
SELECT runtime_fingerprint,process_start_identity,superseded_at
FROM frozen_manifests WHERE manifest_id=?
""",
(manifest_id,),
).fetchone()
if row is None:
raise StaleManifest("unknown manifest_id")
if row["superseded_at"] is not None:
raise StaleManifest("manifest superseded")
if row["runtime_fingerprint"] != runtime_fingerprint:
raise StaleManifest("runtime fingerprint drift")
if row["process_start_identity"] != process_start_identity:
raise StaleManifest("process start identity drift")
def supersede(self, manifest_id: str) -> None:
with self._lock, closing(self._connect()) as conn:
conn.execute(
"UPDATE frozen_manifests SET superseded_at=? WHERE manifest_id=? AND superseded_at IS NULL",
(time.time(), manifest_id),
)
class ActionState(str, Enum):
RUNNING = "RUNNING"
SUCCEEDED = "SUCCEEDED"
FAILED = "FAILED"
EFFECT_UNKNOWN = "EFFECT_UNKNOWN"
class ActionKind(str, Enum):
READ_ONLY = "READ_ONLY"
MALFORMED_TOOL_REQUEST = "MALFORMED_TOOL_REQUEST"
ACTIVATION = "ACTIVATION"
RESTART = "RESTART"
EPOCH_ISSUANCE = "EPOCH_ISSUANCE"
ADMISSION = "ADMISSION"
CLAIM = "CLAIM"
COMMIT = "COMMIT"
GENERIC_MUTATION = "GENERIC_MUTATION"
DEFAULT_RETRY_BUDGETS: Mapping[ActionKind, int] = {
ActionKind.READ_ONLY: 2,
ActionKind.MALFORMED_TOOL_REQUEST: 1,
ActionKind.ACTIVATION: 1,
ActionKind.RESTART: 1,
ActionKind.EPOCH_ISSUANCE: 1,
ActionKind.ADMISSION: 1,
ActionKind.CLAIM: 1,
ActionKind.COMMIT: 1,
ActionKind.GENERIC_MUTATION: 1,
}
@dataclass(frozen=True)
class ActionTicket:
action_id: str
idempotency_key: str
manifest_id: str
phase: str
kind: ActionKind
target: str
precondition_hash: str
attempt_count: int
max_attempts: int
state: ActionState
reused: bool = False
requires_reconcile: bool = False
class ActionLedger:
def __init__(self, db_path: str | Path, *, retry_budgets: Mapping[ActionKind, int] | None = None):
self.db_path = str(db_path)
self.retry_budgets = dict(DEFAULT_RETRY_BUDGETS)
if retry_budgets:
self.retry_budgets.update(retry_budgets)
self._lock = threading.RLock()
self._init_schema()
def _connect(self) -> sqlite3.Connection:
conn = sqlite3.connect(self.db_path, timeout=5, isolation_level=None)
conn.row_factory = sqlite3.Row
return conn
def _init_schema(self) -> None:
with closing(self._connect()) as conn:
conn.execute("PRAGMA journal_mode=WAL")
conn.execute(
"""
CREATE TABLE IF NOT EXISTS actions (
action_id TEXT PRIMARY KEY,
idempotency_key TEXT UNIQUE NOT NULL,
manifest_id TEXT NOT NULL,
phase TEXT NOT NULL,
kind TEXT NOT NULL,
target TEXT NOT NULL,
precondition_hash TEXT NOT NULL,
attempt_count INTEGER NOT NULL,
max_attempts INTEGER NOT NULL,
state TEXT NOT NULL,
result_json TEXT,
evidence_json TEXT,
expected_readback_json TEXT,
rollback_ref TEXT,
created_at REAL NOT NULL,
updated_at REAL NOT NULL
)
"""
)
def begin(
self,
*,
idempotency_key: str,
manifest_id: str,
phase: str,
kind: ActionKind,
target: str,
precondition_hash: str,
expected_readback: Mapping[str, Any] | None = None,
rollback_ref: str | None = None,
) -> ActionTicket:
max_attempts = int(self.retry_budgets[kind])
now = time.time()
with self._lock, closing(self._connect()) as conn:
conn.execute("BEGIN IMMEDIATE")
row = conn.execute(
"SELECT * FROM actions WHERE idempotency_key=?",
(idempotency_key,),
).fetchone()
if row is None:
action_id = "act-" + uuid.uuid4().hex
conn.execute(
"""
INSERT INTO actions(
action_id,idempotency_key,manifest_id,phase,kind,target,
precondition_hash,attempt_count,max_attempts,state,
result_json,evidence_json,expected_readback_json,rollback_ref,
created_at,updated_at
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
action_id,idempotency_key,manifest_id,phase,kind.value,target,
precondition_hash,1,max_attempts,ActionState.RUNNING.value,
None,"[]",json.dumps(dict(expected_readback or {}), sort_keys=True),
rollback_ref,now,now,
),
)
conn.commit()
return ActionTicket(
action_id,idempotency_key,manifest_id,phase,kind,target,
precondition_hash,1,max_attempts,ActionState.RUNNING
)
if row["manifest_id"] != manifest_id or row["precondition_hash"] != precondition_hash:
conn.rollback()
raise IdempotencyConflict(
"same idempotency key used with different manifest/precondition"
)
state = ActionState(row["state"])
if state == ActionState.SUCCEEDED:
conn.commit()
return self._ticket(row, reused=True)
if state == ActionState.RUNNING:
conn.commit()
return self._ticket(row, reused=True)
if state == ActionState.EFFECT_UNKNOWN:
conn.commit()
return self._ticket(row, reused=True, requires_reconcile=True)
attempts = int(row["attempt_count"])
if attempts >= int(row["max_attempts"]):
conn.rollback()
raise RetryBudgetExhausted(
f"retry budget exhausted key={idempotency_key} attempts={attempts}"
)
attempts += 1
conn.execute(
"UPDATE actions SET attempt_count=?,state=?,updated_at=? WHERE action_id=?",
(attempts,ActionState.RUNNING.value,now,row["action_id"]),
)
conn.commit()
updated = dict(row)
updated["attempt_count"] = attempts
updated["state"] = ActionState.RUNNING.value
return self._ticket(updated)
def _ticket(self, row: Mapping[str, Any], *, reused: bool = False, requires_reconcile: bool = False) -> ActionTicket:
return ActionTicket(
action_id=str(row["action_id"]),
idempotency_key=str(row["idempotency_key"]),
manifest_id=str(row["manifest_id"]),
phase=str(row["phase"]),
kind=ActionKind(str(row["kind"])),
target=str(row["target"]),
precondition_hash=str(row["precondition_hash"]),
attempt_count=int(row["attempt_count"]),
max_attempts=int(row["max_attempts"]),
state=ActionState(str(row["state"])),
reused=reused,
requires_reconcile=requires_reconcile,
)
def _finish(self, action_id: str, state: ActionState, *, result: Mapping[str, Any] | None = None, evidence_refs: Sequence[str] = ()) -> None:
if state == ActionState.RUNNING:
raise ValueError("finish state cannot be RUNNING")
with self._lock, closing(self._connect()) as conn:
conn.execute("BEGIN IMMEDIATE")
row = conn.execute("SELECT state FROM actions WHERE action_id=?", (action_id,)).fetchone()
if row is None:
conn.rollback()
raise GovernanceControlError("unknown action_id")
if row["state"] == ActionState.SUCCEEDED.value and state != ActionState.SUCCEEDED:
conn.rollback()
raise InvalidStateTransition("cannot move succeeded action backwards")
conn.execute(
"""
UPDATE actions SET state=?,result_json=?,evidence_json=?,updated_at=?
WHERE action_id=?
""",
(
state.value,json.dumps(dict(result or {}), sort_keys=True),
json.dumps([str(x) for x in evidence_refs], sort_keys=True),
time.time(),action_id,
),
)
conn.commit()
def mark_succeeded(self, action_id: str, *, result: Mapping[str, Any], evidence_refs: Sequence[str] = ()) -> None:
self._finish(action_id, ActionState.SUCCEEDED, result=result, evidence_refs=evidence_refs)
def mark_failed(self, action_id: str, *, result: Mapping[str, Any] | None = None, evidence_refs: Sequence[str] = ()) -> None:
self._finish(action_id, ActionState.FAILED, result=result, evidence_refs=evidence_refs)
def mark_effect_unknown(self, action_id: str, *, result: Mapping[str, Any] | None = None, evidence_refs: Sequence[str] = ()) -> None:
self._finish(action_id, ActionState.EFFECT_UNKNOWN, result=result, evidence_refs=evidence_refs)
class HypothesisStatus(str, Enum):
OPEN = "OPEN"
CONFIRMED = "CONFIRMED"
REJECTED = "REJECTED"
class HypothesisLedger:
def __init__(self, db_path: str | Path):
self.db_path = str(db_path)
self._lock = threading.RLock()
self._init_schema()
def _connect(self) -> sqlite3.Connection:
conn = sqlite3.connect(self.db_path, timeout=5, isolation_level=None)
conn.row_factory = sqlite3.Row
return conn
def _init_schema(self) -> None:
with closing(self._connect()) as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS hypotheses (
hypothesis_id TEXT PRIMARY KEY,
claim TEXT NOT NULL,
evidence_for_json TEXT NOT NULL,
evidence_against_json TEXT NOT NULL,
status TEXT NOT NULL,
next_probe TEXT,
created_at REAL NOT NULL,
updated_at REAL NOT NULL
)
"""
)
def record(
self,
*,
hypothesis_id: str,
claim: str,
next_probe: str | None = None,
evidence_for: Sequence[str] = (),
evidence_against: Sequence[str] = (),
) -> None:
now = time.time()
with self._lock, closing(self._connect()) as conn:
conn.execute(
"""
INSERT INTO hypotheses(
hypothesis_id,claim,evidence_for_json,evidence_against_json,
status,next_probe,created_at,updated_at
) VALUES(?,?,?,?,?,?,?,?)
ON CONFLICT(hypothesis_id) DO UPDATE SET
claim=excluded.claim,
evidence_for_json=excluded.evidence_for_json,
evidence_against_json=excluded.evidence_against_json,
next_probe=excluded.next_probe,
updated_at=excluded.updated_at
""",
(
hypothesis_id,claim,json.dumps(list(evidence_for), sort_keys=True),
json.dumps(list(evidence_against), sort_keys=True),
HypothesisStatus.OPEN.value,next_probe,now,now,
),
)
def set_status(self, hypothesis_id: str, status: HypothesisStatus) -> None:
with self._lock, closing(self._connect()) as conn:
cur = conn.execute(
"UPDATE hypotheses SET status=?,updated_at=? WHERE hypothesis_id=?",
(status.value,time.time(),hypothesis_id),
)
if cur.rowcount != 1:
raise GovernanceControlError("unknown hypothesis_id")
def assert_probe_discriminates(self, candidate_hypothesis_ids: Sequence[str]) -> None:
ids = tuple(dict.fromkeys(str(x) for x in candidate_hypothesis_ids))
if len(ids) < 2:
raise GovernanceControlError("probe must discriminate at least two candidate hypotheses")
placeholders = ",".join("?" for _ in ids)
with closing(self._connect()) as conn:
rows = conn.execute(
f"SELECT hypothesis_id,status FROM hypotheses WHERE hypothesis_id IN ({placeholders})",
ids,
).fetchall()
open_ids = {
str(r["hypothesis_id"]) for r in rows
if r["status"] == HypothesisStatus.OPEN.value
}
if len(open_ids) < 2:
raise GovernanceControlError("probe does not discriminate at least two OPEN hypotheses")
class BlockerClassification(str, Enum):
EXTERNAL_DEPENDENCY = "EXTERNAL_DEPENDENCY"
MECHANISM_DEFECT = "MECHANISM_DEFECT"
TRANSIENT = "TRANSIENT"
OWNER_BOUNDARY = "OWNER_BOUNDARY"
SELF_ACTIONABLE = "SELF_ACTIONABLE"
class ProducerKind(str, Enum):
SELF = "SELF"
AUTHORITY_SIDE = "AUTHORITY_SIDE"
IMPLEMENTER = "IMPLEMENTER"
CONTROL_PLANE_OWNER = "CONTROL_PLANE_OWNER"
OWNER = "OWNER"
class NextAction(str, Enum):
ROOT_COMPLETE = "ROOT_COMPLETE"
CREATE_OR_RESUME_NEXT_CHILD = "CREATE_OR_RESUME_NEXT_CHILD"
WAIT_DEPENDENCY = "WAIT_DEPENDENCY"
RECOVER_EXISTING_CHILD = "RECOVER_EXISTING_CHILD"
OWNER_BOUNDARY = "OWNER_BOUNDARY"
REPAIR_MECHANISM = "REPAIR_MECHANISM"
@dataclass(frozen=True)
class ProducerBinding:
classification: BlockerClassification
producer_kind: ProducerKind
producer_id: str
action_required: str
DEFAULT_PRODUCER_TABLE: Mapping[str, ProducerBinding] = {
"TASK_NOT_RESOLVED": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.AUTHORITY_SIDE, "task-mission-authority", "PROVISION_EXACT_CANDIDATE"),
"TASK_RESOLUTION_AMBIGUOUS": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.AUTHORITY_SIDE, "task-mission-authority", "PROVISION_EXACT_CANDIDATE"),
"LIVE_SURFACE_UNAVAILABLE": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.AUTHORITY_SIDE, "governed-bridge-authority-owner", "EXPOSE_READ_SURFACE"),
"LIVE_BINDING_VIOLATION": ProducerBinding(BlockerClassification.MECHANISM_DEFECT, ProducerKind.IMPLEMENTER, "agent-side-adapter-owner", "FIX_BINDING_ADAPTER"),
"GOVERNED_WRITES_DISABLED": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.CONTROL_PLANE_OWNER, "bridge-runtime-config", "ENABLE_GOVERNED_WRITES"),
"TASK_PRINCIPAL_WRITES_DISABLED": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.CONTROL_PLANE_OWNER, "bridge-runtime-config", "ENABLE_TASK_PRINCIPAL_WRITES"),
"TASK_LEASE_MISSING": ProducerBinding(BlockerClassification.SELF_ACTIONABLE, ProducerKind.SELF, "agent-recovery-executor", "ACQUIRE_LEASE_VIA_GOVERNED_PATH"),
"TASK_PRINCIPAL_MISSING": ProducerBinding(BlockerClassification.SELF_ACTIONABLE, ProducerKind.SELF, "agent-recovery-executor", "ISSUE_TASK_PRINCIPAL"),
"WRITER_LEASE_MISSING": ProducerBinding(BlockerClassification.SELF_ACTIONABLE, ProducerKind.SELF, "agent-recovery-executor", "ACQUIRE_WRITER_LEASE"),
"EXPECTED_BRANCH_OID_UNRESOLVED": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.AUTHORITY_SIDE, "governed-bridge-provisioner", "PROVISION_CANDIDATE_BINDING"),
"GENERATION_UNRESOLVED": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.AUTHORITY_SIDE, "governed-bridge-provisioner", "PROVISION_CANDIDATE_BINDING"),
"TARGET_BRANCH_UNRESOLVED": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.AUTHORITY_SIDE, "governed-bridge-provisioner", "PROVISION_CANDIDATE_BINDING"),
"FILE_ALLOWLIST_MISSING": ProducerBinding(BlockerClassification.OWNER_BOUNDARY, ProducerKind.OWNER, "owner-delegation-epoch", "ISSUE_OWNER_EPOCH"),
"ACCEPTANCE_REVISION_MISSING": ProducerBinding(BlockerClassification.OWNER_BOUNDARY, ProducerKind.OWNER, "owner-delegation-epoch", "ISSUE_OWNER_EPOCH"),
"RECOVERY_RESULT_UNKNOWN": ProducerBinding(BlockerClassification.SELF_ACTIONABLE, ProducerKind.SELF, "agent-recovery-executor", "RECONCILE_BEFORE_RETRY"),
"CAPABILITY_OUT_OF_POLICY": ProducerBinding(BlockerClassification.EXTERNAL_DEPENDENCY, ProducerKind.AUTHORITY_SIDE, "admission-policy-authority", "ALIGN_POLICY_AND_EPOCH"),
"EXECUTION_DIGEST_INVALID": ProducerBinding(BlockerClassification.MECHANISM_DEFECT, ProducerKind.IMPLEMENTER, "candidate-plan-builder", "REGENERATE_CANONICAL_PLAN"),
"TASK_EXPIRED_OWNER_RECOVERY_REQUIRED": ProducerBinding(BlockerClassification.OWNER_BOUNDARY, ProducerKind.OWNER, "agent-task-terminal-recovery", "AUTHORIZE_TERMINAL_RECOVERY_OR_SUCCESSOR"),
"EPOCH_EXPIRED": ProducerBinding(BlockerClassification.SELF_ACTIONABLE, ProducerKind.SELF, "trusted-grant-signer-adapter", "JIT_REISSUE_EPOCH_IF_SCOPE_UNCHANGED"),
}
@dataclass(frozen=True)
class DecisionInput:
goal_id: str
blocker_codes: tuple[str, ...] = ()
owner_boundary_required: bool = False
surfaces_ready: bool = True
resolution_state: str = "RESOLVED"
admission_ready: bool = False
acceptance_complete: bool = False
descendant_statuses: tuple[str, ...] = ()
unchanged_cycles: int = 0
evidence_refs: tuple[str, ...] = ()
@dataclass(frozen=True)
class GovernanceDecision:
goal_id: str
next_valid_action: NextAction
owner_boundary: bool
blocker_code: str | None
classification: BlockerClassification | None
producer_kind: ProducerKind | None
producer_id: str | None
action_required: str | None
evidence_refs: tuple[str, ...]
schema: str = SCHEMA_DECISION
def to_dict(self) -> dict[str, Any]:
result = asdict(self)
result["next_valid_action"] = self.next_valid_action.value
result["classification"] = None if self.classification is None else self.classification.value
result["producer_kind"] = None if self.producer_kind is None else self.producer_kind.value
return result
class AutonomousDecisionEngine:
RECOVERY_STATUSES = frozenset({"FAILED_RECOVERABLE", "EXPIRED", "RECOVERY_REQUIRED"})
BLOCKED_STATUSES = frozenset({"BLOCKED", "WAIT_DEPENDENCY", "TERMINAL_UNPROVEN"})
def __init__(self, producer_table: Mapping[str, ProducerBinding] | None = None):
self.producer_table = dict(DEFAULT_PRODUCER_TABLE)
if producer_table:
self.producer_table.update(producer_table)
def _binding(self, blocker: str) -> ProducerBinding:
return self.producer_table.get(
blocker,
ProducerBinding(BlockerClassification.MECHANISM_DEFECT, ProducerKind.IMPLEMENTER, "unmapped-blocker-owner", "CLASSIFY_BLOCKER"),
)
def decide(self, inp: DecisionInput) -> GovernanceDecision:
blockers = tuple(dict.fromkeys(inp.blocker_codes))
for blocker in blockers:
binding = self._binding(blocker)
if binding.classification == BlockerClassification.MECHANISM_DEFECT:
return self._decision(inp, blocker, binding, NextAction.REPAIR_MECHANISM)
if inp.owner_boundary_required:
blocker = blockers[0] if blockers else "OWNER_BOUNDARY"
binding = self._binding(blocker) if blockers else ProducerBinding(
BlockerClassification.OWNER_BOUNDARY, ProducerKind.OWNER, "owner", "OWNER_DECISION_REQUIRED"
)
return self._decision(inp, blocker, binding, NextAction.OWNER_BOUNDARY)
for blocker in blockers:
binding = self._binding(blocker)
if binding.classification == BlockerClassification.OWNER_BOUNDARY:
return self._decision(inp, blocker, binding, NextAction.OWNER_BOUNDARY)
if not inp.surfaces_ready:
blocker = "LIVE_SURFACE_UNAVAILABLE"
return self._decision(inp, blocker, self._binding(blocker), NextAction.WAIT_DEPENDENCY)
if inp.resolution_state != "RESOLVED":
blocker = "TASK_RESOLUTION_AMBIGUOUS" if inp.resolution_state == "AMBIGUOUS" else "TASK_NOT_RESOLVED"
return self._decision(inp, blocker, self._binding(blocker), NextAction.WAIT_DEPENDENCY)
if not inp.admission_ready:
for blocker in blockers:
binding = self._binding(blocker)
if binding.classification == BlockerClassification.SELF_ACTIONABLE:
return self._decision(inp, blocker, binding, NextAction.RECOVER_EXISTING_CHILD)
if blockers:
blocker = blockers[0]
return self._decision(inp, blocker, self._binding(blocker), NextAction.WAIT_DEPENDENCY)
return GovernanceDecision(
goal_id=inp.goal_id,next_valid_action=NextAction.WAIT_DEPENDENCY,
owner_boundary=False,blocker_code=None,
classification=BlockerClassification.EXTERNAL_DEPENDENCY,
producer_kind=ProducerKind.AUTHORITY_SIDE,producer_id="admission-authority",
action_required="MAKE_ADMISSION_READY",evidence_refs=inp.evidence_refs,
)
if inp.acceptance_complete:
return GovernanceDecision(
goal_id=inp.goal_id,next_valid_action=NextAction.ROOT_COMPLETE,
owner_boundary=False,blocker_code=None,classification=None,
producer_kind=None,producer_id=None,action_required=None,evidence_refs=inp.evidence_refs,
)
statuses = {str(x).upper() for x in inp.descendant_statuses}
if statuses & self.RECOVERY_STATUSES:
return GovernanceDecision(
goal_id=inp.goal_id,next_valid_action=NextAction.RECOVER_EXISTING_CHILD,
owner_boundary=False,blocker_code=None,
classification=BlockerClassification.SELF_ACTIONABLE,
producer_kind=ProducerKind.SELF,producer_id="agent-recovery-executor",
action_required="RECOVER_EXISTING_CHILD",evidence_refs=inp.evidence_refs,
)
if statuses & self.BLOCKED_STATUSES:
return GovernanceDecision(
goal_id=inp.goal_id,next_valid_action=NextAction.WAIT_DEPENDENCY,
owner_boundary=False,blocker_code=None,
classification=BlockerClassification.EXTERNAL_DEPENDENCY,
producer_kind=ProducerKind.AUTHORITY_SIDE,producer_id="dependency-authority",
action_required="UNBLOCK_DEPENDENCY",evidence_refs=inp.evidence_refs,
)
return GovernanceDecision(
goal_id=inp.goal_id,next_valid_action=NextAction.CREATE_OR_RESUME_NEXT_CHILD,
owner_boundary=False,blocker_code=None,
classification=BlockerClassification.SELF_ACTIONABLE,
producer_kind=ProducerKind.SELF,producer_id="autonomy-controller",
action_required="CREATE_OR_RESUME_NEXT_CHILD",evidence_refs=inp.evidence_refs,
)
def _decision(self, inp: DecisionInput, blocker: str, binding: ProducerBinding, next_action: NextAction) -> GovernanceDecision:
return GovernanceDecision(
goal_id=inp.goal_id,next_valid_action=next_action,
owner_boundary=binding.classification == BlockerClassification.OWNER_BOUNDARY,
blocker_code=blocker,classification=binding.classification,
producer_kind=binding.producer_kind,producer_id=binding.producer_id,
action_required=binding.action_required,evidence_refs=inp.evidence_refs,
)
class MutationGuard:
def __init__(self, manifest_store: ManifestStore, action_ledger: ActionLedger):
self.manifest_store = manifest_store
self.action_ledger = action_ledger
self._writer_lock = threading.Lock()
def begin_mutation(
self,
*,
manifest_id: str,
runtime_fingerprint: str,
process_start_identity: str,
idempotency_key: str,
phase: str,
kind: ActionKind,
target: str,
precondition_hash: str,
expected_readback: Mapping[str, Any] | None = None,
rollback_ref: str | None = None,
) -> ActionTicket:
self.manifest_store.assert_current(
manifest_id=manifest_id,
runtime_fingerprint=runtime_fingerprint,
process_start_identity=process_start_identity,
)
if not self._writer_lock.acquire(blocking=False):
raise ConcurrentWriterDenied("another governed mutation is in progress")
try:
ticket = self.action_ledger.begin(
idempotency_key=idempotency_key,manifest_id=manifest_id,phase=phase,
kind=kind,target=target,precondition_hash=precondition_hash,
expected_readback=expected_readback,rollback_ref=rollback_ref,
)
if ticket.reused:
self._writer_lock.release()
return ticket
except Exception:
self._writer_lock.release()
raise
def finish_success(self, ticket: ActionTicket, *, result: Mapping[str, Any], evidence_refs: Sequence[str] = ()) -> None:
try:
self.action_ledger.mark_succeeded(ticket.action_id, result=result, evidence_refs=evidence_refs)
finally:
if self._writer_lock.locked():
self._writer_lock.release()
def finish_failed(self, ticket: ActionTicket, *, result: Mapping[str, Any] | None = None, evidence_refs: Sequence[str] = ()) -> None:
try:
self.action_ledger.mark_failed(ticket.action_id, result=result, evidence_refs=evidence_refs)
finally:
if self._writer_lock.locked():
self._writer_lock.release()
def finish_effect_unknown(self, ticket: ActionTicket, *, result: Mapping[str, Any] | None = None, evidence_refs: Sequence[str] = ()) -> None:
try:
self.action_ledger.mark_effect_unknown(ticket.action_id, result=result, evidence_refs=evidence_refs)
finally:
if self._writer_lock.locked():
self._writer_lock.release()
@@ -1,118 +0,0 @@
"""Live driver that converts external surface failures into one checkpoint.
The controller remains authority-neutral. This driver owns the site boundary:
surface availability, binding-contract failures, and poll fallback.
"""
from __future__ import annotations
from .governance_checkpoint import RecoveryCheckpoint, RetryBudget
from .governance_recovery_controller import (
GovernanceEvent,
GovernanceRecoveryController,
ReconcileResult,
)
from .site_adapters import LiveBindingViolation, LiveSurfaceUnavailable
class GovernanceLiveDriver:
def __init__(
self,
controller: GovernanceRecoveryController,
*,
retry_budget: RetryBudget | None = None,
) -> None:
self.controller = controller
self.retry_budget = retry_budget or controller.retry_budget
def _candidate_digest(self) -> str:
query = self.controller.query
return (
query.patch_digest
or query.artifact_sha256
or query.candidate_oid
or ""
)
def _persist_external_blocker(
self,
event: GovernanceEvent,
*,
code: str,
detail: str,
mechanism_repair_required: bool,
) -> ReconcileResult:
previous = self.controller.checkpoints.load()
fingerprint = self.retry_budget.blocker_fingerprint(code, (detail,))
if mechanism_repair_required:
cycles = (
previous.unchanged_cycles + 1
if previous
and previous.blocker_code == code
and previous.blocker_fingerprint == fingerprint
else 1
)
else:
cycles, _ = self.retry_budget.observe(
previous=previous,
blocker_code=code,
blocker_fingerprint=fingerprint,
)
next_action = (
"REPAIR_MECHANISM"
if mechanism_repair_required
else "WAIT_DEPENDENCY"
)
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.controller.goal_id,
repository=self.controller.query.repository,
source_oid=self.controller.query.base_oid,
candidate_digest=self._candidate_digest(),
task_id=None,
mission_id=None,
root_task_id=None,
parent_task_id=None,
principal_id=None,
blocker_code=code,
blocker_fingerprint=fingerprint,
next_legal_action=next_action,
last_verified_at=self.controller._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=cycles,
mechanism_repair_required=mechanism_repair_required,
)
self.controller.checkpoints.save(checkpoint)
return ReconcileResult(
status="BLOCKED",
next_legal_action=next_action,
task_id=None,
blocker_code=code,
mechanism_repair_required=mechanism_repair_required,
)
def on_event(self, event: GovernanceEvent) -> ReconcileResult:
try:
return self.controller.on_event(event)
except LiveSurfaceUnavailable as exc:
return self._persist_external_blocker(
event,
code="LIVE_SURFACE_UNAVAILABLE",
detail=str(exc),
mechanism_repair_required=False,
)
except LiveBindingViolation as exc:
return self._persist_external_blocker(
event,
code="LIVE_BINDING_VIOLATION",
detail=str(exc),
mechanism_repair_required=True,
)
def poll_once(
self, *, revision: str, occurred_at: str
) -> ReconcileResult:
return self.on_event(GovernanceEvent(
kind="POLL_FALLBACK",
revision=revision,
occurred_at=occurred_at,
))
@@ -1,474 +0,0 @@
"""Event-driven governed recovery reconciler with polling fallback.
The controller cannot create a task and cannot bypass admission. It resumes
only an exact, already-existing child through an injected governed executor.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
from typing import Mapping, Protocol
from .governance_admission import (
AdmissionOperation,
AdmissionPreflight,
GovernanceFacts,
)
from .governance_checkpoint import (
CheckpointStore,
RecoveryCheckpoint,
RetryBudget,
)
from .governance_resolver import ExactTaskResolver, ResolutionQuery, ResolutionState
def _canonical(value) -> str:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
def _digest(value) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class GovernanceEvent:
kind: str
revision: str
occurred_at: str
@dataclass(frozen=True)
class RecoveryReceipt:
receipt_ref: str
task_id: str
accepted: bool
@dataclass(frozen=True)
class ReconcileResult:
status: str
next_legal_action: str
task_id: str | None
blocker_code: str | None
mechanism_repair_required: bool
receipt_ref: str | None = None
class GovernanceFactsProvider(Protocol):
def snapshot(
self,
*,
task_id: str | None,
actor_identity: str,
operation: AdmissionOperation,
) -> GovernanceFacts: ...
class ExistingChildRecoveryExecutor(Protocol):
def recover_existing_child(
self,
*,
task_id: str,
actor_identity: str,
idempotency_key: str,
facts: GovernanceFacts,
) -> RecoveryReceipt: ...
# Optional at runtime. If implemented, this is preferred after an
# unknown-result write so the controller reconciles before re-executing.
def reconcile_recovery_result(
self,
*,
task_id: str,
actor_identity: str,
idempotency_key: str,
) -> RecoveryReceipt | None: ...
class GovernanceRecoveryController:
def __init__(
self,
*,
goal_id: str,
query: ResolutionQuery,
actor_identity: str,
resolver: ExactTaskResolver,
facts_provider: GovernanceFactsProvider,
executor: ExistingChildRecoveryExecutor,
checkpoint_store: CheckpointStore,
retry_budget: RetryBudget = RetryBudget(),
) -> None:
self.goal_id = goal_id
self.query = query
self.actor_identity = actor_identity
self.resolver = resolver
self.facts_provider = facts_provider
self.executor = executor
self.checkpoints = checkpoint_store
self.retry_budget = retry_budget
self.preflight = AdmissionPreflight()
def _timestamp(self, event: GovernanceEvent) -> str:
return event.occurred_at or datetime.now(timezone.utc).isoformat()
def _save_blocked(
self,
*,
event: GovernanceEvent,
task_id: str | None,
mission_id: str | None,
root_task_id: str | None,
parent_task_id: str | None,
principal_id: str | None,
blocker_code: str,
blocker_details: tuple[str, ...],
) -> ReconcileResult:
previous = self.checkpoints.load()
fingerprint = self.retry_budget.blocker_fingerprint(
blocker_code, blocker_details
)
cycles, repair = self.retry_budget.observe(
previous=previous,
blocker_code=blocker_code,
blocker_fingerprint=fingerprint,
)
next_action = "REPAIR_MECHANISM" if repair else "WAIT_DEPENDENCY"
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=(
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
),
task_id=task_id,
mission_id=mission_id,
root_task_id=root_task_id,
parent_task_id=parent_task_id,
principal_id=principal_id,
blocker_code=blocker_code,
blocker_fingerprint=fingerprint,
next_legal_action=next_action,
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=cycles,
mechanism_repair_required=repair,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="BLOCKED",
next_legal_action=next_action,
task_id=task_id,
blocker_code=blocker_code,
mechanism_repair_required=repair,
)
def _candidate_digest(self) -> str:
return (
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
)
def _recovery_context_digest(
self, *, task_id: str, facts: GovernanceFacts
) -> str:
return _digest({
"task_id": task_id,
"repository": self.query.repository,
"candidate": self._candidate_digest(),
"principal": facts.authenticated_principal_id,
"branch": facts.target_branch,
"branch_oid": facts.expected_branch_oid,
"generation": facts.generation,
"task_lease_id": facts.task_lease_id,
"task_principal_id": facts.task_principal_id,
"writer_lease_id": facts.writer_lease_id,
"acceptance_revision": facts.acceptance_revision,
"file_allowlist": sorted(facts.accepted_file_allowlist),
})
def _finish_recovered(
self,
*,
event: GovernanceEvent,
resolved,
facts: GovernanceFacts,
idempotency_key: str,
context_digest: str,
receipt: RecoveryReceipt,
) -> ReconcileResult:
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=self._candidate_digest(),
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code=None,
blocker_fingerprint=None,
next_legal_action="ROOT_REVIEW",
last_verified_at=self._timestamp(event),
evidence_refs=(
f"event:{event.kind}:{event.revision}",
receipt.receipt_ref,
),
unchanged_cycles=0,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_receipt_ref=receipt.receipt_ref,
recovery_context_digest=context_digest,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RECOVERED",
next_legal_action="ROOT_REVIEW",
task_id=resolved.task_id,
blocker_code=None,
mechanism_repair_required=False,
receipt_ref=receipt.receipt_ref,
)
def reconcile(self, event: GovernanceEvent) -> ReconcileResult:
resolution = self.resolver.resolve(
self.query, actor_identity=self.actor_identity
)
if resolution.state is not ResolutionState.RESOLVED:
code = {
ResolutionState.NOT_FOUND: "TASK_NOT_RESOLVED",
ResolutionState.AMBIGUOUS: "TASK_RESOLUTION_AMBIGUOUS",
ResolutionState.INVALID_QUERY: "TASK_RESOLUTION_INVALID",
}[resolution.state]
details = (
resolution.detail,
*resolution.candidate_task_ids,
)
return self._save_blocked(
event=event,
task_id=None,
mission_id=None,
root_task_id=None,
parent_task_id=None,
principal_id=None,
blocker_code=code,
blocker_details=tuple(str(v) for v in details if v),
)
resolved = resolution.resolved
assert resolved is not None
facts = self.facts_provider.snapshot(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
admission = self.preflight.evaluate(
resolution=resolution,
facts=facts,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
if not admission.ready:
primary = admission.blockers[0]
details = tuple(
f"{blocker.code}:{blocker.detail}"
for blocker in admission.blockers
)
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code=primary.code,
blocker_details=details,
)
context_digest = self._recovery_context_digest(
task_id=resolved.task_id, facts=facts
)
previous = self.checkpoints.load()
candidate_digest = self._candidate_digest()
unknown_previous = (
previous is not None
and previous.blocker_code == "RECOVERY_RESULT_UNKNOWN"
and previous.task_id == resolved.task_id
and previous.candidate_digest == candidate_digest
and bool(previous.recovery_idempotency_key)
)
if unknown_previous:
idempotency_key = previous.recovery_idempotency_key
reconcile = getattr(
self.executor, "reconcile_recovery_result", None
)
if callable(reconcile):
receipt = reconcile(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
idempotency_key=idempotency_key,
)
if receipt is not None:
if receipt.accepted:
return self._finish_recovered(
event=event,
resolved=resolved,
facts=facts,
idempotency_key=idempotency_key,
context_digest=(
previous.recovery_context_digest
or context_digest
),
receipt=receipt,
)
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_NOT_ACCEPTED",
blocker_details=(receipt.receipt_ref,),
)
if (
previous.recovery_context_digest is not None
and previous.recovery_context_digest != context_digest
):
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=candidate_digest,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
blocker_fingerprint=_digest({
"task_id": resolved.task_id,
"idempotency_key": idempotency_key,
"context_changed": True,
}),
next_legal_action="RECOVER_EXISTING_CHILD",
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=1,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_context_digest=(
previous.recovery_context_digest
),
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RESULT_UNKNOWN",
next_legal_action="RECOVER_EXISTING_CHILD",
task_id=resolved.task_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
mechanism_repair_required=False,
)
else:
idempotency_key = "recover-" + _digest({
"task_id": resolved.task_id,
"repository": self.query.repository,
"candidate": candidate_digest,
"branch_oid": facts.expected_branch_oid,
"generation": facts.generation,
"writer_lease_id": facts.writer_lease_id,
})[:40]
try:
receipt = self.executor.recover_existing_child(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
idempotency_key=idempotency_key,
facts=facts,
)
except Exception:
# Unknown-result write: retain the SAME idempotency key for the next
# reconcile. Never assume the side effect did not happen.
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=(
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
),
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
blocker_fingerprint=_digest({
"task_id": resolved.task_id,
"idempotency_key": idempotency_key,
}),
next_legal_action="RECOVER_EXISTING_CHILD",
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=1,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_context_digest=context_digest,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RESULT_UNKNOWN",
next_legal_action="RECOVER_EXISTING_CHILD",
task_id=resolved.task_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
mechanism_repair_required=False,
)
if not receipt.accepted:
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_NOT_ACCEPTED",
blocker_details=(receipt.receipt_ref,),
)
return self._finish_recovered(
event=event,
resolved=resolved,
facts=facts,
idempotency_key=idempotency_key,
context_digest=context_digest,
receipt=receipt,
)
def on_event(self, event: GovernanceEvent) -> ReconcileResult:
# Event-driven primary path: reconcile immediately in the same call.
return self.reconcile(event)
def poll_once(self, *, revision: str, occurred_at: str) -> ReconcileResult:
# Polling is only a fallback and uses exactly the same reconcile path.
return self.reconcile(GovernanceEvent(
kind="POLL_FALLBACK",
revision=revision,
occurred_at=occurred_at,
))
@@ -1,229 +0,0 @@
"""Read-only exact Task/lineage resolver.
This is an index/projection over existing TaskPlane authority, not a second task
authority. It never creates, claims, mutates, or guesses a task.
The source supplied by the host MUST enforce the caller's existing TaskPlane
read authorization before returning records.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Mapping, Protocol, Sequence
class ResolutionState(str, Enum):
RESOLVED = "RESOLVED"
NOT_FOUND = "NOT_FOUND"
AMBIGUOUS = "AMBIGUOUS"
INVALID_QUERY = "INVALID_QUERY"
@dataclass(frozen=True)
class ResolutionQuery:
repository: str
base_oid: str
task_id: str | None = None
candidate_oid: str | None = None
patch_digest: str | None = None
artifact_sha256: str | None = None
pr_id: str | None = None
release_intent: str | None = None
mission_id: str | None = None
root_task_id: str | None = None
parent_task_id: str | None = None
owner_principal_id: str | None = None
def selectors(self) -> Mapping[str, str]:
values = {
"repository": self.repository,
"base_oid": self.base_oid,
"task_id": self.task_id,
"candidate_oid": self.candidate_oid,
"patch_digest": self.patch_digest,
"artifact_sha256": self.artifact_sha256,
"pr_id": self.pr_id,
"release_intent": self.release_intent,
"mission_id": self.mission_id,
"root_task_id": self.root_task_id,
"parent_task_id": self.parent_task_id,
"owner_principal_id": self.owner_principal_id,
}
return {key: str(value) for key, value in values.items() if value}
def validate(self) -> None:
if not self.repository.strip() or not self.base_oid.strip():
raise ValueError("repository and base_oid required")
strong = (
self.task_id,
self.candidate_oid,
self.patch_digest,
self.artifact_sha256,
self.pr_id,
self.release_intent,
self.mission_id,
self.root_task_id,
)
if not any(value for value in strong):
raise ValueError(
"at least one strong selector required "
"(task/candidate/patch/artifact/pr/intent/mission/root)"
)
@dataclass(frozen=True)
class ResolvedTask:
task_id: str
mission_id: str | None
root_task_id: str | None
parent_task_id: str | None
owner_principal_id: str | None
record: Mapping[str, Any]
matched_selectors: tuple[str, ...]
@dataclass(frozen=True)
class ResolutionResult:
state: ResolutionState
resolved: ResolvedTask | None = None
candidate_task_ids: tuple[str, ...] = ()
detail: str = ""
class TaskResolutionSource(Protocol):
"""Caller-authorized read projection over the existing TaskPlane."""
def get_task(
self, task_id: str, *, actor_identity: str
) -> Mapping[str, Any] | None: ...
def list_tasks(
self, *, actor_identity: str
) -> Sequence[Mapping[str, Any]]: ...
def _mapping(value: Any) -> Mapping[str, Any]:
return value if isinstance(value, Mapping) else {}
def _metadata(record: Mapping[str, Any]) -> Mapping[str, Any]:
plan = _mapping(record.get("plan") or record.get("task_plan"))
merged: dict[str, Any] = {}
for source in (
_mapping(record.get("metadata")),
_mapping(plan.get("metadata")),
):
merged.update(source)
# Some existing lineage metadata is nested.
for key in ("aios_task_lineage", "lineage", "task_lineage"):
nested = _mapping(merged.get(key))
for nkey, value in nested.items():
merged.setdefault(nkey, value)
return merged
def _first(record: Mapping[str, Any], metadata: Mapping[str, Any], *keys: str):
for key in keys:
if key in record and record.get(key) not in (None, ""):
return record.get(key)
if key in metadata and metadata.get(key) not in (None, ""):
return metadata.get(key)
return None
def _projection(record: Mapping[str, Any]) -> Mapping[str, str]:
metadata = _metadata(record)
values = {
"repository": _first(record, metadata, "repository", "repo"),
"base_oid": _first(record, metadata, "base_oid", "source_oid"),
"task_id": _first(record, metadata, "task_id"),
"candidate_oid": _first(record, metadata, "candidate_oid", "head_oid"),
"patch_digest": _first(record, metadata, "patch_digest", "candidate_digest"),
"artifact_sha256": _first(record, metadata, "artifact_sha256", "artifact_digest"),
"pr_id": _first(record, metadata, "pr_id", "pull_request_id"),
"release_intent": _first(record, metadata, "release_intent", "release_intent_id"),
"mission_id": _first(record, metadata, "mission_id"),
"root_task_id": _first(record, metadata, "root_task_id"),
"parent_task_id": _first(record, metadata, "parent_task_id"),
"owner_principal_id": _first(record, metadata, "owner_principal_id"),
}
return {
key: str(value)
for key, value in values.items()
if value not in (None, "")
}
def _matches(record: Mapping[str, Any], selectors: Mapping[str, str]) -> bool:
projection = _projection(record)
# Exact-only: a missing selector never counts as a partial match.
return all(projection.get(key) == value for key, value in selectors.items())
class ExactTaskResolver:
def __init__(self, source: TaskResolutionSource) -> None:
self.source = source
def resolve(
self, query: ResolutionQuery, *, actor_identity: str
) -> ResolutionResult:
try:
query.validate()
except ValueError as exc:
return ResolutionResult(
ResolutionState.INVALID_QUERY, detail=str(exc)
)
selectors = query.selectors()
if query.task_id:
record = self.source.get_task(
query.task_id, actor_identity=actor_identity
)
records = [record] if record is not None else []
else:
records = list(
self.source.list_tasks(actor_identity=actor_identity)
)
matches = [
record
for record in records
if record is not None and _matches(record, selectors)
]
if not matches:
return ResolutionResult(
ResolutionState.NOT_FOUND,
detail="no caller-authorized task exactly matches all selectors",
)
if len(matches) > 1:
ids = sorted(
str(_projection(record).get("task_id") or "")
for record in matches
)
return ResolutionResult(
ResolutionState.AMBIGUOUS,
candidate_task_ids=tuple(ids),
detail="multiple exact matches; resolver refuses to guess",
)
record = matches[0]
projected = _projection(record)
task_id = projected.get("task_id")
if not task_id:
return ResolutionResult(
ResolutionState.NOT_FOUND,
detail="matching record lacks authoritative task_id",
)
resolved = ResolvedTask(
task_id=task_id,
mission_id=projected.get("mission_id"),
root_task_id=projected.get("root_task_id"),
parent_task_id=projected.get("parent_task_id"),
owner_principal_id=projected.get("owner_principal_id"),
record=record,
matched_selectors=tuple(sorted(selectors)),
)
return ResolutionResult(ResolutionState.RESOLVED, resolved=resolved)
@@ -1,911 +0,0 @@
from __future__ import annotations
import hashlib
import json
import time
from dataclasses import asdict, dataclass, field
from enum import Enum
from typing import Any, Mapping, Protocol, Sequence
from .governance_control_kernel import (
ActionKind,
ActionLedger,
AutonomousDecisionEngine,
BlockerClassification,
GovernanceControlError,
ManifestStore,
NextAction,
ProducerKind,
)
class AuthorizationDenied(GovernanceControlError):
pass
class GovernedWriterUnavailable(GovernanceControlError):
pass
class OwnerActionRequired(GovernanceControlError):
pass
class GovernedWriterState(str, Enum):
PREFLIGHT_FROZEN = "PREFLIGHT_FROZEN"
OWNER_AUTHORIZED = "OWNER_AUTHORIZED"
EPOCH_VALID = "EPOCH_VALID"
TASK_CLAIMED = "TASK_CLAIMED"
TASK_LEASED = "TASK_LEASED"
PRINCIPAL_ISSUED = "PRINCIPAL_ISSUED"
JOB_BOUND = "JOB_BOUND"
CAS_VERIFIED = "CAS_VERIFIED"
WRITER_LEASED = "WRITER_LEASED"
RISK_ORDER = {
"low": 10,
"medium": 20,
"high": 30,
"critical": 40,
}
def _canonical(value: Any) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _digest(value: Any) -> str:
return "sha256:" + hashlib.sha256(_canonical(value)).hexdigest()
@dataclass(frozen=True)
class OwnerAuthorizationPolicy:
"""Long-lived business authorization, separate from short-lived credentials."""
authorization_id: str
subject: str
repository: str
allowed_branch_prefixes: tuple[str, ...]
allowed_capabilities: frozenset[str]
risk_ceiling: str
trust_domain: str
revocation_epoch: int
not_before: float
expires_at: float | None = None
revoked: bool = False
allowed_operations: frozenset[str] = frozenset(
{
"claim",
"heartbeat",
"task_lease",
"task_principal",
"job_reconcile",
"writer_lease",
"governed_commit",
"readback",
"reconcile",
"epoch_reissue",
"successor_recovery",
}
)
def validate(self, request: "GovernedWriterRequest", *, now: float | None = None) -> None:
now = time.time() if now is None else float(now)
if self.revoked:
raise AuthorizationDenied("owner authorization revoked")
if now < self.not_before:
raise AuthorizationDenied("owner authorization not yet valid")
if self.expires_at is not None and now >= self.expires_at:
raise AuthorizationDenied("owner authorization expired")
if request.subject != self.subject:
raise AuthorizationDenied("subject mismatch")
if request.repository != self.repository:
raise AuthorizationDenied("repository outside authorization")
if not any(request.target_branch.startswith(p) for p in self.allowed_branch_prefixes):
raise AuthorizationDenied("branch outside authorization")
if not request.required_capabilities.issubset(self.allowed_capabilities):
missing = sorted(request.required_capabilities - self.allowed_capabilities)
raise AuthorizationDenied("capability outside authorization: " + ",".join(missing))
ceiling = RISK_ORDER.get(self.risk_ceiling.lower())
requested = RISK_ORDER.get(request.risk.lower())
if ceiling is None or requested is None:
raise AuthorizationDenied("unknown risk level")
if requested > ceiling:
raise AuthorizationDenied("risk exceeds authorization ceiling")
@property
def policy_fingerprint(self) -> str:
return _digest(
{
"authorization_id": self.authorization_id,
"subject": self.subject,
"repository": self.repository,
"allowed_branch_prefixes": list(self.allowed_branch_prefixes),
"allowed_capabilities": sorted(self.allowed_capabilities),
"risk_ceiling": self.risk_ceiling,
"trust_domain": self.trust_domain,
"revocation_epoch": self.revocation_epoch,
"not_before": self.not_before,
"expires_at": self.expires_at,
"allowed_operations": sorted(self.allowed_operations),
}
)
@dataclass(frozen=True)
class GovernedWriterRequest:
subject: str
task_id: str
repository: str
target_branch: str
risk: str
generation: int
base_oid: str
required_capabilities: frozenset[str]
file_allowlist: tuple[str, ...]
plan_digest: str
manifest_id: str
runtime_fingerprint: str
process_start_identity: str
@property
def scope_fingerprint(self) -> str:
return _digest(
{
"subject": self.subject,
"task_id": self.task_id,
"repository": self.repository,
"target_branch": self.target_branch,
"risk": self.risk,
"generation": self.generation,
"base_oid": self.base_oid,
"required_capabilities": sorted(self.required_capabilities),
"file_allowlist": list(self.file_allowlist),
"plan_digest": self.plan_digest,
}
)
@dataclass(frozen=True)
class GovernedWriteReadiness:
"""Unambiguous write-path semantics.
`legacy_global_writes_enabled=False` is a required safety invariant, not a
blocker. `governed_write_path_open` is derived from the scoped writer
prerequisites below and is intentionally independent from any ambiguous
legacy `writes_enabled` field.
"""
legacy_global_writes_enabled: bool
task_principal_writes_enabled: bool
task_principal_verifier_ready: bool
governed_write_blockers: tuple[str, ...] = ()
@property
def blockers(self) -> tuple[str, ...]:
blockers = list(self.governed_write_blockers)
if self.legacy_global_writes_enabled:
blockers.append("LEGACY_GLOBAL_WRITES_MUST_REMAIN_DISABLED")
if not self.task_principal_writes_enabled:
blockers.append("TASK_PRINCIPAL_WRITES_DISABLED")
if not self.task_principal_verifier_ready:
blockers.append("TASK_PRINCIPAL_VERIFIER_NOT_READY")
return tuple(dict.fromkeys(blockers))
@property
def governed_write_path_open(self) -> bool:
return not self.blockers
@classmethod
def from_runtime_status(cls, status: Mapping[str, Any]) -> "GovernedWriteReadiness":
# Deliberately ignore ambiguous `writes_enabled` /
# `AIOS_GITOPS_WRITES_ENABLED`; those describe legacy/global surfaces
# in older control planes and must never be used as the sole governed
# writer readiness signal.
readiness = status.get("task_principal_readiness") or {}
blockers = status.get("governed_write_blockers")
if blockers is None:
blockers = status.get("blockers") or ()
return cls(
legacy_global_writes_enabled=bool(
status.get(
"legacy_global_writes_enabled",
status.get("legacy_writes_enabled", False),
)
),
task_principal_writes_enabled=bool(
status.get("task_principal_writes_enabled", False)
),
task_principal_verifier_ready=bool(
readiness.get("ready", status.get("task_principal_verifier_ready", False))
),
governed_write_blockers=tuple(str(x) for x in blockers),
)
@dataclass(frozen=True)
class EpochStatus:
epoch_id: str
scope_fingerprint: str
expires_at: float
policy_digest: str
def is_valid(self, *, now: float, minimum_remaining_ttl: float) -> bool:
return (self.expires_at - now) >= minimum_remaining_ttl
@dataclass(frozen=True)
class TaskState:
task_id: str
status: str
revision: int
claimed_by: str | None = None
@dataclass(frozen=True)
class ClaimReceipt:
claim_id: str
task_id: str
actor: str
fencing: int
@dataclass(frozen=True)
class TaskLeaseReceipt:
task_lease_id: str
task_id: str
token_ref: str
expires_at: float
fencing: int
@dataclass(frozen=True)
class TaskPrincipalReceipt:
principal_id: str
task_id: str
capabilities: frozenset[str]
expires_at: float
@dataclass(frozen=True)
class JobBindingReceipt:
job_id: str
task_id: str
repository: str
target_branch: str
generation: int
@dataclass(frozen=True)
class BranchReadback:
exists: bool
head_oid: str | None
@dataclass(frozen=True)
class WriterLeaseReceipt:
writer_lease_id: str
task_id: str
job_id: str
repository: str
target_branch: str
branch_mode: str
expected_branch_oid: str
generation: int
expires_at: float
fencing: int
@dataclass(frozen=True)
class GovernedWriterSession:
state: GovernedWriterState
authorization_id: str
policy_fingerprint: str
scope_fingerprint: str
epoch_id: str
task_id: str
claim_id: str
task_lease_id: str
principal_id: str
job_id: str
writer_lease_id: str
branch_mode: str
expected_branch_oid: str
expires_at: float
evidence_refs: tuple[str, ...] = ()
def to_dict(self) -> dict[str, Any]:
result = asdict(self)
result["state"] = self.state.value
return result
@dataclass(frozen=True)
class RecoveryAuthorization:
"""Pre-approved signed recovery/successor authorization reference."""
authorization_ref: str
task_id: str
action: str # TERMINAL_RECOVERY | SUCCESSOR
expires_at: float
class TrustedSignerAdapter(Protocol):
def reissue_epoch(
self,
*,
owner_policy: OwnerAuthorizationPolicy,
request: GovernedWriterRequest,
prior_epoch: EpochStatus | None,
minimum_remaining_ttl: float,
idempotency_key: str,
) -> EpochStatus: ...
class ExecutionPlane(Protocol):
def read_task(self, task_id: str) -> TaskState: ...
def apply_recovery(
self,
*,
task: TaskState,
authorization: RecoveryAuthorization,
idempotency_key: str,
) -> TaskState: ...
def claim(
self,
*,
task: TaskState,
actor: str,
idempotency_key: str,
) -> ClaimReceipt: ...
def heartbeat(self, *, claim: ClaimReceipt) -> None: ...
def issue_task_lease(
self,
*,
claim: ClaimReceipt,
idempotency_key: str,
) -> TaskLeaseReceipt: ...
class TaskPrincipalIssuer(Protocol):
def issue(
self,
*,
lease: TaskLeaseReceipt,
requested_capabilities: frozenset[str],
idempotency_key: str,
) -> TaskPrincipalReceipt: ...
class JobAuthority(Protocol):
def read_or_reconcile(
self,
*,
task_id: str,
principal: TaskPrincipalReceipt,
idempotency_key: str,
) -> JobBindingReceipt: ...
class GitAuthority(Protocol):
def read_branch(self, *, repository: str, target_branch: str) -> BranchReadback: ...
class WriterLeaseAuthority(Protocol):
def acquire(
self,
*,
task_id: str,
job: JobBindingReceipt,
principal: TaskPrincipalReceipt,
repository: str,
target_branch: str,
branch_mode: str,
base_oid: str,
expected_branch_oid: str,
generation: int,
idempotency_key: str,
) -> WriterLeaseReceipt: ...
@dataclass(frozen=True)
class VerifiedOwnerAuthorization:
policy: OwnerAuthorizationPolicy
authorization_ref: str
verification_receipt: str
verified_at: float
class OwnerAuthorizationAuthority(Protocol):
def verify(
self,
*,
authorization_ref: str,
request: GovernedWriterRequest,
) -> VerifiedOwnerAuthorization: ...
@dataclass
class GovernedWriterAdapters:
signer: TrustedSignerAdapter
execution: ExecutionPlane
principal_issuer: TaskPrincipalIssuer
jobs: JobAuthority
git: GitAuthority
writer_lease: WriterLeaseAuthority
owner_authority: OwnerAuthorizationAuthority | None = None
class GovernedWriterAutomation:
"""Owner-policy-driven acquisition of a scoped governed writer capability.
This is the mechanism that removes repeated manual intervention. Long-lived
OwnerAuthorizationPolicy establishes allowed scope. Every concrete effect is
still protected by fresh runtime manifest, short-lived TaskLease,
TaskPrincipal, WriterLease, CAS and idempotency.
"""
def __init__(
self,
*,
manifest_store: ManifestStore,
action_ledger: ActionLedger,
decision_engine: AutonomousDecisionEngine | None = None,
):
self.manifest_store = manifest_store
self.action_ledger = action_ledger
self.decision_engine = decision_engine or AutonomousDecisionEngine()
def _begin_action(
self,
*,
request: GovernedWriterRequest,
suffix: str,
kind: ActionKind,
target: str,
precondition: Mapping[str, Any],
):
return self.action_ledger.begin(
idempotency_key=f"writer:{request.scope_fingerprint}:{suffix}",
manifest_id=request.manifest_id,
phase="GOVERNED_WRITER",
kind=kind,
target=target,
precondition_hash=_digest(precondition),
)
def _ensure_epoch(
self,
*,
request: GovernedWriterRequest,
owner_policy: OwnerAuthorizationPolicy,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
now: float,
minimum_remaining_ttl: float,
) -> EpochStatus:
if (
epoch is not None
and epoch.scope_fingerprint == request.scope_fingerprint
and epoch.is_valid(now=now, minimum_remaining_ttl=minimum_remaining_ttl)
):
return epoch
# Changed scope may not silently inherit an old Owner grant.
if epoch is not None and epoch.scope_fingerprint != request.scope_fingerprint:
raise OwnerActionRequired("epoch scope changed; new Owner authorization required")
ticket = self._begin_action(
request=request,
suffix="epoch",
kind=ActionKind.EPOCH_ISSUANCE,
target="trusted-grant-signer-adapter",
precondition={
"owner_policy": owner_policy.policy_fingerprint,
"scope": request.scope_fingerprint,
"prior_epoch": None if epoch is None else epoch.epoch_id,
},
)
if ticket.reused and ticket.requires_reconcile:
raise GovernedWriterUnavailable("epoch issuance effect unknown; reconcile required")
if ticket.reused and ticket.state.value == "SUCCEEDED":
# The ledger proves the action succeeded, but the concrete receipt
# must be obtained from the signer/readback, not fabricated locally.
raise GovernedWriterUnavailable("epoch already issued; signer readback required")
try:
fresh = adapters.signer.reissue_epoch(
owner_policy=owner_policy,
request=request,
prior_epoch=epoch,
minimum_remaining_ttl=minimum_remaining_ttl,
idempotency_key=ticket.idempotency_key,
)
if fresh.scope_fingerprint != request.scope_fingerprint:
raise GovernedWriterUnavailable("signer returned mismatched scope")
if not fresh.is_valid(now=now, minimum_remaining_ttl=minimum_remaining_ttl):
raise GovernedWriterUnavailable("signer returned insufficient TTL")
self.action_ledger.mark_succeeded(
ticket.action_id,
result={
"epoch_id": fresh.epoch_id,
"expires_at": fresh.expires_at,
"scope_fingerprint": fresh.scope_fingerprint,
},
)
return fresh
except OwnerActionRequired:
self.action_ledger.mark_failed(ticket.action_id, result={"owner_action": True})
raise
except Exception as exc:
self.action_ledger.mark_effect_unknown(
ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
def _ensure_claimable_task(
self,
*,
request: GovernedWriterRequest,
recovery_authorization: RecoveryAuthorization | None,
adapters: GovernedWriterAdapters,
now: float,
) -> TaskState:
task = adapters.execution.read_task(request.task_id)
status = task.status.upper()
if status == "QUEUED":
return task
if status in {"CLAIMED", "RUNNING"}:
return task
if status == "EXPIRED":
if recovery_authorization is None:
raise OwnerActionRequired(
"terminal task expired; owner-signed recovery/successor authorization required"
)
if recovery_authorization.task_id != task.task_id:
raise AuthorizationDenied("recovery authorization task mismatch")
if recovery_authorization.expires_at <= now:
raise AuthorizationDenied("recovery authorization expired")
if recovery_authorization.action not in {"TERMINAL_RECOVERY", "SUCCESSOR"}:
raise AuthorizationDenied("unsupported recovery action")
ticket = self._begin_action(
request=request,
suffix="task-recovery",
kind=ActionKind.GENERIC_MUTATION,
target="agent-task-terminal-recovery",
precondition={
"task_id": task.task_id,
"revision": task.revision,
"status": task.status,
"authorization_ref": recovery_authorization.authorization_ref,
"action": recovery_authorization.action,
},
)
if ticket.reused and ticket.requires_reconcile:
raise GovernedWriterUnavailable("task recovery effect unknown; reconcile required")
try:
recovered = adapters.execution.apply_recovery(
task=task,
authorization=recovery_authorization,
idempotency_key=ticket.idempotency_key,
)
if recovered.status.upper() != "QUEUED":
raise GovernedWriterUnavailable(
"recovery did not return claimable QUEUED task"
)
self.action_ledger.mark_succeeded(
ticket.action_id,
result={
"task_id": recovered.task_id,
"status": recovered.status,
"revision": recovered.revision,
},
)
return recovered
except Exception as exc:
self.action_ledger.mark_effect_unknown(
ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
raise GovernedWriterUnavailable(f"task status not claimable: {task.status}")
def acquire_verified(
self,
*,
request: GovernedWriterRequest,
authorization_ref: str,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
"""Production entry: verify Owner authorization before any effect.
The field composition root MUST use this method. `acquire()` is an
internal/test hook for already-verified policy objects.
"""
if adapters.owner_authority is None:
raise AuthorizationDenied("OwnerAuthorizationAuthority is not configured")
verified = adapters.owner_authority.verify(
authorization_ref=authorization_ref,
request=request,
)
if verified.authorization_ref != authorization_ref:
raise AuthorizationDenied("owner authorization reference mismatch")
if not verified.verification_receipt:
raise AuthorizationDenied("owner authorization verification receipt missing")
return self.acquire(
request=request,
owner_policy=verified.policy,
readiness=readiness,
epoch=epoch,
adapters=adapters,
recovery_authorization=recovery_authorization,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
def acquire(
self,
*,
request: GovernedWriterRequest,
owner_policy: OwnerAuthorizationPolicy,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
now = time.time() if now is None else float(now)
# Gate 1: authoritative frozen runtime.
self.manifest_store.assert_current(
manifest_id=request.manifest_id,
runtime_fingerprint=request.runtime_fingerprint,
process_start_identity=request.process_start_identity,
)
# Gate 2: long-lived Owner policy.
owner_policy.validate(request, now=now)
# Gate 3: scoped writer readiness, deliberately independent from legacy
# global-write switches.
if not readiness.governed_write_path_open:
raise GovernedWriterUnavailable(
"governed writer not ready: " + ",".join(readiness.blockers)
)
# Gate 4: JIT delegation epoch. If unchanged scope is authorized, the
# Agent may use the trusted signer adapter autonomously.
epoch = self._ensure_epoch(
request=request,
owner_policy=owner_policy,
epoch=epoch,
adapters=adapters,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
# Gate 5: task must be claimable, with Owner recovery only when the
# lifecycle contract explicitly requires a signed terminal recovery.
task = self._ensure_claimable_task(
request=request,
recovery_authorization=recovery_authorization,
adapters=adapters,
now=now,
)
# Claim is idempotent per scope/task revision.
claim_ticket = self._begin_action(
request=request,
suffix=f"claim-r{task.revision}",
kind=ActionKind.CLAIM,
target=f"task:{task.task_id}",
precondition={"task_id": task.task_id, "revision": task.revision, "status": task.status},
)
if claim_ticket.reused and claim_ticket.requires_reconcile:
raise GovernedWriterUnavailable("claim effect unknown; reconcile required")
if task.status.upper() in {"CLAIMED", "RUNNING"}:
raise GovernedWriterUnavailable(
"existing claim must be reconciled into a concrete claim receipt"
)
try:
claim = adapters.execution.claim(
task=task,
actor=request.subject,
idempotency_key=claim_ticket.idempotency_key,
)
adapters.execution.heartbeat(claim=claim)
self.action_ledger.mark_succeeded(
claim_ticket.action_id,
result={"claim_id": claim.claim_id, "fencing": claim.fencing},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
claim_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
lease_ticket = self._begin_action(
request=request,
suffix=f"tasklease-{claim.claim_id}",
kind=ActionKind.GENERIC_MUTATION,
target="task-lease",
precondition={"claim_id": claim.claim_id, "fencing": claim.fencing},
)
try:
task_lease = adapters.execution.issue_task_lease(
claim=claim,
idempotency_key=lease_ticket.idempotency_key,
)
self.action_ledger.mark_succeeded(
lease_ticket.action_id,
result={
"task_lease_id": task_lease.task_lease_id,
"expires_at": task_lease.expires_at,
},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
lease_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
principal_ticket = self._begin_action(
request=request,
suffix=f"principal-{task_lease.task_lease_id}",
kind=ActionKind.GENERIC_MUTATION,
target="task-principal-issuer",
precondition={
"task_lease_id": task_lease.task_lease_id,
"capabilities": sorted(request.required_capabilities),
},
)
try:
principal = adapters.principal_issuer.issue(
lease=task_lease,
requested_capabilities=request.required_capabilities,
idempotency_key=principal_ticket.idempotency_key,
)
if not request.required_capabilities.issubset(principal.capabilities):
raise GovernedWriterUnavailable(
"issued TaskPrincipal lacks required capabilities"
)
self.action_ledger.mark_succeeded(
principal_ticket.action_id,
result={"principal_id": principal.principal_id, "expires_at": principal.expires_at},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
principal_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
job_ticket = self._begin_action(
request=request,
suffix="job",
kind=ActionKind.GENERIC_MUTATION,
target="gitops-job",
precondition={
"task_id": task.task_id,
"principal_id": principal.principal_id,
"plan_digest": request.plan_digest,
},
)
try:
job = adapters.jobs.read_or_reconcile(
task_id=task.task_id,
principal=principal,
idempotency_key=job_ticket.idempotency_key,
)
if (
job.repository != request.repository
or job.target_branch != request.target_branch
or job.generation != request.generation
):
raise GovernedWriterUnavailable("job binding mismatch")
self.action_ledger.mark_succeeded(
job_ticket.action_id,
result={"job_id": job.job_id},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
job_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
branch = adapters.git.read_branch(
repository=request.repository,
target_branch=request.target_branch,
)
if branch.exists:
if not branch.head_oid:
raise GovernedWriterUnavailable("branch exists without readable HEAD")
branch_mode = "CONTINUE_EXISTING"
expected_branch_oid = branch.head_oid
else:
branch_mode = "CREATE_NEW"
expected_branch_oid = request.base_oid
writer_ticket = self._begin_action(
request=request,
suffix=f"writer-{job.job_id}-{expected_branch_oid}",
kind=ActionKind.GENERIC_MUTATION,
target="writer-lease",
precondition={
"job_id": job.job_id,
"principal_id": principal.principal_id,
"branch_mode": branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": expected_branch_oid,
"generation": request.generation,
},
)
try:
writer = adapters.writer_lease.acquire(
task_id=task.task_id,
job=job,
principal=principal,
repository=request.repository,
target_branch=request.target_branch,
branch_mode=branch_mode,
base_oid=request.base_oid,
expected_branch_oid=expected_branch_oid,
generation=request.generation,
idempotency_key=writer_ticket.idempotency_key,
)
if writer.expected_branch_oid != expected_branch_oid:
raise GovernedWriterUnavailable("WriterLease CAS mismatch")
self.action_ledger.mark_succeeded(
writer_ticket.action_id,
result={
"writer_lease_id": writer.writer_lease_id,
"expires_at": writer.expires_at,
"expected_branch_oid": writer.expected_branch_oid,
},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
writer_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
expires_at = min(
epoch.expires_at,
task_lease.expires_at,
principal.expires_at,
writer.expires_at,
)
return GovernedWriterSession(
state=GovernedWriterState.WRITER_LEASED,
authorization_id=owner_policy.authorization_id,
policy_fingerprint=owner_policy.policy_fingerprint,
scope_fingerprint=request.scope_fingerprint,
epoch_id=epoch.epoch_id,
task_id=task.task_id,
claim_id=claim.claim_id,
task_lease_id=task_lease.task_lease_id,
principal_id=principal.principal_id,
job_id=job.job_id,
writer_lease_id=writer.writer_lease_id,
branch_mode=branch_mode,
expected_branch_oid=expected_branch_oid,
expires_at=expires_at,
)
@@ -1,859 +0,0 @@
"""Live AIOS protocol adapters for governed-writer execution.
This module closes the gap between the generic governed-writer model and the
live field contracts observed on the V2R2C3 control plane:
- agent-task-api :18940
* POST /v1/agents/register
* GET /v1/tasks/{task_id}
* POST /v1/tasks/{task_id}/claim -> lease_token
* POST /v1/tasks/{task_id}/heartbeat
- task-principal-issuer :18946
* POST /v1/task-principal/assertions
* Authorization: TaskLease <lease-token>
* one assertion per concrete operation + bridge_request
- bridge :18892
* GET /v2/runtime
* POST /v2/writer-lease
* POST /v2/commit-files
* Authorization: TaskPrincipal <operation-assertion>
WriterLease and commit-files share one continuation idempotency key.
The module never prints or returns the agent token, lease token or TaskPrincipal
assertion. Secret values are kept only in an in-memory handle store.
"""
from __future__ import annotations
import base64
import hashlib
import json
import os
import secrets
import subprocess
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Mapping, Sequence
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
GovernedWriteReadiness,
GovernedWriterRequest,
OwnerAuthorizationPolicy,
)
class LiveProtocolError(RuntimeError):
pass
class LiveProtocolAuthError(LiveProtocolError):
pass
class LiveProtocolConflict(LiveProtocolError):
pass
class LiveProtocolUnavailable(LiveProtocolError):
pass
@dataclass(frozen=True)
class HttpResult:
status: int
body: Mapping[str, Any]
class JsonHttpTransport:
"""Small stdlib JSON transport. Authorization values are never logged."""
def request(
self,
method: str,
url: str,
*,
body: Mapping[str, Any] | None = None,
headers: Mapping[str, str] | None = None,
timeout: float = 20.0,
) -> HttpResult:
payload = None
req_headers = {"Accept": "application/json"}
if body is not None:
payload = json.dumps(
dict(body),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
req_headers["Content-Type"] = "application/json"
if headers:
req_headers.update({str(k): str(v) for k, v in headers.items()})
req = urllib.request.Request(
url,
data=payload,
headers=req_headers,
method=method.upper(),
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read()
status = int(resp.status)
except urllib.error.HTTPError as exc:
raw = exc.read()
status = int(exc.code)
except (OSError, urllib.error.URLError) as exc:
raise LiveProtocolUnavailable(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: transport unavailable"
) from exc
if not raw:
obj: Any = {}
else:
try:
obj = json.loads(raw.decode("utf-8"))
except Exception as exc:
raise LiveProtocolError(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: non-JSON response"
) from exc
if not isinstance(obj, Mapping):
raise LiveProtocolError(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: response must be object"
)
return HttpResult(status=status, body=dict(obj))
@dataclass(frozen=True)
class LiveProtocolConfig:
bridge_url: str = "http://127.0.0.1:18892"
agent_task_url: str = "http://127.0.0.1:18940"
principal_issuer_url: str = "http://127.0.0.1:18946"
control_token_file: str = (
"/run/credentials/aios-agent-task-api.service/control_token"
)
authority_git_dir: str = "/data/aios-gitops/repo.git"
issue_schema: str = "aios.task-principal-issue.v1"
agent_type: str = "aios.p0-task-admission-worker"
agent_version: str = "governed-writer-live-v1"
lease_seconds: int = 900
assertion_ttl_seconds: int = 60
minimum_remaining_window_seconds: int = 240
def normalized(self) -> "LiveProtocolConfig":
return LiveProtocolConfig(
bridge_url=self.bridge_url.rstrip("/"),
agent_task_url=self.agent_task_url.rstrip("/"),
principal_issuer_url=self.principal_issuer_url.rstrip("/"),
control_token_file=self.control_token_file,
authority_git_dir=self.authority_git_dir,
issue_schema=self.issue_schema,
agent_type=self.agent_type,
agent_version=self.agent_version,
lease_seconds=int(self.lease_seconds),
assertion_ttl_seconds=int(self.assertion_ttl_seconds),
minimum_remaining_window_seconds=int(
self.minimum_remaining_window_seconds
),
)
class SecretHandleStore:
"""Process-local secret container.
Public receipts contain handles only; secret values are not serialized.
"""
def __init__(self) -> None:
self._values: dict[str, str] = {}
def put(self, kind: str, value: str) -> str:
if not isinstance(value, str) or not value:
raise LiveProtocolAuthError(f"{kind} secret missing")
handle = f"{kind}:{secrets.token_hex(12)}"
self._values[handle] = value
return handle
def get(self, handle: str) -> str:
try:
return self._values[handle]
except KeyError as exc:
raise LiveProtocolAuthError("secret handle not available") from exc
def drop(self, handle: str) -> None:
self._values.pop(handle, None)
@dataclass(frozen=True)
class LiveTask:
task_id: str
status: str
revision: int
deadline_epoch: float | None
raw: Mapping[str, Any]
@dataclass(frozen=True)
class LiveClaim:
task_id: str
claim_ref: str
server_claim_id: str | None
lease_handle: str
fencing_generation: int | None
lease_expires_at: float | None
task_revision: int | None
@dataclass(frozen=True)
class LiveWriterLease:
writer_lease_id: str
task_id: str
job_id: str
branch_mode: str
expected_branch_oid: str
generation: int
expires_at: float | None
continuation_key: str
raw: Mapping[str, Any]
@dataclass(frozen=True)
class LiveWriterSession:
authorization_id: str
task_id: str
agent_id: str
claim_ref: str
job_id: str
branch_mode: str
expected_branch_oid: str
writer_lease_id: str
continuation_key: str
principal_issue_schema: str
lease_handle: str = field(repr=False)
def public_dict(self) -> dict[str, Any]:
return {
"authorization_id": self.authorization_id,
"task_id": self.task_id,
"agent_id": self.agent_id,
"claim_ref": self.claim_ref,
"job_id": self.job_id,
"branch_mode": self.branch_mode,
"expected_branch_oid": self.expected_branch_oid,
"writer_lease_id": self.writer_lease_id,
"continuation_key": self.continuation_key,
"principal_issue_schema": self.principal_issue_schema,
}
def _parse_epoch(value: Any) -> float | None:
if value is None:
return None
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str):
text = value.strip()
if not text:
return None
try:
return float(text)
except ValueError:
pass
# ISO-8601 minimal support.
import datetime as dt
try:
return dt.datetime.fromisoformat(text.replace("Z", "+00:00")).timestamp()
except Exception:
return None
return None
def _safe_ref_from_token(token: str, prefix: str) -> str:
return f"{prefix}:sha256:{hashlib.sha256(token.encode('utf-8')).hexdigest()[:24]}"
class LiveAgentTaskClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
secrets_store: SecretHandleStore | None = None,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.secrets = secrets_store or SecretHandleStore()
def _control_token(self) -> str:
path = Path(self.config.control_token_file)
try:
token = path.read_text(encoding="utf-8").strip()
except OSError as exc:
raise LiveProtocolAuthError(
"agent-task control token file unavailable"
) from exc
if len(token) < 8:
raise LiveProtocolAuthError("agent-task control token invalid")
return token
def _control_headers(self) -> dict[str, str]:
return {"Authorization": "Bearer " + self._control_token()}
def read_task(self, task_id: str) -> LiveTask:
quoted = urllib.parse.quote(task_id, safe="")
result = self.transport.request(
"GET",
f"{self.config.agent_task_url}/v1/tasks/{quoted}",
headers=self._control_headers(),
timeout=15,
)
if result.status == 404:
raise LiveProtocolConflict("task not found")
if result.status not in (200, 201):
raise LiveProtocolError(f"task read failed HTTP {result.status}")
raw = result.body
task_obj = raw.get("task") if isinstance(raw.get("task"), Mapping) else raw
status = str(task_obj.get("status") or "")
if not status:
raise LiveProtocolError("task readback missing status")
revision = int(task_obj.get("revision") or 0)
return LiveTask(
task_id=str(task_obj.get("task_id") or task_id),
status=status,
revision=revision,
deadline_epoch=_parse_epoch(task_obj.get("deadline")),
raw=dict(task_obj),
)
def register_agent(
self,
*,
task_id: str,
capabilities: Sequence[str],
route: str,
agent_id: str | None = None,
) -> tuple[str, str]:
if agent_id is None:
agent_id = (
"governed-writer-"
+ hashlib.sha256(
(task_id + "\0" + str(time.time_ns())).encode("utf-8")
).hexdigest()[:20]
)
body = {
"agent_id": agent_id,
"agent_type": self.config.agent_type,
"agent_version": self.config.agent_version,
"capabilities": list(capabilities),
"metadata": {"task_id": task_id, "route": route},
}
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/agents/register",
body=body,
headers=self._control_headers(),
timeout=15,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"agent register failed HTTP {result.status}")
token = result.body.get("agent_token")
if not isinstance(token, str) or len(token) < 8:
raise LiveProtocolAuthError("agent register returned no usable token")
return agent_id, self.secrets.put("agent-token", token)
def claim(
self,
*,
task_id: str,
agent_token_handle: str,
lease_seconds: int | None = None,
) -> LiveClaim:
lease_seconds = int(lease_seconds or self.config.lease_seconds)
quoted = urllib.parse.quote(task_id, safe="")
token = self.secrets.get(agent_token_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/claim",
body={"lease_seconds": lease_seconds},
headers={"Authorization": "Bearer " + token},
timeout=15,
)
if result.status == 409:
raise LiveProtocolConflict(
str(result.body.get("error") or "task not claimable")
)
if result.status not in (200, 201):
raise LiveProtocolError(f"claim failed HTTP {result.status}")
if result.body.get("status") != "CLAIMED":
raise LiveProtocolError("claim response not CLAIMED")
lease_token = result.body.get("lease_token")
if not isinstance(lease_token, str) or len(lease_token) < 8:
raise LiveProtocolAuthError("claim returned no lease token")
server_claim_id = result.body.get("claim_id")
lease_id = result.body.get("lease_id")
public_ref = None
for candidate in (server_claim_id, lease_id):
if isinstance(candidate, str) and candidate:
public_ref = candidate
break
if public_ref is None:
public_ref = _safe_ref_from_token(lease_token, "claim")
lease_handle = self.secrets.put("task-lease", lease_token)
fencing = result.body.get("fencing_generation")
return LiveClaim(
task_id=task_id,
claim_ref=public_ref,
server_claim_id=(
str(server_claim_id)
if isinstance(server_claim_id, str) and server_claim_id
else None
),
lease_handle=lease_handle,
fencing_generation=int(fencing) if fencing is not None else None,
lease_expires_at=_parse_epoch(result.body.get("lease_expires_at")),
task_revision=(
int(result.body["receipt_revision"])
if result.body.get("receipt_revision") is not None
else None
),
)
def heartbeat(
self,
*,
claim: LiveClaim,
agent_token_handle: str,
phase: str,
lease_seconds: int | None = None,
) -> LiveTask:
lease_seconds = int(lease_seconds or self.config.lease_seconds)
quoted = urllib.parse.quote(claim.task_id, safe="")
agent_token = self.secrets.get(agent_token_handle)
lease_token = self.secrets.get(claim.lease_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/heartbeat",
body={
"lease_token": lease_token,
"lease_seconds": lease_seconds,
"progress": {"phase": phase, "status": "running"},
},
headers={"Authorization": "Bearer " + agent_token},
timeout=15,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"heartbeat failed HTTP {result.status}")
if result.body.get("status") not in ("RUNNING", "CLAIMED"):
raise LiveProtocolError("heartbeat did not yield RUNNING/CLAIMED")
task = self.read_task(claim.task_id)
if task.status not in ("RUNNING", "CLAIMED"):
raise LiveProtocolConflict(
f"authoritative task state after heartbeat is {task.status}"
)
return task
class LiveTaskPrincipalClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
secrets_store: SecretHandleStore,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.secrets = secrets_store
def issue_assertion(
self,
*,
task_id: str,
lease_handle: str,
operation: str,
bridge_request: Mapping[str, Any],
request_id: str,
) -> str:
lease_token = self.secrets.get(lease_handle)
body = {
"schema": self.config.issue_schema,
"request_id": request_id,
"task_id": task_id,
"operation": operation,
"bridge_request": dict(bridge_request),
"requested_ttl_seconds": self.config.assertion_ttl_seconds,
}
last: HttpResult | None = None
for attempt in range(2):
result = self.transport.request(
"POST",
f"{self.config.principal_issuer_url}/v1/task-principal/assertions",
body=body,
headers={"Authorization": "TaskLease " + lease_token},
timeout=20,
)
last = result
assertion = result.body.get("assertion")
if result.status in (200, 201) and isinstance(assertion, str) and assertion:
return assertion
if not (
attempt == 0
and result.status == 503
and result.body.get("error") == "TASK_PRINCIPAL_ISSUER_UNAVAILABLE"
):
break
time.sleep(1)
status = 0 if last is None else last.status
raise LiveProtocolError(
f"TaskPrincipal assertion issue failed operation={operation} HTTP {status}"
)
class LiveGitReader:
def __init__(self, git_dir: str) -> None:
self.git_dir = str(git_dir)
def branch_head(self, branch: str) -> str | None:
proc = subprocess.run(
["git", "--git-dir", self.git_dir, "rev-parse", f"refs/heads/{branch}"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
timeout=15,
)
if proc.returncode != 0:
return None
head = proc.stdout.strip()
if len(head) != 40 or any(c not in "0123456789abcdef" for c in head):
raise LiveProtocolError("git branch HEAD is not a full OID")
return head
def file_sha256(self, oid: str, path: str) -> str:
proc = subprocess.run(
["git", "--git-dir", self.git_dir, "show", f"{oid}:{path}"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=20,
)
if proc.returncode != 0:
raise LiveProtocolError(f"git readback failed for {path}")
return hashlib.sha256(proc.stdout).hexdigest()
class LiveBridgeClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
principal_client: LiveTaskPrincipalClient,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.principal_client = principal_client
def runtime_status(self) -> Mapping[str, Any]:
result = self.transport.request(
"GET", f"{self.config.bridge_url}/v2/runtime", timeout=10
)
if result.status != 200:
raise LiveProtocolUnavailable(f"bridge runtime HTTP {result.status}")
return result.body
def acquire_writer_lease(
self,
*,
task_id: str,
job_id: str,
lease_handle: str,
repository: str,
target_branch: str,
generation: int,
base_oid: str,
expected_branch_oid: str,
branch_mode: str,
continuation_key: str,
) -> LiveWriterLease:
req = {
"schema": "aios.task-writer-lease.v2",
"task_id": task_id,
"job_id": job_id,
"idempotency_key": continuation_key,
"repository": repository,
"branch_mode": branch_mode,
"base_oid": base_oid,
"expected_branch_oid": expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"lease_seconds": int(self.config.lease_seconds),
}
assertion = self.principal_client.issue_assertion(
task_id=task_id,
lease_handle=lease_handle,
operation="acquireWriterLease",
bridge_request=req,
request_id="writer-lease-" + secrets.token_hex(8),
)
result = self.transport.request(
"POST",
f"{self.config.bridge_url}/v2/writer-lease",
body=req,
headers={"Authorization": "TaskPrincipal " + assertion},
timeout=30,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"writer lease HTTP {result.status}")
body = result.body
if body.get("schema") != "aios.task-writer-lease.v2":
raise LiveProtocolError("writer lease schema mismatch")
if body.get("task_id") != task_id:
raise LiveProtocolError("writer lease task mismatch")
if body.get("expected_branch_oid") not in (None, expected_branch_oid):
raise LiveProtocolConflict("writer lease expected_branch_oid mismatch")
writer_lease_id = body.get("writer_lease_id") or body.get("lease_id")
if not isinstance(writer_lease_id, str) or not writer_lease_id:
raise LiveProtocolError("writer lease response missing lease identifier")
return LiveWriterLease(
writer_lease_id=writer_lease_id,
task_id=task_id,
job_id=job_id,
branch_mode=branch_mode,
expected_branch_oid=expected_branch_oid,
generation=int(generation),
expires_at=_parse_epoch(body.get("expires_at")),
continuation_key=continuation_key,
raw=dict(body),
)
def commit_files(
self,
*,
session: LiveWriterSession,
repository: str,
target_branch: str,
generation: int,
files: Mapping[str, bytes],
message: str,
) -> Mapping[str, Any]:
entries = [
{
"path": path,
"content_base64": base64.b64encode(data).decode("ascii"),
}
for path, data in files.items()
]
req = {
"task_id": session.task_id,
"job_id": session.job_id,
"repository": repository,
"expected_branch_oid": session.expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"idempotency_key": session.continuation_key,
"files": entries,
"message": message,
}
assertion = self.principal_client.issue_assertion(
task_id=session.task_id,
lease_handle=session.lease_handle,
operation="commitFilesGoverned",
bridge_request=req,
request_id="commit-files-" + secrets.token_hex(8),
)
result = self.transport.request(
"POST",
f"{self.config.bridge_url}/v2/commit-files",
body=req,
headers={"Authorization": "TaskPrincipal " + assertion},
timeout=45,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"commit-files HTTP {result.status}")
return dict(result.body)
class LiveGovernedWriterExecutor:
"""Execute the real post-admission writer chain.
Prerequisites:
- Owner authorization was already verified by the caller/authority.
- A fresh admission task exists and is QUEUED.
- The task's execution plan already binds the supplied job_id.
- A valid delegation epoch/admission window is already active.
This executor deliberately does not create/rewrite admission plans or sign
Owner epochs. Those remain in the existing admission/signer authority.
"""
def __init__(
self,
*,
config: LiveProtocolConfig,
owner_policy: OwnerAuthorizationPolicy,
authorization_ref: str,
transport: JsonHttpTransport | None = None,
) -> None:
self.config = config.normalized()
self.owner_policy = owner_policy
self.authorization_ref = authorization_ref
self.transport = transport or JsonHttpTransport()
self.secret_store = SecretHandleStore()
self.agent = LiveAgentTaskClient(
self.config,
transport=self.transport,
secrets_store=self.secret_store,
)
self.principals = LiveTaskPrincipalClient(
self.config,
transport=self.transport,
secrets_store=self.secret_store,
)
self.bridge = LiveBridgeClient(
self.config,
transport=self.transport,
principal_client=self.principals,
)
self.git = LiveGitReader(self.config.authority_git_dir)
def _assert_owner_scope(self, request: GovernedWriterRequest) -> None:
if self.authorization_ref != self.owner_policy.authorization_id:
raise AuthorizationDenied("authorization_ref does not match verified policy")
self.owner_policy.validate(request, now=time.time())
def acquire(
self,
*,
request: GovernedWriterRequest,
job_id: str,
route: str = "owner-authorized-governed-writer",
) -> LiveWriterSession:
self._assert_owner_scope(request)
runtime = self.bridge.runtime_status()
readiness = GovernedWriteReadiness.from_runtime_status(runtime)
if not readiness.governed_write_path_open:
raise LiveProtocolConflict(
"governed write path blocked: " + ",".join(readiness.blockers)
)
if readiness.legacy_global_writes_enabled:
raise LiveProtocolConflict("legacy/global writes must remain disabled")
task = self.agent.read_task(request.task_id)
if task.status != "QUEUED":
raise LiveProtocolConflict(
f"task must be QUEUED before claim; live={task.status}"
)
now = time.time()
if (
task.deadline_epoch is not None
and task.deadline_epoch - now
< self.config.minimum_remaining_window_seconds
):
raise LiveProtocolConflict("task deadline too close to safely claim")
agent_id, agent_token_handle = self.agent.register_agent(
task_id=request.task_id,
capabilities=sorted(request.required_capabilities),
route=route,
)
claim = self.agent.claim(
task_id=request.task_id,
agent_token_handle=agent_token_handle,
)
running = self.agent.heartbeat(
claim=claim,
agent_token_handle=agent_token_handle,
phase=route,
)
if (
running.deadline_epoch is not None
and running.deadline_epoch - time.time()
< self.config.minimum_remaining_window_seconds
):
raise LiveProtocolConflict(
"remaining governed task window too short after heartbeat"
)
live_head = self.git.branch_head(request.target_branch)
if live_head is None:
branch_mode = "CREATE_NEW"
expected_branch_oid = request.base_oid
else:
branch_mode = "CONTINUE_EXISTING"
expected_branch_oid = live_head
continuation_key = (
"gw-cont-"
+ hashlib.sha256(
(request.task_id + "\0" + job_id).encode("utf-8")
).hexdigest()[:32]
)
writer = self.bridge.acquire_writer_lease(
task_id=request.task_id,
job_id=job_id,
lease_handle=claim.lease_handle,
repository=request.repository,
target_branch=request.target_branch,
generation=request.generation,
base_oid=request.base_oid,
expected_branch_oid=expected_branch_oid,
branch_mode=branch_mode,
continuation_key=continuation_key,
)
return LiveWriterSession(
authorization_id=self.authorization_ref,
task_id=request.task_id,
agent_id=agent_id,
claim_ref=claim.claim_ref,
job_id=job_id,
branch_mode=writer.branch_mode,
expected_branch_oid=writer.expected_branch_oid,
writer_lease_id=writer.writer_lease_id,
continuation_key=writer.continuation_key,
principal_issue_schema=self.config.issue_schema,
lease_handle=claim.lease_handle,
)
def commit_and_readback(
self,
*,
session: LiveWriterSession,
request: GovernedWriterRequest,
files: Mapping[str, bytes],
expected_sha256: Mapping[str, str],
message: str,
) -> Mapping[str, Any]:
self._assert_owner_scope(request)
if set(files) != set(expected_sha256):
raise LiveProtocolConflict("commit file set differs from expected SHA set")
for path, data in files.items():
got = hashlib.sha256(data).hexdigest()
if got != expected_sha256[path]:
raise LiveProtocolConflict(f"pre-commit SHA mismatch: {path}")
response = self.bridge.commit_files(
session=session,
repository=request.repository,
target_branch=request.target_branch,
generation=request.generation,
files=files,
message=message,
)
new_head = self.git.branch_head(request.target_branch)
if new_head is None or new_head == session.expected_branch_oid:
raise LiveProtocolConflict("branch did not advance after governed commit")
readback = {
path: self.git.file_sha256(new_head, path) for path in expected_sha256
}
if readback != dict(expected_sha256):
raise LiveProtocolConflict("governed commit readback SHA mismatch")
return {
"commit_response": response,
"branch_head": new_head,
"readback_sha256": readback,
}
@@ -1,212 +0,0 @@
"""S5.1 live authority adapters with exact caller/candidate binding.
These adapters are projections over existing authorities. They do not create
tasks, grants, leases, branches, or recovery authority.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Callable, Mapping, Protocol
from .governance_admission import AdmissionOperation, GovernanceFacts
from .governance_recovery_controller import GovernanceEvent
from .governance_resolver import ExactTaskResolver, ResolutionQuery, ResolutionResult
class LiveSurfaceUnavailable(RuntimeError):
def __init__(self, role: str, reason: str) -> None:
self.role = role
self.reason = reason
super().__init__(f"LIVE_SURFACE_UNAVAILABLE[{role}]: {reason}")
class LiveBindingViolation(RuntimeError):
def __init__(self, role: str, reason: str) -> None:
self.role = role
self.reason = reason
super().__init__(f"LIVE_BINDING_VIOLATION[{role}]: {reason}")
class TaskPlaneReadClient(Protocol):
def get_task(
self, task_id: str, *, actor_identity: str
) -> Mapping[str, Any] | None: ...
def list_tasks(
self, *, actor_identity: str
) -> list[Mapping[str, Any]]: ...
class LiveTaskResolverAdapter:
"""Bind ExactTaskResolver to one caller-authorized TaskPlane projection."""
def __init__(
self,
*,
client: TaskPlaneReadClient | None = None,
actor_identity: str | None = None,
bound_repository: str | None = None,
) -> None:
self._client = client
self._actor_identity = actor_identity
self._bound_repository = bound_repository
@property
def live(self) -> bool:
return self._client is not None and bool(self._actor_identity)
def resolve(
self, query: ResolutionQuery, *, actor_identity: str
) -> ResolutionResult:
if not self.live:
raise LiveSurfaceUnavailable(
"A1",
"no caller-authorized TaskPlaneReadClient and bound identity",
)
if actor_identity != self._actor_identity:
raise LiveBindingViolation(
"A1",
"controller actor_identity does not match bound read principal",
)
if (
self._bound_repository
and query.repository != self._bound_repository
):
raise LiveBindingViolation(
"A1",
"query repository is outside the bound authority projection",
)
return ExactTaskResolver(self._client).resolve(
query, actor_identity=self._actor_identity
)
@dataclass(frozen=True)
class GovernedAuthoritySnapshot:
"""One revision-consistent, candidate-specific governed read."""
task_id: str
actor_identity: str
operation: AdmissionOperation
snapshot_revision: str
facts: GovernanceFacts
class GovernedAuthorityReadClient(Protocol):
def read_snapshot(
self,
*,
task_id: str,
actor_identity: str,
operation: AdmissionOperation,
) -> GovernedAuthoritySnapshot: ...
class LiveGovernanceFactsAdapter:
"""Materialize GovernanceFacts from one atomic/revision-consistent read."""
def __init__(
self,
*,
client: GovernedAuthorityReadClient | None = None,
actor_identity: str | None = None,
) -> None:
self._client = client
self._actor_identity = actor_identity
@property
def live(self) -> bool:
return self._client is not None and bool(self._actor_identity)
def snapshot(
self,
*,
task_id: str | None,
actor_identity: str,
operation: AdmissionOperation,
) -> GovernanceFacts:
if not self.live:
raise LiveSurfaceUnavailable(
"A2",
"no candidate-specific governed authority reader and identity",
)
if task_id is None:
raise LiveBindingViolation("A2", "exact task_id required")
if actor_identity != self._actor_identity:
raise LiveBindingViolation(
"A2",
"controller actor_identity does not match bound governed principal",
)
snapshot = self._client.read_snapshot(
task_id=task_id,
actor_identity=actor_identity,
operation=operation,
)
if not snapshot.snapshot_revision:
raise LiveBindingViolation(
"A2", "authority snapshot revision is missing"
)
if snapshot.task_id != task_id:
raise LiveBindingViolation("A2", "authority snapshot task mismatch")
if snapshot.actor_identity != actor_identity:
raise LiveBindingViolation("A2", "authority snapshot actor mismatch")
if snapshot.operation is not operation:
raise LiveBindingViolation("A2", "authority snapshot operation mismatch")
return snapshot.facts
@dataclass(frozen=True)
class LiveGovernanceEvent:
kind: str
revision: str
occurred_at: str
payload: Mapping[str, Any] = field(default_factory=dict)
class GovernanceEventSubscriber(Protocol):
def subscribe(
self, on_event: Callable[[LiveGovernanceEvent], object]
) -> None: ...
def close(self) -> None: ...
class LiveEventSubscriptionAdapter:
"""Translate site events into the core GovernanceEvent contract."""
def __init__(
self,
sink: Callable[[GovernanceEvent], object],
subscriber: GovernanceEventSubscriber | None = None,
) -> None:
self._sink = sink
self._subscriber = subscriber
self._active = False
@property
def live(self) -> bool:
return self._subscriber is not None
def _on_live_event(self, event: LiveGovernanceEvent):
if not isinstance(event, LiveGovernanceEvent):
raise LiveBindingViolation(
"A3", "subscriber emitted an unsupported event type"
)
return self._sink(GovernanceEvent(
kind=event.kind,
revision=event.revision,
occurred_at=event.occurred_at,
))
def start(self) -> None:
if not self.live:
raise LiveSurfaceUnavailable(
"A3",
"no governance event subscriber injected; polling remains fallback",
)
self._subscriber.subscribe(self._on_live_event)
self._active = True
def close(self) -> None:
if self._subscriber is not None:
self._subscriber.close()
self._active = False
+42
View File
@@ -0,0 +1,42 @@
{
"schema": "aios.status-pipeline-config.v1",
"repository": "aios/aios",
"checkout": "/data/aios-gitops/status-checkout",
"evidence_store_root": "/var/lib/aios-gitops/status-evidence",
"required_contexts": [
"py-compile",
"git-diff-check",
"secret-scan",
"autonomy-unit-tests",
"autonomy-smoke-tests",
"reconciliation-contract",
"release-state-machine",
"campaign-approval-gate",
"signer-validator-contract"
],
"capabilities": {
"granted": ["status_publish"],
"denied": [
"direct_main_push",
"force_push",
"tag_update",
"tag_delete",
"baseline_write",
"production_deploy",
"protected_merge"
]
},
"na_contracts": {
"campaign-approval-gate": "aios.campaign-approval-gate.v1#formally-not-applicable",
"signer-validator-contract": "aios.signer-validator-contract.v1#formally-not-applicable"
},
"secret_scan": {
"gitleaks_bin": "/usr/bin/gitleaks",
"allowlist": ".gitleaks.toml"
},
"integration": {
"trigger": "external-hosted (webhook/durable-job/poll) — not part of this pipeline; see status_pipeline_entry.py",
"producer_commit": "pending-team-commit",
"reviewed_tree_oid": "pending-open-code-review"
}
}
@@ -1,16 +1,9 @@
#!/usr/bin/env python3
"""Build a deterministic plan-bound production payload from one clean source tree."""
from __future__ import annotations
import argparse, hashlib, json, re, shutil, tarfile, tempfile
import argparse, hashlib, json, shutil, tarfile, tempfile
from pathlib import Path
from release_version import validate_release_version
# Adapter service must be a well-formed systemd unit base name (no path, no
# leading dash) so the produced payload binds to an explicit, governed adapter
# without pinning this producer to a single V2R2C2-era service/port.
_SERVICE_UNIT_NAME_RE = re.compile(r"^[A-Za-z0-9_.:@-]{1,255}$")
SOURCE_FILES = (
"active-runtime/app/deployment_state.py",
"active-runtime/app/v2r1c6_privacy_tools.py",
@@ -41,22 +34,8 @@ def build(source: Path, output: Path, *, version: str, source_oid: str, tree_oid
if not source.is_dir() or source.is_symlink(): raise SystemExit("source_directory_invalid")
require_oid(source_oid, "source_oid"); require_oid(tree_oid, "tree_oid")
require_oid(rollback_target_oid, "rollback_target_oid"); require_sha(rollback_artifact_sha256, "rollback_artifact_sha256")
# Successor-aware binding: version is validated against the canonical
# release-version contract (VnRnCn[-suffix], shared with the evidence
# producer). Rollback must never target the same version as the payload
# (fail-closed safety invariant preserved).
try:
validate_release_version(version)
validate_release_version(rollback_version)
except ValueError:
raise SystemExit("release_version_binding_invalid")
if rollback_version == version: raise SystemExit("release_version_binding_invalid")
# Adapter binding: service must be a well-formed unit name and port within
# the valid range. No single V2R2C2-era service/port is pinned.
if not (isinstance(service, str) and _SERVICE_UNIT_NAME_RE.fullmatch(service)):
raise SystemExit("adapter_policy_binding_invalid")
if not isinstance(port, int) or not (1 <= port <= 65535):
raise SystemExit("adapter_policy_binding_invalid")
if version != "V2R2C2" or rollback_version == version: raise SystemExit("release_version_binding_invalid")
if service != "aios-v2r2c2-production.service" or port != 18928: raise SystemExit("adapter_policy_binding_invalid")
if output.exists(): raise SystemExit("output_exists")
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="trusted-release-build-") as td:
+5 -5
View File
@@ -4,10 +4,10 @@ from unittest import mock
import pytest
from app.task_plane.api import TaskPlaneService
from app.task_plane.errors import ConflictError, LockedError
from app.task_plane.policy import ALL_ACTION_CAPABILITIES, PolicyEngine
from app.task_plane.store import TaskPlaneStore
from task_plane.api import TaskPlaneService
from task_plane.errors import ConflictError, LockedError
from task_plane.policy import ALL_ACTION_CAPABILITIES, PolicyEngine
from task_plane.store import TaskPlaneStore
IDENTITY = "spiffe://aios/agent/heartbeat"
@@ -66,7 +66,7 @@ def test_lease_at_current_time_is_expired_and_cannot_be_renewed():
store, task, claim = claimed_task()
boundary = store.get_claim(claim["claim_id"]).lease_until
with mock.patch("app.task_plane.store._now", return_value=boundary):
with mock.patch("task_plane.store._now", return_value=boundary):
with pytest.raises(LockedError, match="lease expired"):
store.heartbeat_claim(task["task_id"], claim["claim_id"], 30)
-2
View File
@@ -568,8 +568,6 @@ def test_subprocess_crash_after_durable_sink_success_recovers_without_repeat(tmp
environment["PYTHONDONTWRITEBYTECODE"] = "1"
environment["ACTION"] = "execute"
environment["CRASH_AFTER_EFFECT"] = "1"
_repo_root = Path(__file__).resolve().parents[2]
environment["PYTHONPATH"] = str(_repo_root / "active-runtime" / "app") + os.pathsep + environment.get("PYTHONPATH", "")
crashed = subprocess.run([sys.executable, "-c", script], env=environment, check=False)
assert crashed.returncode == 77
assert json.loads(sink.read_text()) == {"count": 1, "oid": NEW}
+6 -69
View File
@@ -517,78 +517,14 @@ def test_root_complete_and_not_ready_never_project_plan(tmp_path):
assert world["ledger"].list_root(ROOT_TASK) == ()
def test_live_binding_file_matches_observed_children_exactly(tmp_path):
# Originally this was a LIVE runtime cross-check that read the
# deployment-only artifacts checkout/s3/live-mission-children.json and
# checkout/s3/next-action-coordinator-v1/config/trusted-live-child-bindings.json,
# which no producer materialises in an isolated source checkout. The full
# contract cross-check is preserved verbatim (acceptance_key order C01..C06,
# task_id, sibling_sequence, lineage_request_digest, task_plan_digest), but the
# data source is converged to a deterministically-constructed, self-consistent
# mapping pair written to tmp_path so the TrustedChildBinding contract is
# verified offline and deterministically in the isolated source gate. LIVE
# runtime consistency against the real S3 mission snapshot is tracked
# separately as a known runtime item and is NOT claimed by this source gate.
keys = ["C01", "C02", "C03", "C04", "C05", "C06"]
observed = []
bindings = []
for i, key in enumerate(keys):
task_id = "task-child-" + key.lower()
lineage_request_digest = sha256(("lineage-request-" + key).encode()).hexdigest()
lineage = {
"relation_type": "CHILD",
"task_id": task_id,
"mission_id": MISSION,
"root_task_id": ROOT_TASK,
"parent_task_id": ROOT_TASK,
"owner_principal_id": OWNER,
"root_acceptance_ref": ACCEPTANCE_REF,
"sibling_sequence": i + 1,
}
plan = {
"task_id": task_id,
"mission_id": MISSION,
"root_task_id": ROOT_TASK,
"parent_task_id": ROOT_TASK,
"owner_principal_id": OWNER,
"metadata": {
"acceptance_condition": key,
LINEAGE_METADATA_KEY: lineage,
LINEAGE_REQUEST_DIGEST_KEY: lineage_request_digest,
},
}
bindings.append({
"schema": mod.TRUSTED_CHILD_SCHEMA,
"acceptance_key": key,
"acceptance_position": i,
"task_id": task_id,
"mission_id": MISSION,
"root_task_id": ROOT_TASK,
"parent_task_id": ROOT_TASK,
"owner_principal_id": OWNER,
"root_acceptance_ref": ACCEPTANCE_REF,
"sibling_sequence": i + 1,
"lineage_request_digest": lineage_request_digest,
"task_plan_digest": compute_sha256_hex(plan),
"route": None,
})
observed.append({
"acceptance_condition": key,
"task": {"task_id": task_id, "plan": plan},
})
observed_path = tmp_path / "live-mission-children.json"
configured_path = tmp_path / "trusted-live-child-bindings.json"
observed_path.write_text(
json.dumps(observed, ensure_ascii=False, sort_keys=True), encoding="utf-8"
def test_live_binding_file_matches_observed_children_exactly():
observed = json.loads((ROOT / "s3" / "live-mission-children.json").read_text(encoding="utf-8"))
configured = json.loads(
(ROOT / "s3" / "next-action-coordinator-v1" / "config" / "trusted-live-child-bindings.json").read_text(encoding="utf-8")
)
configured_path.write_text(
json.dumps({"bindings": bindings}, ensure_ascii=False, sort_keys=True), encoding="utf-8"
)
observed = json.loads(observed_path.read_text(encoding="utf-8"))
configured = json.loads(configured_path.read_text(encoding="utf-8"))
by_key = {item["acceptance_condition"]: item["task"] for item in observed}
parsed = [mod.TrustedChildBinding.from_mapping(item) for item in configured["bindings"]]
assert [item.acceptance_key for item in parsed] == keys
assert [item.acceptance_key for item in parsed] == ["C01", "C02", "C03", "C04", "C05", "C06"]
for binding in parsed:
task = by_key[binding.acceptance_key]
plan = task["plan"]
@@ -598,6 +534,7 @@ def test_live_binding_file_matches_observed_children_exactly(tmp_path):
assert binding.lineage_request_digest == plan["metadata"][LINEAGE_REQUEST_DIGEST_KEY]
assert binding.task_plan_digest == compute_sha256_hex(plan)
def test_same_child_readiness_advances_with_cas_without_identity_change(tmp_path):
world = make_world(tmp_path)
binding = world["bindings"][0]
@@ -1,458 +0,0 @@
from __future__ import annotations
import unittest
from dataclasses import replace
from task_plane.autonomous_resume_aios_adapter import (
AcceptanceState,
AcceptanceView,
AiosUnattendedCallbackFactory,
AiosUnattendedMissionConfig,
DurableGrantView,
GateState,
GateView,
IntegrationView,
LaneExecutionView,
MissionEffectView,
PromotionState,
PromotionView,
ReadyState,
ReleaseReadyView,
TaskAdmissionReceipt,
TaskLaneSpec,
WorkerActivationReceipt,
WorkerState,
WorkState,
)
from task_plane.autonomous_resume_controller import (
AutonomousResumeController,
EffectState,
MissionPhase,
OutcomeStatus,
ResumeLease,
StepResult,
StepStatus,
)
def lane(name, path):
return TaskLaneSpec(
lane=name,
task_id=f"task-{name}",
repository="aios/aios",
target_branch=f"agent/{name}",
risk="medium",
description=name,
write_set=(path,),
)
class FakeAuthorities:
def __init__(self):
self.head="a"*40
self.material="mat-1"
self.generation=1
self.effect=MissionEffectView(
EffectState.ABSENT,"effect-1",False,()
)
self.grant=DurableGrantView(
grant_id="grant-1",
active=True,
repository="aios/aios",
allowed_target_prefixes=("agent/",),
max_task_risk="engineering",
evidence_refs=("grant:1",),
)
self.worker=WorkerActivationReceipt(
WorkerState.ACTIVE,
receipt_ref="worker:1",
code="WORKER_ACTIVE",
)
self.admission_overrides={}
self.lane_views={}
self.integration=IntegrationView(
WorkState.COMPLETE,"integration-1",("integration:1",),
code="INTEGRATION_COMPLETE",
)
self.gate=GateView(
GateState.PASS,"gate-1",("gate:1",),code="GATE0_PASS"
)
self.ready=ReleaseReadyView(
ReadyState.READY,"ready-1","mat-1",("ready:1",),
code="RELEASE_READY",
)
self.promotion=PromotionView(
PromotionState.VERIFIED,"promotion-1","mat-1",("promotion:1",),
code="PROMOTION_VERIFIED",
)
self.acceptance=AcceptanceView(
AcceptanceState.PASS,"accept-1",("accept:1",),
code="FINAL_ACCEPTANCE_PASS",
)
self.checkpoint=None
self.owner_resolved=False
self.requests=[]
self.persisted_admissions={}
self.released=[]
self.reconcile_calls=0
def read_head_oid(self, repository): return self.head
def read_material_digest(self, mission_id): return self.material
def read_generation(self, mission_id): return self.generation
def read_effect_view(self, mission_id): return self.effect
def owner_action_resolved(self, mission_id): return self.owner_resolved
def load_checkpoint(self, mission_id): return self.checkpoint
def acquire_resume_lease(self, mission_id,generation,key,seconds):
return ResumeLease("lease-1",mission_id,generation,"fence-1",9999)
def persist_checkpoint(self, checkpoint, lease): self.checkpoint=checkpoint
def persist_owner_action_required(self,payload,observation,checkpoint,lease):
return "owner:1"
def release_resume_lease(self, lease, outcome_code):
self.released.append((lease.lease_id,outcome_code))
def reconcile_effects(self,observation,checkpoint,lease):
self.reconcile_calls+=1
return StepResult(
StepStatus.SUCCEEDED,
"RECONCILED",
"reconcile-1",
next_phase=checkpoint.phase,
)
def read_durable_grant(self,mission_id): return self.grant
def ensure_canonical_worker(self,mission_id,generation,idempotency_key):
return self.worker
def create_or_resume_task(self,request):
self.requests.append(request)
receipt=self.admission_overrides.get(
request.task_id,
TaskAdmissionReceipt(
request.task_id,1,request.target_branch,True,True,
"task:"+request.task_id,
)
)
if receipt.persisted:
self.persisted_admissions[request.task_id]=receipt
return receipt
def read_task_admission(self,lane):
return self.persisted_admissions.get(lane.task_id)
def read_lane_execution(self,lane,task):
return self.lane_views.get(
lane.lane,
LaneExecutionView(
lane.lane,WorkState.COMPLETE,"lane:"+lane.lane,
("lane-receipt:"+lane.lane,),code="LANE_COMPLETE",
)
)
def drive_integration(self,lanes,tasks): return self.integration
def run_gate0(self,mission_id): return self.gate
def build_release_ready(self,mission_id): return self.ready
def run_promotion(self,mission_id): return self.promotion
def run_final_acceptance(self,mission_id): return self.acceptance
class AiosAdapterTests(unittest.TestCase):
def setUp(self):
self.auth=FakeAuthorities()
self.lanes=(
lane("A","a.py"),
lane("B","b.py"),
lane("C","c.py"),
)
self.config=AiosUnattendedMissionConfig(
mission_id="v2r2c4",
repository="aios/aios",
lanes=self.lanes,
)
self.factory=AiosUnattendedCallbackFactory(
self.auth,self.config
)
def observation(self):
return self.factory.read_observation("v2r2c4")
def test_config_rejects_write_set_overlap(self):
with self.assertRaisesRegex(ValueError,"overlapping_lane_resource"):
AiosUnattendedMissionConfig(
"v2r2c4","aios/aios",
(lane("A","same.py"),lane("B","same.py")),
)
def test_config_rejects_non_agent_branch(self):
with self.assertRaisesRegex(ValueError,"agent_prefix"):
TaskLaneSpec(
"A","task-a","aios/aios","feature/a","medium","x",
("a.py",),
)
def test_observation_fingerprint_changes_with_head(self):
first=self.observation().condition_fingerprint
self.auth.head="b"*40
second=self.observation().condition_fingerprint
self.assertNotEqual(first,second)
def test_inactive_grant_blocks_without_task_creation(self):
self.auth.grant=replace(self.auth.grant,active=False)
result=self.factory.admission(
self.observation(),None,None
)
self.assertEqual(result.status,StepStatus.BLOCKED_DETERMINISTIC)
self.assertEqual(self.auth.requests,[])
def test_owner_only_grant_boundary_is_typed_owner_blocker(self):
self.auth.grant=replace(
self.auth.grant,active=False,owner_action_required=True
)
result=self.factory.admission(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.BLOCKED_OWNER)
self.assertIsNotNone(result.owner_action_payload)
def test_grant_scope_mismatch_blocks_before_worker(self):
self.auth.grant=replace(
self.auth.grant,allowed_target_prefixes=("other/",)
)
result=self.factory.admission(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.BLOCKED_DETERMINISTIC)
self.assertEqual(self.auth.requests,[])
def test_worker_starting_is_in_progress(self):
self.auth.worker=WorkerActivationReceipt(
WorkerState.STARTING,"worker:start",7,False,"WORKER_STARTING"
)
result=self.factory.admission(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.IN_PROGRESS)
self.assertEqual(result.retry_after_seconds,7)
self.assertEqual(self.auth.requests,[])
def test_worker_unknown_reconciles(self):
self.auth.worker=WorkerActivationReceipt(
WorkerState.UNKNOWN,None,None,False,"WORKER_UNKNOWN"
)
result=self.factory.admission(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_active_worker_creates_all_tasks_deterministically(self):
obs=self.observation()
result=self.factory.admission(obs,None,None)
self.assertEqual(result.status,StepStatus.SUCCEEDED)
self.assertEqual(len(self.auth.requests),3)
self.assertTrue(all(r.base_oid==obs.head_oid for r in self.auth.requests))
keys=[r.idempotency_key for r in self.auth.requests]
self.assertEqual(len(keys),len(set(keys)))
def test_unpersisted_task_requires_reconcile(self):
self.auth.admission_overrides["task-B"]=TaskAdmissionReceipt(
"task-B",1,"agent/B",False,False,"task:B"
)
result=self.factory.admission(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_unclaimable_task_waits(self):
self.auth.admission_overrides["task-B"]=TaskAdmissionReceipt(
"task-B",1,"agent/B",True,False,"task:B"
)
result=self.factory.admission(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.IN_PROGRESS)
def test_source_execution_unknown_lane_reconciles(self):
self.factory.admission(self.observation(),None,None)
self.auth.lane_views["B"]=LaneExecutionView(
"B",WorkState.UNKNOWN,"lane:B",code="LANE_B_UNKNOWN"
)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_source_execution_running_lane_waits(self):
self.factory.admission(self.observation(),None,None)
self.auth.lane_views["B"]=LaneExecutionView(
"B",WorkState.RUNNING,"lane:B",retry_after_seconds=9,
code="LANE_B_RUNNING"
)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.IN_PROGRESS)
self.assertEqual(result.retry_after_seconds,9)
def test_source_execution_blocked_lane_is_deterministic_block(self):
self.factory.admission(self.observation(),None,None)
self.auth.lane_views["B"]=LaneExecutionView(
"B",WorkState.BLOCKED,"lane:B",code="LANE_B_BLOCKED"
)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.BLOCKED_DETERMINISTIC)
def test_source_execution_complete_advances_to_gate0(self):
self.factory.admission(self.observation(),None,None)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.SUCCEEDED)
self.assertEqual(result.next_phase,MissionPhase.GATE0)
def test_missing_admission_receipts_reconciles_if_authority_has_none(self):
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
self.assertEqual(result.code,"TASK_ADMISSION_NOT_RESOLVED")
def test_restart_rebuilds_admission_receipts_from_authority(self):
self.factory.admission(self.observation(),None,None)
restarted=AiosUnattendedCallbackFactory(self.auth,self.config)
result=restarted.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.SUCCEEDED)
self.assertEqual(result.next_phase,MissionPhase.GATE0)
def test_stale_generation_admission_receipt_reconciles(self):
self.auth.persisted_admissions["task-A"]=TaskAdmissionReceipt(
"task-A",2,"agent/A",True,True,"task:A:gen2"
)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
self.assertEqual(result.code,"TASK_GENERATION_MISMATCH")
def test_wrong_branch_admission_receipt_reconciles(self):
self.auth.persisted_admissions["task-A"]=TaskAdmissionReceipt(
"task-A",1,"agent/wrong",True,True,"task:A:wrong"
)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
self.assertEqual(result.code,"TASK_BRANCH_MISMATCH")
def test_gate_hold_blocks(self):
self.auth.gate=GateView(
GateState.HOLD,"gate-hold",code="GATE0_HOLD"
)
result=self.factory.gate0(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.BLOCKED_DETERMINISTIC)
def test_gate_unknown_reconciles(self):
self.auth.gate=GateView(
GateState.UNKNOWN,"gate-unknown",code="GATE0_UNKNOWN"
)
result=self.factory.gate0(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_ready_without_material_digest_reconciles(self):
self.auth.ready=ReleaseReadyView(
ReadyState.READY,"ready",None,code="READY"
)
result=self.factory.release_ready(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_ready_freezes_material_for_controller(self):
result=self.factory.release_ready(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.SUCCEEDED)
self.assertEqual(result.material_digest,"mat-1")
self.assertEqual(result.next_phase,MissionPhase.PROMOTION)
def test_promotion_unknown_reconciles(self):
self.auth.promotion=PromotionView(
PromotionState.UNKNOWN,"p-unknown","mat-1",code="PROMOTION_UNKNOWN"
)
result=self.factory.promotion(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_promotion_in_progress_waits(self):
self.auth.promotion=PromotionView(
PromotionState.IN_PROGRESS,"p-run","mat-1",
retry_after_seconds=5,code="PROMOTION_RUNNING"
)
result=self.factory.promotion(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.IN_PROGRESS)
self.assertEqual(result.retry_after_seconds,5)
def test_promotion_verified_advances(self):
result=self.factory.promotion(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.SUCCEEDED)
self.assertEqual(result.next_phase,MissionPhase.FINAL_ACCEPTANCE)
def test_rolled_back_promotion_blocks_condition(self):
self.auth.promotion=PromotionView(
PromotionState.ROLLED_BACK,"p-rb","mat-1",code="ROLLED_BACK"
)
result=self.factory.promotion(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.BLOCKED_DETERMINISTIC)
def test_final_acceptance_pass_completes(self):
result=self.factory.final_acceptance(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.SUCCEEDED)
self.assertEqual(result.next_phase,MissionPhase.COMPLETE)
def test_final_acceptance_unknown_reconciles(self):
self.auth.acceptance=AcceptanceView(
AcceptanceState.UNKNOWN,"a-unknown",code="ACCEPT_UNKNOWN"
)
result=self.factory.final_acceptance(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_active_worker_without_receipt_reconciles(self):
self.auth.worker=WorkerActivationReceipt(
WorkerState.ACTIVE,None,None,False,"WORKER_ACTIVE"
)
result=self.factory.admission(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
self.assertEqual(result.code,"WORKER_ACTIVE_WITHOUT_RECEIPT")
def test_complete_lane_without_receipt_reconciles(self):
self.factory.admission(self.observation(),None,None)
self.auth.lane_views["B"]=LaneExecutionView(
"B",WorkState.COMPLETE,"lane:B",(),code="LANE_COMPLETE"
)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
self.assertEqual(result.code,"LANE_COMPLETE_WITHOUT_RECEIPT")
def test_complete_integration_without_receipt_reconciles(self):
self.factory.admission(self.observation(),None,None)
self.auth.integration=IntegrationView(
WorkState.COMPLETE,"integration-empty",(),code="COMPLETE"
)
result=self.factory.source_execution(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
self.assertEqual(result.code,"INTEGRATION_COMPLETE_WITHOUT_RECEIPT")
def test_gate_pass_without_receipt_reconciles(self):
self.auth.gate=GateView(GateState.PASS,"gate-empty",(),code="PASS")
result=self.factory.gate0(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_ready_without_receipt_reconciles(self):
self.auth.ready=ReleaseReadyView(
ReadyState.READY,"ready-empty","mat-1",(),code="READY"
)
result=self.factory.release_ready(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_promotion_verified_without_receipt_reconciles(self):
self.auth.promotion=PromotionView(
PromotionState.VERIFIED,"p-empty","mat-1",(),code="VERIFIED"
)
result=self.factory.promotion(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_final_pass_without_receipt_reconciles(self):
self.auth.acceptance=AcceptanceView(
AcceptanceState.PASS,"a-empty",(),code="PASS"
)
result=self.factory.final_acceptance(self.observation(),None,None)
self.assertEqual(result.status,StepStatus.RECONCILE)
def test_controller_port_can_execute_admission_cycle(self):
controller=AutonomousResumeController(
self.factory.build_port(),
clock=lambda:100,
)
out=controller.run_once("v2r2c4")
self.assertEqual(out.status,OutcomeStatus.PROGRESSED)
self.assertEqual(self.auth.checkpoint.phase,MissionPhase.S2_EXECUTION)
def test_unknown_effect_routes_controller_to_reconcile(self):
self.auth.effect=MissionEffectView(
EffectState.UNKNOWN,"unknown-effect",True,()
)
controller=AutonomousResumeController(
self.factory.build_port(),
clock=lambda:100,
)
out=controller.run_once("v2r2c4")
self.assertEqual(self.auth.reconcile_calls,1)
self.assertEqual(out.status,OutcomeStatus.PROGRESSED)
if __name__=="__main__":
unittest.main()
@@ -1,491 +0,0 @@
from __future__ import annotations
import unittest
from dataclasses import replace
from task_plane.autonomous_resume_controller import (
AutonomousResumeController,
EffectState,
MissionCheckpoint,
MissionObservation,
MissionPhase,
OutcomeStatus,
ResumeLease,
ResumePolicy,
StepResult,
StepStatus,
)
class FakePort:
def __init__(self):
self.observation = MissionObservation(
mission_id="v2r2c4",
generation=1,
head_oid="a" * 40,
material_digest="mat-1",
condition_fingerprint="cond-1",
)
self.checkpoint = None
self.lease_available = True
self.step_results = []
self.reconcile_results = []
self.persisted = []
self.owner_records = []
self.released = []
self.lease_override = None
self.persist_failure = None
self.step_calls = 0
self.reconcile_calls = 0
def read_observation(self, mission_id):
self.assert_mission(mission_id)
return self.observation
def load_checkpoint(self, mission_id):
self.assert_mission(mission_id)
return self.checkpoint
def acquire_resume_lease(self, *, mission_id, generation, idempotency_key, lease_seconds):
self.assert_mission(mission_id)
if not self.lease_available:
return None
if self.lease_override is not None:
return self.lease_override
return ResumeLease(
lease_id="lease-1",
mission_id=mission_id,
generation=generation,
fencing_token="fence-1",
expires_at=999999,
)
def step(self, *, phase, observation, checkpoint, lease):
self.step_calls += 1
if not self.step_results:
raise AssertionError("missing step result")
return self.step_results.pop(0)
def reconcile(self, *, observation, checkpoint, lease):
self.reconcile_calls += 1
if not self.reconcile_results:
raise AssertionError("missing reconcile result")
return self.reconcile_results.pop(0)
def persist_checkpoint(self, checkpoint, *, lease):
if self.persist_failure is not None:
raise self.persist_failure
self.checkpoint = checkpoint
self.persisted.append(checkpoint)
def persist_owner_action_required(self, payload, *, observation, checkpoint, lease):
self.owner_records.append(dict(payload))
return "evidence:owner-action-1"
def release_resume_lease(self, lease, *, outcome_code):
self.released.append((lease.lease_id, outcome_code))
@staticmethod
def assert_mission(mission_id):
if mission_id != "v2r2c4":
raise AssertionError("unexpected mission")
def success(code="OK", *, next_phase=None, material_digest=None):
return StepResult(
StepStatus.SUCCEEDED,
code,
"cond-step",
receipt_refs=("receipt:" + code,),
next_phase=next_phase,
material_digest=material_digest,
)
class AutonomousResumeControllerTests(unittest.TestCase):
def setUp(self):
self.port = FakePort()
self.now = 100.0
self.controller = AutonomousResumeController(
self.port,
policy=ResumePolicy(
resume_lease_seconds=120,
default_poll_seconds=30,
deterministic_block_poll_seconds=300,
transient_max_attempts=3,
transient_base_delay_seconds=2,
transient_max_delay_seconds=60,
),
clock=lambda: self.now,
)
def cycle(self):
return self.controller.run_once("v2r2c4")
def test_initial_success_advances_from_admission_to_execution(self):
self.port.step_results=[success("ADMITTED")]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.PROGRESSED)
self.assertEqual(self.port.checkpoint.phase, MissionPhase.S2_EXECUTION)
self.assertEqual(self.port.step_calls, 1)
self.assertEqual(self.port.reconcile_calls, 0)
self.assertTrue(self.port.released)
def test_resume_lease_denied_means_active_elsewhere_without_mutation(self):
self.port.lease_available=False
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.ACTIVE_ELSEWHERE)
self.assertEqual(self.port.step_calls, 0)
self.assertEqual(self.port.persisted, [])
def test_unknown_effect_reconciles_before_phase_step(self):
self.port.observation=replace(
self.port.observation,
effect_state=EffectState.UNKNOWN,
)
self.port.reconcile_results=[success("RECONCILED", next_phase=MissionPhase.S2_ADMISSION)]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.PROGRESSED)
self.assertEqual(self.port.reconcile_calls, 1)
self.assertEqual(self.port.step_calls, 0)
def test_requires_reconcile_uses_reconcile_port(self):
self.port.observation=replace(
self.port.observation,
requires_reconcile=True,
)
self.port.reconcile_results=[success("RECONCILED", next_phase=MissionPhase.S2_ADMISSION)]
self.cycle()
self.assertEqual(self.port.reconcile_calls, 1)
self.assertEqual(self.port.step_calls, 0)
def test_transient_retry_uses_exponential_backoff(self):
self.port.step_results=[
StepResult(StepStatus.RETRYABLE,"TIMEOUT","transient"),
]
first=self.cycle()
self.assertEqual(first.status, OutcomeStatus.RETRY_SCHEDULED)
self.assertEqual(first.next_wake_after_seconds,2)
self.assertEqual(self.port.checkpoint.attempt_count,1)
self.port.step_results=[
StepResult(StepStatus.RETRYABLE,"TIMEOUT","transient"),
]
second=self.cycle()
self.assertEqual(second.next_wake_after_seconds,4)
self.assertEqual(self.port.checkpoint.attempt_count,2)
def test_retry_budget_exhaustion_blocks_until_condition_change(self):
for _ in range(3):
self.port.step_results=[
StepResult(StepStatus.RETRYABLE,"TIMEOUT","cond-1"),
]
self.cycle()
self.port.step_results=[
StepResult(StepStatus.RETRYABLE,"TIMEOUT","cond-1"),
]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.WAIT_CONDITION_CHANGE)
self.assertEqual(self.port.checkpoint.blocked_condition_fingerprint,"cond-1")
# Same observed condition suppresses another consequential step.
step_calls=self.port.step_calls
self.port.observation=replace(
self.port.observation,
condition_fingerprint="cond-1",
)
out2=self.cycle()
self.assertEqual(out2.status, OutcomeStatus.WAIT_CONDITION_CHANGE)
self.assertEqual(self.port.step_calls,step_calls)
def test_condition_change_clears_deterministic_block(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(
cp,
blocked_condition_fingerprint="old",
attempt_count=4,
)
self.port.observation=replace(
self.port.observation,
condition_fingerprint="new",
)
self.port.step_results=[success("RECOVERED")]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.PROGRESSED)
self.assertIsNone(self.port.checkpoint.blocked_condition_fingerprint)
self.assertEqual(self.port.checkpoint.attempt_count,0)
def test_deterministic_blocker_is_persisted_without_retry_storm(self):
self.port.step_results=[
StepResult(
StepStatus.BLOCKED_DETERMINISTIC,
"WORKER_IDENTITY_UNAVAILABLE",
"identity-v1",
)
]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.WAIT_CONDITION_CHANGE)
self.assertEqual(out.next_wake_after_seconds,300)
def test_owner_blocker_persists_record_and_stops_only_phase(self):
self.port.step_results=[
StepResult(
StepStatus.BLOCKED_OWNER,
"HARDWARE_SIGNATURE_REQUIRED",
"owner-boundary-1",
owner_action_payload={"envelope_sha256":"abc"},
)
]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.OWNER_ACTION_REQUIRED)
self.assertEqual(self.port.checkpoint.phase, MissionPhase.OWNER_ACTION_REQUIRED)
self.assertEqual(self.port.checkpoint.resume_phase_after_owner, MissionPhase.S2_ADMISSION)
self.assertEqual(len(self.port.owner_records),1)
self.assertIn("evidence:owner-action-1",self.port.checkpoint.receipt_refs)
def test_owner_action_resolved_resumes_previous_phase(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(
cp,
phase=MissionPhase.OWNER_ACTION_REQUIRED,
resume_phase_after_owner=MissionPhase.GATE0,
)
self.port.observation=replace(
self.port.observation,
owner_action_resolved=True,
)
self.port.step_results=[
success("GATE0_PASS", next_phase=MissionPhase.RELEASE_READY),
]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.PROGRESSED)
self.assertEqual(self.port.checkpoint.phase, MissionPhase.RELEASE_READY)
def test_owner_action_unresolved_returns_without_acquiring_lease(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(
cp,
phase=MissionPhase.OWNER_ACTION_REQUIRED,
resume_phase_after_owner=MissionPhase.GATE0,
)
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.OWNER_ACTION_REQUIRED)
self.assertEqual(self.port.released,[])
def test_generation_increase_rewinds_to_admission(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(
cp,
phase=MissionPhase.GATE0,
generation=1,
)
self.port.observation=replace(
self.port.observation,
generation=2,
head_oid="b"*40,
material_digest="mat-2",
)
self.port.step_results=[success("READMITTED")]
self.cycle()
self.assertEqual(self.port.checkpoint.phase, MissionPhase.S2_EXECUTION)
self.assertEqual(self.port.checkpoint.generation,2)
def test_checkpoint_generation_ahead_is_rejected(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(cp,generation=2)
with self.assertRaisesRegex(ValueError,"checkpoint_generation_ahead"):
self.cycle()
def test_head_change_before_ready_rewinds_to_admission(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(cp,phase=MissionPhase.GATE0)
self.port.observation=replace(
self.port.observation,
head_oid="b"*40,
material_digest="mat-2",
)
self.port.step_results=[success("READMITTED")]
self.cycle()
self.assertEqual(self.port.checkpoint.phase, MissionPhase.S2_EXECUTION)
self.assertEqual(self.port.checkpoint.head_oid,"b"*40)
def test_release_ready_success_freezes_material_digest(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(cp,phase=MissionPhase.RELEASE_READY)
self.port.step_results=[
success("READY",material_digest="frozen-1"),
]
self.cycle()
self.assertEqual(self.port.checkpoint.phase,MissionPhase.PROMOTION)
self.assertEqual(self.port.checkpoint.frozen_material_digest,"frozen-1")
def test_material_change_after_ready_rewinds_to_release_ready(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(
cp,
phase=MissionPhase.PROMOTION,
frozen_material_digest="frozen-1",
material_digest="frozen-1",
)
self.port.observation=replace(
self.port.observation,
material_digest="changed",
)
self.port.step_results=[success("READY_AGAIN",material_digest="changed")]
self.cycle()
self.assertEqual(self.port.checkpoint.phase,MissionPhase.PROMOTION)
self.assertEqual(self.port.checkpoint.frozen_material_digest,"changed")
def test_complete_checkpoint_suppresses_all_effects(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(cp,phase=MissionPhase.COMPLETE)
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.COMPLETE)
self.assertEqual(self.port.step_calls,0)
self.assertEqual(self.port.released,[])
def test_final_acceptance_success_completes_mission(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(cp,phase=MissionPhase.FINAL_ACCEPTANCE)
self.port.step_results=[success("FINAL_OK")]
out=self.cycle()
self.assertEqual(out.status, OutcomeStatus.COMPLETE)
self.assertEqual(self.port.checkpoint.phase,MissionPhase.COMPLETE)
def test_reconcile_status_schedules_immediate_reconcile_cycle(self):
self.port.step_results=[
StepResult(StepStatus.RECONCILE,"CAS_UNKNOWN","cas-unknown")
]
out=self.cycle()
self.assertEqual(out.status,OutcomeStatus.IN_PROGRESS)
self.assertEqual(out.next_wake_after_seconds,0)
self.assertTrue(out.code.startswith("RECONCILE_REQUIRED:"))
def test_step_receipts_are_deduplicated_and_persisted(self):
self.port.observation=replace(
self.port.observation,
evidence_refs=("e:obs",),
)
self.port.step_results=[
StepResult(
StepStatus.SUCCEEDED,
"OK",
"cond-step",
receipt_refs=("e:obs","e:step"),
)
]
self.cycle()
self.assertEqual(
self.port.checkpoint.receipt_refs,
("e:obs","e:step"),
)
def test_stale_resume_lease_is_rejected_before_effect(self):
self.port.lease_override=ResumeLease(
lease_id="stale",
mission_id="v2r2c4",
generation=1,
fencing_token="fence-stale",
expires_at=99,
)
with self.assertRaisesRegex(ValueError,"resume_lease_expired"):
self.cycle()
self.assertEqual(self.port.step_calls,0)
self.assertEqual(self.port.released,[("stale","INVALID_RESUME_LEASE")])
def test_wrong_generation_resume_lease_is_rejected_before_effect(self):
self.port.lease_override=ResumeLease(
lease_id="wrong-gen",
mission_id="v2r2c4",
generation=2,
fencing_token="fence-2",
expires_at=999,
)
with self.assertRaisesRegex(ValueError,"resume_lease_generation_mismatch"):
self.cycle()
self.assertEqual(self.port.step_calls,0)
self.assertEqual(self.port.released,[("wrong-gen","INVALID_RESUME_LEASE")])
def test_checkpoint_persist_failure_still_releases_resume_lease(self):
self.port.step_results=[success("ADMITTED")]
self.port.persist_failure=RuntimeError("persist failed")
with self.assertRaisesRegex(RuntimeError,"persist failed"):
self.cycle()
self.assertEqual(self.port.released,[("lease-1","ADMITTED")])
def test_promotion_step_material_mismatch_rewinds_and_reconciles(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(
cp,
phase=MissionPhase.PROMOTION,
frozen_material_digest="frozen-1",
material_digest="frozen-1",
)
self.port.observation=replace(
self.port.observation,
material_digest="frozen-1",
)
self.port.step_results=[
success("PROMOTED",material_digest="late-change")
]
out=self.cycle()
self.assertEqual(out.status,OutcomeStatus.IN_PROGRESS)
self.assertEqual(self.port.checkpoint.phase,MissionPhase.RELEASE_READY)
self.assertTrue(self.port.checkpoint.reconcile_required)
self.assertIsNone(self.port.checkpoint.frozen_material_digest)
self.assertEqual(out.next_wake_after_seconds,0)
def test_step_reconcile_flag_is_persisted_until_reconcile_succeeds(self):
self.port.step_results=[
StepResult(StepStatus.RECONCILE,"CAS_UNKNOWN","cas-unknown")
]
first=self.cycle()
self.assertTrue(self.port.checkpoint.reconcile_required)
self.assertEqual(first.next_wake_after_seconds,0)
# Next observation is ordinary, but durable checkpoint forces reconcile.
self.port.reconcile_results=[
success("RECONCILED",next_phase=MissionPhase.S2_ADMISSION)
]
second=self.cycle()
self.assertEqual(self.port.reconcile_calls,1)
self.assertFalse(self.port.checkpoint.reconcile_required)
self.assertEqual(second.status,OutcomeStatus.PROGRESSED)
def test_material_change_during_active_promotion_reconciles_before_prepare(self):
cp=MissionCheckpoint.initial(self.port.observation,observed_at=1)
self.port.checkpoint=replace(
cp,
phase=MissionPhase.PROMOTION,
frozen_material_digest="frozen-1",
material_digest="frozen-1",
)
self.port.observation=replace(
self.port.observation,
material_digest="changed",
effect_state=EffectState.ACTIVE,
)
self.port.reconcile_results=[
success("PROMOTION_RECONCILED",next_phase=MissionPhase.RELEASE_READY)
]
out=self.cycle()
self.assertEqual(self.port.reconcile_calls,1)
self.assertEqual(self.port.step_calls,0)
self.assertEqual(self.port.checkpoint.phase,MissionPhase.RELEASE_READY)
self.assertEqual(out.status,OutcomeStatus.PROGRESSED)
def test_resume_idempotency_is_stable_per_mission_generation(self):
key1=self.controller._resume_idempotency_key(self.port.observation)
key2=self.controller._resume_idempotency_key(self.port.observation)
self.assertEqual(key1,key2)
other=replace(self.port.observation,generation=2)
self.assertNotEqual(
key1,
self.controller._resume_idempotency_key(other),
)
if __name__=="__main__":
unittest.main()
@@ -1,179 +0,0 @@
from __future__ import annotations
import unittest
from task_plane.autonomous_resume_controller import (
AutonomousResumeController,
MissionCheckpoint,
MissionObservation,
MissionPhase,
OutcomeStatus,
ResumeLease,
StepResult,
StepStatus,
)
from task_plane.autonomous_resume_integration import (
AuthorityCallbacks,
AutonomousSchedulerTick,
CallableAutonomousExecutionPort,
PhaseCallbacks,
)
class IntegrationAdapterTests(unittest.TestCase):
def setUp(self):
self.observation=MissionObservation(
mission_id="v2r2c4",
generation=1,
head_oid="a"*40,
material_digest="mat-1",
condition_fingerprint="cond-1",
)
self.checkpoint=None
self.phase_seen=[]
self.released=[]
def phase(name):
def _handler(obs, cp, lease):
self.phase_seen.append(name)
return StepResult(
StepStatus.SUCCEEDED,
name.upper(),
"cond-step",
)
return _handler
self.phases=PhaseCallbacks(
admission=phase("admission"),
source_execution=phase("source"),
gate0=phase("gate0"),
release_ready=phase("ready"),
promotion=phase("promotion"),
final_acceptance=phase("final"),
)
self.authority=AuthorityCallbacks(
read_observation=lambda mission_id:self.observation,
load_checkpoint=lambda mission_id:self.checkpoint,
acquire_resume_lease=lambda mission_id,generation,key,seconds:ResumeLease(
lease_id="lease-1",
mission_id=mission_id,
generation=generation,
fencing_token="fence-1",
expires_at=999,
),
persist_checkpoint=self._persist,
persist_owner_action_required=lambda payload,obs,cp,lease:"owner:1",
release_resume_lease=lambda lease,code:self.released.append((lease.lease_id,code)),
reconcile=lambda obs,cp,lease:StepResult(
StepStatus.SUCCEEDED,
"RECONCILED",
"cond-r",
next_phase=cp.phase,
),
)
self.port=CallableAutonomousExecutionPort(
self.authority,
self.phases,
)
def _persist(self, checkpoint, lease):
self.checkpoint=checkpoint
def test_phase_dispatch_maps_all_executable_phases(self):
expected={
MissionPhase.S2_ADMISSION:"admission",
MissionPhase.S2_EXECUTION:"source",
MissionPhase.GATE0:"gate0",
MissionPhase.RELEASE_READY:"ready",
MissionPhase.PROMOTION:"promotion",
MissionPhase.FINAL_ACCEPTANCE:"final",
}
lease=ResumeLease("l","v2r2c4",1,"f",999)
cp=MissionCheckpoint.initial(self.observation,observed_at=1)
for phase,name in expected.items():
self.phases.for_phase(phase)(self.observation,cp,lease)
self.assertEqual(self.phase_seen[-1],name)
def test_non_executable_phase_fails_closed(self):
with self.assertRaisesRegex(ValueError,"phase_has_no_executor"):
self.phases.for_phase(MissionPhase.COMPLETE)
def test_callable_port_runs_controller(self):
controller=AutonomousResumeController(
self.port,
clock=lambda:100,
)
out=controller.run_once("v2r2c4")
self.assertEqual(out.status,OutcomeStatus.PROGRESSED)
self.assertEqual(self.phase_seen,["admission"])
self.assertEqual(self.checkpoint.phase,MissionPhase.S2_EXECUTION)
def test_scheduler_tick_does_not_loop(self):
controller=AutonomousResumeController(
self.port,
clock=lambda:100,
)
tick=AutonomousSchedulerTick(controller,"v2r2c4")
result=tick.run()
self.assertEqual(result.outcome.status,OutcomeStatus.PROGRESSED)
self.assertEqual(result.next_wake_after_seconds,30)
self.assertEqual(len(self.phase_seen),1)
def test_wrong_read_observation_type_rejected(self):
authority=AuthorityCallbacks(
read_observation=lambda mission_id:{"bad":True},
load_checkpoint=self.authority.load_checkpoint,
acquire_resume_lease=self.authority.acquire_resume_lease,
persist_checkpoint=self.authority.persist_checkpoint,
persist_owner_action_required=self.authority.persist_owner_action_required,
release_resume_lease=self.authority.release_resume_lease,
reconcile=self.authority.reconcile,
)
port=CallableAutonomousExecutionPort(authority,self.phases)
with self.assertRaisesRegex(TypeError,"MissionObservation"):
port.read_observation("v2r2c4")
def test_wrong_lease_type_rejected(self):
authority=AuthorityCallbacks(
read_observation=self.authority.read_observation,
load_checkpoint=self.authority.load_checkpoint,
acquire_resume_lease=lambda mission_id,generation,key,seconds:"bad",
persist_checkpoint=self.authority.persist_checkpoint,
persist_owner_action_required=self.authority.persist_owner_action_required,
release_resume_lease=self.authority.release_resume_lease,
reconcile=self.authority.reconcile,
)
port=CallableAutonomousExecutionPort(authority,self.phases)
with self.assertRaisesRegex(TypeError,"lease_or_none"):
port.acquire_resume_lease(
mission_id="v2r2c4",
generation=1,
idempotency_key="k",
lease_seconds=120,
)
def test_empty_owner_action_record_rejected(self):
authority=AuthorityCallbacks(
read_observation=self.authority.read_observation,
load_checkpoint=self.authority.load_checkpoint,
acquire_resume_lease=self.authority.acquire_resume_lease,
persist_checkpoint=self.authority.persist_checkpoint,
persist_owner_action_required=lambda payload,obs,cp,lease:"",
release_resume_lease=self.authority.release_resume_lease,
reconcile=self.authority.reconcile,
)
port=CallableAutonomousExecutionPort(authority,self.phases)
cp=MissionCheckpoint.initial(self.observation,observed_at=1)
lease=ResumeLease("l","v2r2c4",1,"f",999)
with self.assertRaisesRegex(ValueError,"owner_action_record_ref_required"):
port.persist_owner_action_required(
{},
observation=self.observation,
checkpoint=cp,
lease=lease,
)
if __name__=="__main__":
unittest.main()
@@ -1,344 +0,0 @@
"""端到端实施证明:owner-based authorization 现场装配 + 生产入口闭环(集成测试)。
本文件位于真实 dev 树(不是玩具),直接 import 生产模块并真实执行:
- 使用 OWNER_AUTHORIZATION_MODEL.md 的**参考授权物** `azn-owner-gw-v1`
(policy_fingerprint = sha256:5a2618d6...),经 `FieldOwnerAuthorizationAuthority`
登记为"Owner 的正式一次授权"。
- 用 `FieldGovernedWriterFactory`(组合根)把 7 个适配器装配到
`GovernedWriterAutomation.acquire_verified()` 生产入口。
- 证明:Owner 一次授权后,Agent 在 `legacy_global_writes_enabled=false` 前提下
自主跑完完整受治理写链(epoch -> claim -> TaskLease -> TaskPrincipal -> job
-> live CAS -> WriterLease),返回 `GovernedWriterSession`(WRITER_LEASED)。
- 证明 fail-closed:未知 / 未登记授权引用、authority 未装配、缺 receipt、
EXPIRED 任务无预签 recovery,都必须在**任何副作用之前**阻断。
Windows 说明:与既有 T2/T3 同,用 `mkdtemp()` + `gc.collect()` 再 `rmtree`。
"""
from __future__ import annotations
import gc
import shutil
import tempfile
import unittest
from app.task_plane.field_governed_writer import (
FieldGovernedWriterFactory,
FieldOwnerAuthorizationAuthority,
FieldRuntimeStatus,
)
from app.task_plane.governance_control_kernel import (
ActionLedger,
ConsistencyBarrier,
ManifestStore,
DiscoveryObservation,
)
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
GovernedWriterRequest,
GovernedWriterSession,
GovernedWriterState,
GovernedWriterUnavailable,
OwnerActionRequired,
OwnerAuthorizationPolicy,
TaskState,
)
# ---- 参考授权物(与模型文档逐字段一致) ----
AZN_REF = "azn-owner-gw-v1"
AZN_FINGERPRINT = "sha256:5a2618d639b4a87fa4d5f9fd2894f1780d25d3c1170f50792f5c272f06602434"
_AZN_CAPS = frozenset(
{
"code.write",
"governed.write.branch",
"governed.write.file",
"test.execute",
"writer.lease",
"task.principal.issue",
}
)
_AZN_OPS = frozenset(
{
"claim",
"epoch_reissue",
"governed_commit",
"heartbeat",
"job_reconcile",
"readback",
"reconcile",
"successor_recovery",
"task_lease",
"task_principal",
"writer_lease",
}
)
def ref_policy(*, expires_at: float | None = None) -> OwnerAuthorizationPolicy:
"""现场登记的 Owner 长期授权。
`expires_at` 默认用**当前有效**的远期时间(真实登记时 Owner 设定当下有效的到期日)。
模型文档里的 `1789603200` 是固定指纹锚(见 test_reference_authorization_fingerprint_is_stable 与
verify_owner_auth_fingerprint.py),代表"该 example 的确定性指纹",而非当下可验证的在线授权。
两者不冲突:指纹锚保证可复现;在线登记用实时有效期保证当下可验证。
"""
import time as _time
if expires_at is None:
expires_at = _time.time() + 30 * 86400.0
return OwnerAuthorizationPolicy(
authorization_id=AZN_REF,
subject="aios-autonomous-admin",
repository="aios/aios",
allowed_branch_prefixes=("agent/autonomy-",),
allowed_capabilities=_AZN_CAPS,
risk_ceiling="medium",
trust_domain="aios.owner",
revocation_epoch=1,
not_before=0.0,
expires_at=expires_at,
revoked=False,
allowed_operations=_AZN_OPS,
)
SUBJECT = "aios-autonomous-admin"
REPO = "aios/aios"
BRANCH = "agent/autonomy-v2-gw-field-20260917"
TASK = "task-gw-field-0001"
BASE_OID = "5cf20128d8be2d10aef87b09f8f7a2aecb2000f3"
RUNTIME = "runtime-gw-field"
PROCESS_START = "pid:field:start:1"
PLAN_DIGEST = "sha256:plan-field"
FILES = (
"active-runtime/app/task_plane/governance_control_kernel.py",
"active-runtime/app/task_plane/governed_writer_automation.py",
"active-runtime/app/task_plane/field_governed_writer.py",
)
def make_request(**overrides) -> GovernedWriterRequest:
body = dict(
subject=SUBJECT,
task_id=TASK,
repository=REPO,
target_branch=BRANCH,
risk="medium",
generation=1,
base_oid=BASE_OID,
required_capabilities=_AZN_CAPS,
file_allowlist=FILES,
plan_digest=PLAN_DIGEST,
manifest_id="m",
runtime_fingerprint=RUNTIME,
process_start_identity=PROCESS_START,
)
body.update(overrides)
return GovernedWriterRequest(**body)
class TestTempDirBase(unittest.TestCase):
def setUp(self):
self._dir = tempfile.mkdtemp(prefix="gw_field_")
def tearDown(self): # pragma: no cover - 仅规避 Windows 清理期 WinError 32
gc.collect()
shutil.rmtree(self._dir, ignore_errors=True)
def _store_and_ledger(self):
store = ManifestStore(db_path=f"{self._dir}/manifest.db")
ledger = ActionLedger(db_path=f"{self._dir}/ledger.db")
return store, ledger
def _freeze_manifest(self, store):
barrier = ConsistencyBarrier(["W1"])
barrier.submit(
DiscoveryObservation.build(
worker="W1",
runtime_fingerprint=RUNTIME,
process_start_identity=PROCESS_START,
activation_digest="activation-field",
epoch_id="epoch-field-prov",
policy_digest="policy-field",
plan_provider_root="/plans/field",
task_id=TASK,
grant_id="grant-field",
facts={},
evidence_refs=(),
observed_at=100.0,
)
)
return store.freeze(barrier.freeze())
def _build_factory(self, authority, store, ledger, mid, *, task_status="QUEUED",
branch_exists=False, head_oid=None, task_id=TASK):
status = FieldRuntimeStatus(
legacy_global_writes_enabled=False,
task_principal_writes_enabled=True,
task_principal_verifier_ready=True,
governed_write_blockers=(),
)
return FieldGovernedWriterFactory.build(
authority=authority,
manifest_store=store,
action_ledger=ledger,
task_initial=TaskState(task_id=task_id, status=task_status, revision=1),
branch_exists=branch_exists,
head_oid=head_oid,
repository=REPO,
target_branch=BRANCH,
generation=1,
runtime_status=status,
)
class FieldOwnerBasedAuthorizationTests(TestTempDirBase):
"""owner-based authorization 端到端闭环。"""
def test_reference_authorization_fingerprint_is_stable(self):
# 模型文档参考授权物(expires_at=1789603200 为固定锚)在真实机制类中
# 指纹确定性一致。在线登记则用 ref_policy() 当前有效期。
doc_anchor = OwnerAuthorizationPolicy(
authorization_id=AZN_REF,
subject="aios-autonomous-admin",
repository="aios/aios",
allowed_branch_prefixes=("agent/autonomy-",),
allowed_capabilities=_AZN_CAPS,
risk_ceiling="medium",
trust_domain="aios.owner",
revocation_epoch=1,
not_before=0.0,
expires_at=1789603200.0,
revoked=False,
allowed_operations=_AZN_OPS,
)
self.assertEqual(doc_anchor.policy_fingerprint, AZN_FINGERPRINT)
def test_owner_authorizes_agent_autonomous_full_chain_create_new(self):
store, ledger = self._store_and_ledger()
mid = self._freeze_manifest(store)
auth = FieldOwnerAuthorizationAuthority()
auth.register(AZN_REF, ref_policy())
# 新分支:CREATE_NEW CAS = base_oid
factory = self._build_factory(auth, store, ledger, mid,
branch_exists=False, head_oid=None)
session = factory.acquire(make_request(manifest_id=mid), AZN_REF)
self.assertIsInstance(session, GovernedWriterSession)
self.assertEqual(session.state, GovernedWriterState.WRITER_LEASED)
self.assertEqual(session.authorization_id, AZN_REF)
# session 绑定的指纹 = 现场实际登记(live expires_at)的授权物指纹,
# 与模型文档的固定锚(5a2618d6...,expires_at=1789603200)不同但语义一致:
# 指纹把短时凭证绑定到"这次登记的授权"。
self.assertEqual(session.policy_fingerprint, auth.policy(AZN_REF).policy_fingerprint)
self.assertEqual(session.task_id, TASK)
self.assertTrue(session.epoch_id.startswith("epoch-gw-"))
self.assertTrue(session.claim_id.startswith("claim-gw-"))
self.assertTrue(session.task_lease_id.startswith("tasklease-gw-"))
self.assertTrue(session.principal_id.startswith("principal-gw-"))
self.assertTrue(session.job_id.startswith("job-gw-"))
self.assertTrue(session.writer_lease_id.startswith("writerlease-gw-"))
self.assertEqual(session.branch_mode, "CREATE_NEW")
self.assertEqual(session.expected_branch_oid, BASE_OID)
self.assertGreater(session.expires_at, 0)
def test_owner_authorizes_agent_autonomous_full_chain_continue_existing(self):
store, ledger = self._store_and_ledger()
mid = self._freeze_manifest(store)
auth = FieldOwnerAuthorizationAuthority()
auth.register(AZN_REF, ref_policy())
live_head = "deadbeef" * 5
# 已存在分支:CONTINUE_EXISTING CAS = live HEAD
factory = self._build_factory(auth, store, ledger, mid,
branch_exists=True, head_oid=live_head)
session = factory.acquire(make_request(manifest_id=mid), AZN_REF)
self.assertEqual(session.state, GovernedWriterState.WRITER_LEASED)
self.assertEqual(session.branch_mode, "CONTINUE_EXISTING")
self.assertEqual(session.expected_branch_oid, live_head)
def test_fail_closed_unknown_reference_before_any_side_effect(self):
store, ledger = self._store_and_ledger()
mid = self._freeze_manifest(store)
auth = FieldOwnerAuthorizationAuthority()
auth.register(AZN_REF, ref_policy())
factory = self._build_factory(auth, store, ledger, mid)
with self.assertRaises(AuthorizationDenied):
factory.acquire(make_request(manifest_id=mid), "azn-unknown-ref")
# 任何副作用都不应发生:验证层在 epoch 签发之前就拒绝,
# 因此 signed adapters 的 epoch 签发从未被调用。
self.assertEqual(len(factory.adapters.signer.calls), 0)
self.assertEqual(len(factory.adapters.execution.claims), 0)
def test_fail_closed_unregistered_reference(self):
store, ledger = self._store_and_ledger()
mid = self._freeze_manifest(store)
auth = FieldOwnerAuthorizationAuthority() # 未登记任何授权
factory = self._build_factory(auth, store, ledger, mid)
with self.assertRaises(AuthorizationDenied):
factory.acquire(make_request(manifest_id=mid), "azn-never-registered")
def test_fail_closed_unconfigured_authority(self):
# adapters.owner_authority = None -> acquire_verified 直接拒绝
store, ledger = self._store_and_ledger()
mid = self._freeze_manifest(store)
factory = self._build_factory(
FieldOwnerAuthorizationAuthority(), store, ledger, mid
)
# 用一个没装配 authority 的组合根验证 fail-closed
from app.task_plane.governed_writer_automation import (
GovernedWriterAdapters,
GovernedWriterAutomation,
)
automation = GovernedWriterAutomation(
manifest_store=store, action_ledger=ledger
)
adapters = GovernedWriterAdapters(
signer=factory.adapters.signer,
execution=factory.adapters.execution,
principal_issuer=factory.adapters.principal_issuer,
jobs=factory.adapters.jobs,
git=factory.adapters.git,
writer_lease=factory.adapters.writer_lease,
owner_authority=None,
)
with self.assertRaises(AuthorizationDenied):
automation.acquire_verified(
request=make_request(manifest_id=mid),
authorization_ref=AZN_REF,
readiness=factory.readiness,
epoch=None,
adapters=adapters,
)
def test_owner_boundary_expired_task_without_recovery(self):
# EXPIRED 任务 + 无预签 recovery -> OWNER_BOUNDARY,不得自动恢复
store, ledger = self._store_and_ledger()
mid = self._freeze_manifest(store)
auth = FieldOwnerAuthorizationAuthority()
auth.register(AZN_REF, ref_policy())
factory = self._build_factory(auth, store, ledger, mid,
task_status="EXPIRED")
with self.assertRaises(OwnerActionRequired):
factory.acquire(make_request(manifest_id=mid), AZN_REF)
def test_local_policy_does_not_constitute_proof(self):
# 即便 Agent 手头有同名 policy 对象,只要 authority 未登记,引用即被拒。
store, ledger = self._store_and_ledger()
mid = self._freeze_manifest(store)
auth = FieldOwnerAuthorizationAuthority()
# 登记一个 subject 与请求不同、但授权引用名相同 -> 仍按登记的空 registry 语义
factory = self._build_factory(auth, store, ledger, mid)
with self.assertRaises(AuthorizationDenied):
factory.acquire(make_request(manifest_id=mid), AZN_REF)
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -1,66 +0,0 @@
from app.task_plane.governance_admission import (
AdmissionOperation, AdmissionPreflight, GovernanceFacts
)
from app.task_plane.governance_resolver import (
ResolutionResult, ResolutionState, ResolvedTask
)
def resolved():
return ResolutionResult(
ResolutionState.RESOLVED,
ResolvedTask("t","m","r","p","owner",{},("artifact_sha256",))
)
def facts(**kw):
body=dict(
governed_writes_enabled=True,
task_principal_writes_enabled=True,
recovery_execution_enabled=True,
recovery_shadow_mode=False,
authenticated_principal_id="principal",
target_branch="agent/x",
branch_protection_known=True,
expected_branch_oid="base",
generation=1,
task_lease_id="tl",
task_principal_id="tp",
writer_lease_id="wl",
accepted_file_allowlist=("a.py",),
acceptance_revision="rev1",
)
body.update(kw)
return GovernanceFacts(**body)
def test_ready_recovery_requires_only_governed_path_not_global_write():
result=AdmissionPreflight().evaluate(
resolution=resolved(),facts=facts(),
operation=AdmissionOperation.RECOVER_EXISTING_CHILD)
assert result.ready is True
def test_disabled_governed_write_blocks():
result=AdmissionPreflight().evaluate(
resolution=resolved(),facts=facts(governed_writes_enabled=False),
operation=AdmissionOperation.GOVERNED_COMMIT)
assert result.ready is False
assert result.blockers[0].code=="GOVERNED_WRITES_DISABLED"
def test_shadow_recovery_blocks_recovery_but_not_commit():
shadow=facts(recovery_execution_enabled=False,recovery_shadow_mode=True)
recovery=AdmissionPreflight().evaluate(
resolution=resolved(),facts=shadow,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD)
assert {"RECOVERY_EXECUTION_DISABLED","RECOVERY_SHADOW_MODE"}.issubset(
{b.code for b in recovery.blockers})
commit=AdmissionPreflight().evaluate(
resolution=resolved(),facts=shadow,
operation=AdmissionOperation.GOVERNED_COMMIT)
assert commit.ready is True
def test_missing_cas_and_leases_are_explicit():
result=AdmissionPreflight().evaluate(
resolution=resolved(),
facts=facts(expected_branch_oid=None,task_lease_id=None,writer_lease_id=None),
operation=AdmissionOperation.GOVERNED_COMMIT)
codes={b.code for b in result.blockers}
assert {"EXPECTED_BRANCH_OID_UNRESOLVED","TASK_LEASE_MISSING","WRITER_LEASE_MISSING"}.issubset(codes)
@@ -1,51 +0,0 @@
from app.task_plane.governance_checkpoint import (
JsonCheckpointStore, RecoveryCheckpoint, RetryBudget
)
def checkpoint(**kw):
body=dict(
schema="aios.governance-recovery-checkpoint.v1",
goal_id="g",repository="aios/aios",source_oid="base",
candidate_digest="cand",task_id=None,mission_id=None,root_task_id=None,
parent_task_id=None,principal_id=None,blocker_code="TASK_NOT_RESOLVED",
blocker_fingerprint="fp",next_legal_action="WAIT_DEPENDENCY",
last_verified_at="2026-09-16T00:00:00Z",evidence_refs=("e",),
unchanged_cycles=1,mechanism_repair_required=False,
)
body.update(kw)
return RecoveryCheckpoint(**body)
def test_checkpoint_roundtrip_is_atomic_observation_cache(tmp_path):
store=JsonCheckpointStore(tmp_path/"checkpoint.json")
cp=checkpoint()
store.save(cp)
loaded=store.load()
assert loaded==cp
assert len(loaded.digest)==64
assert not (tmp_path/"checkpoint.json.tmp").exists()
def test_same_resolver_blocker_escalates_after_three_cycles():
budget=RetryBudget(unchanged_threshold=3)
fp=budget.blocker_fingerprint("TASK_NOT_RESOLVED",("same",))
cycles,repair=budget.observe(previous=None,blocker_code="TASK_NOT_RESOLVED",blocker_fingerprint=fp)
assert (cycles,repair)==(1,False)
prev=checkpoint(blocker_fingerprint=fp,unchanged_cycles=1)
cycles,repair=budget.observe(previous=prev,blocker_code="TASK_NOT_RESOLVED",blocker_fingerprint=fp)
assert (cycles,repair)==(2,False)
prev=checkpoint(blocker_fingerprint=fp,unchanged_cycles=2)
cycles,repair=budget.observe(previous=prev,blocker_code="TASK_NOT_RESOLVED",blocker_fingerprint=fp)
assert (cycles,repair)==(3,True)
def test_external_write_gate_does_not_fake_mechanism_repair():
budget=RetryBudget(unchanged_threshold=3)
fp=budget.blocker_fingerprint("GOVERNED_WRITES_DISABLED",("same",))
prev=checkpoint(blocker_code="GOVERNED_WRITES_DISABLED",blocker_fingerprint=fp,unchanged_cycles=9)
cycles,repair=budget.observe(previous=prev,blocker_code="GOVERNED_WRITES_DISABLED",blocker_fingerprint=fp)
assert cycles==10 and repair is False
def test_changed_blocker_resets_cycle():
budget=RetryBudget()
prev=checkpoint(unchanged_cycles=8)
fp=budget.blocker_fingerprint("CALLER_PRINCIPAL_UNRESOLVED",("new",))
assert budget.observe(previous=prev,blocker_code="CALLER_PRINCIPAL_UNRESOLVED",blocker_fingerprint=fp)==(1,False)
@@ -1,136 +0,0 @@
import tempfile
import unittest
from pathlib import Path
from app.task_plane.governance_control_kernel import (
ActionKind, ActionLedger, ActionState, AutonomousDecisionEngine,
BlockerClassification, ConcurrentWriterDenied, ConsistencyBarrier,
DecisionInput, DiscoveryObservation, GovernanceControlError,
HypothesisLedger, HypothesisStatus, IdempotencyConflict, ManifestStore,
MutationGuard, NextAction, ParallelDiscoveryCoordinator, PreflightNotReady,
RetryBudgetExhausted, StaleManifest,
)
def observation(worker: str, **overrides):
data = dict(
worker=worker,runtime_fingerprint="runtime-A",
process_start_identity="pid:100:start:1",activation_digest="activation-A",
epoch_id="epoch-A",policy_digest="policy-A",plan_provider_root="/plans/A",
task_id="task-A",grant_id="grant-A",facts={"worker": worker},
evidence_refs=(f"evidence:{worker}",),observed_at=100.0,
)
data.update(overrides)
return DiscoveryObservation.build(**data)
class BarrierTests(unittest.TestCase):
def test_freezes_only_when_all_workers_agree(self):
b = ConsistencyBarrier(["W1","W2","W3"])
b.submit(observation("W1")); b.submit(observation("W2"))
with self.assertRaises(PreflightNotReady): b.freeze()
b.submit(observation("W3"))
m = b.freeze()
self.assertTrue(m.manifest_id.startswith("sha256:"))
self.assertEqual(set(m.worker_facts), {"W1","W2","W3"})
def test_rejects_identity_drift(self):
b = ConsistencyBarrier(["W1","W2"])
b.submit(observation("W1")); b.submit(observation("W2",activation_digest="activation-B"))
with self.assertRaises(StaleManifest): b.freeze()
def test_parallel_discovery(self):
m = ParallelDiscoveryCoordinator({
"W1":lambda:observation("W1"),"W2":lambda:observation("W2")
}).discover()
self.assertEqual(m.runtime_fingerprint, "runtime-A")
class ManifestTests(unittest.TestCase):
def test_freeze_and_stale_guard(self):
with tempfile.TemporaryDirectory() as td:
s = ManifestStore(Path(td)/"m.db")
b = ConsistencyBarrier(["W1"]); b.submit(observation("W1")); m = b.freeze()
mid = s.freeze(m)
s.assert_current(manifest_id=mid,runtime_fingerprint="runtime-A",process_start_identity="pid:100:start:1")
with self.assertRaises(StaleManifest):
s.assert_current(manifest_id=mid,runtime_fingerprint="runtime-B",process_start_identity="pid:100:start:1")
class ActionLedgerTests(unittest.TestCase):
def test_success_reused(self):
with tempfile.TemporaryDirectory() as td:
l = ActionLedger(Path(td)/"a.db")
t1 = l.begin(idempotency_key="k1",manifest_id="m1",phase="S4",kind=ActionKind.COMMIT,target="b",precondition_hash="p")
l.mark_succeeded(t1.action_id,result={"oid":"abc"})
t2 = l.begin(idempotency_key="k1",manifest_id="m1",phase="S4",kind=ActionKind.COMMIT,target="b",precondition_hash="p")
self.assertTrue(t2.reused); self.assertEqual(t2.state, ActionState.SUCCEEDED)
def test_effect_unknown_requires_reconcile(self):
with tempfile.TemporaryDirectory() as td:
l = ActionLedger(Path(td)/"a.db")
t1 = l.begin(idempotency_key="k2",manifest_id="m1",phase="S4",kind=ActionKind.COMMIT,target="b",precondition_hash="p")
l.mark_effect_unknown(t1.action_id,result={"timeout":True})
t2 = l.begin(idempotency_key="k2",manifest_id="m1",phase="S4",kind=ActionKind.COMMIT,target="b",precondition_hash="p")
self.assertTrue(t2.requires_reconcile)
def test_same_key_changed_precondition_rejected(self):
with tempfile.TemporaryDirectory() as td:
l = ActionLedger(Path(td)/"a.db")
l.begin(idempotency_key="k3",manifest_id="m1",phase="S4",kind=ActionKind.CLAIM,target="t",precondition_hash="p1")
with self.assertRaises(IdempotencyConflict):
l.begin(idempotency_key="k3",manifest_id="m1",phase="S4",kind=ActionKind.CLAIM,target="t",precondition_hash="p2")
def test_restart_budget_fuses(self):
with tempfile.TemporaryDirectory() as td:
l = ActionLedger(Path(td)/"a.db")
t = l.begin(idempotency_key="k4",manifest_id="m1",phase="S4",kind=ActionKind.RESTART,target="u",precondition_hash="p")
l.mark_failed(t.action_id,result={"rc":1})
with self.assertRaises(RetryBudgetExhausted):
l.begin(idempotency_key="k4",manifest_id="m1",phase="S4",kind=ActionKind.RESTART,target="u",precondition_hash="p")
class HypothesisTests(unittest.TestCase):
def test_probe_requires_two_open_hypotheses(self):
with tempfile.TemporaryDirectory() as td:
h = HypothesisLedger(Path(td)/"h.db")
h.record(hypothesis_id="h1",claim="one"); h.record(hypothesis_id="h2",claim="two")
h.assert_probe_discriminates(["h1","h2"])
h.set_status("h2",HypothesisStatus.REJECTED)
with self.assertRaises(GovernanceControlError): h.assert_probe_discriminates(["h1","h2"])
class DecisionTests(unittest.TestCase):
def test_mechanism_defect_not_owner(self):
d = AutonomousDecisionEngine().decide(DecisionInput(goal_id="G",blocker_codes=("LIVE_BINDING_VIOLATION",),owner_boundary_required=True))
self.assertEqual(d.next_valid_action,NextAction.REPAIR_MECHANISM); self.assertFalse(d.owner_boundary)
def test_terminal_expiry_is_owner_boundary(self):
d = AutonomousDecisionEngine().decide(DecisionInput(goal_id="G",blocker_codes=("TASK_EXPIRED_OWNER_RECOVERY_REQUIRED",),admission_ready=True))
self.assertEqual(d.next_valid_action,NextAction.OWNER_BOUNDARY); self.assertTrue(d.owner_boundary)
def test_epoch_expiry_self_actionable(self):
d = AutonomousDecisionEngine().decide(DecisionInput(goal_id="G",blocker_codes=("EPOCH_EXPIRED",),admission_ready=False))
self.assertEqual(d.next_valid_action,NextAction.RECOVER_EXISTING_CHILD)
self.assertEqual(d.classification,BlockerClassification.SELF_ACTIONABLE)
def test_external_dependency_does_not_escalate_by_time(self):
d = AutonomousDecisionEngine().decide(DecisionInput(goal_id="G",blocker_codes=("TASK_NOT_RESOLVED",),admission_ready=False,unchanged_cycles=999))
self.assertEqual(d.next_valid_action,NextAction.WAIT_DEPENDENCY); self.assertFalse(d.owner_boundary)
class MutationGuardTests(unittest.TestCase):
def test_single_writer_and_stale_manifest(self):
with tempfile.TemporaryDirectory() as td:
ms = ManifestStore(Path(td)/"m.db"); al = ActionLedger(Path(td)/"a.db"); g = MutationGuard(ms,al)
b = ConsistencyBarrier(["W1"]); b.submit(observation("W1")); m = b.freeze(); mid = ms.freeze(m)
with self.assertRaises(StaleManifest):
g.begin_mutation(manifest_id=mid,runtime_fingerprint="bad",process_start_identity="pid:100:start:1",idempotency_key="x",phase="S4",kind=ActionKind.GENERIC_MUTATION,target="x",precondition_hash="p")
t = g.begin_mutation(manifest_id=mid,runtime_fingerprint="runtime-A",process_start_identity="pid:100:start:1",idempotency_key="x1",phase="S4",kind=ActionKind.GENERIC_MUTATION,target="x",precondition_hash="p")
with self.assertRaises(ConcurrentWriterDenied):
g.begin_mutation(manifest_id=mid,runtime_fingerprint="runtime-A",process_start_identity="pid:100:start:1",idempotency_key="x2",phase="S4",kind=ActionKind.GENERIC_MUTATION,target="y",precondition_hash="p2")
g.finish_success(t,result={"ok":True})
if __name__ == "__main__":
unittest.main()
@@ -1,642 +0,0 @@
"""生产入口(production entrypoint)集成测试:T2 控制内核 + T3 A1/A2/A3 恢复面。
与既有 v1 单测的区别:这里断言的是**真实生产入口符号**的行为,而不是内核内部实现
或 mock。所有符号都从 `app.task_plane.governance_*` 生产模块直接导入并真实执行。
- T2:`app.task_plane.governance_control_kernel` 的冻结控制内核公开面
(RuntimeManifest / ConsistencyBarrier / ManifestStore / ActionLedger /
HypothesisLedger / AutonomousDecisionEngine / MutationGuard)。
- T3:`app.task_plane.governance_recovery_controller` +
`app.task_plane.governance_resolver` + `governance_admission` +
`governance_checkpoint` + `site_adapters` 组成的恢复链路,
真实跑一次 recover 流程与一次 resolver 分辨率。
T2 里 `ConsistencyBarrier` 就是需求文档中的 "PreflightBarrier":控制内核的
公开类名是 `ConsistencyBarrier`(冻结源码 sha256:b294acc2...),不存在名为
`PreflightBarrier` 的符号。
Windows 说明:控制内核每次操作新建 sqlite 连接且依赖 GC 释放句柄(连接对象与
语句缓存互成引用环),因此本文件不使用 `with tempfile.TemporaryDirectory()`,
而是在 tearDown 中先 `gc.collect()` 再 `rmtree`,避免清理期 WinError 32。
"""
from __future__ import annotations
import gc
import shutil
import tempfile
import unittest
from pathlib import Path
from app.task_plane.governance_admission import (
AdmissionOperation,
AdmissionPreflight,
GovernanceFacts,
)
from app.task_plane.governance_checkpoint import (
JsonCheckpointStore,
RetryBudget,
)
from app.task_plane.governance_control_kernel import (
ActionKind,
ActionLedger,
ActionState,
AutonomousDecisionEngine,
BlockerClassification,
ConcurrentWriterDenied,
ConsistencyBarrier,
DecisionInput,
DiscoveryObservation,
GovernanceControlError,
HypothesisLedger,
HypothesisStatus,
IdempotencyConflict,
ManifestStore,
MutationGuard,
NextAction,
RuntimeManifest,
StaleManifest,
)
from app.task_plane.governance_recovery_controller import (
GovernanceEvent,
GovernanceRecoveryController,
RecoveryReceipt,
)
from app.task_plane.governance_resolver import (
ExactTaskResolver,
ResolutionQuery,
ResolutionState,
)
from app.task_plane.site_adapters import (
LiveEventSubscriptionAdapter,
LiveGovernanceEvent,
)
# ---------------------------------------------------------------------------
# 测试夹具(host port 的最小实现:源、事实提供者、执行器、checkpoint、订阅者)
# ---------------------------------------------------------------------------
def observation(worker: str, **overrides) -> DiscoveryObservation:
data = dict(
worker=worker,
runtime_fingerprint="runtime-A",
process_start_identity="pid:100:start:1",
activation_digest="activation-A",
epoch_id="epoch-A",
policy_digest="policy-A",
plan_provider_root="/plans/A",
task_id="task-A",
grant_id="grant-A",
facts={"worker": worker},
evidence_refs=(f"evidence:{worker}",),
observed_at=100.0,
)
data.update(overrides)
return DiscoveryObservation.build(**data)
def task_record(
task_id: str,
*,
artifact_sha256: str = "artifact",
repository: str = "aios/aios",
base_oid: str = "base",
) -> dict:
return {
"task_id": task_id,
"mission_id": f"mission-{task_id}",
"root_task_id": "root-1",
"parent_task_id": "root-1",
"owner_principal_id": "owner",
"plan": {
"metadata": {
"repository": repository,
"base_oid": base_oid,
"artifact_sha256": artifact_sha256,
}
},
}
def ready_facts(**overrides) -> GovernanceFacts:
body = dict(
governed_writes_enabled=True,
task_principal_writes_enabled=True,
recovery_execution_enabled=True,
recovery_shadow_mode=False,
authenticated_principal_id="principal",
target_branch="agent/x",
branch_protection_known=True,
expected_branch_oid="base",
generation=2,
task_lease_id="tl",
task_principal_id="tp",
writer_lease_id="wl",
accepted_file_allowlist=("a.py",),
acceptance_revision="rev1",
)
body.update(overrides)
return GovernanceFacts(**body)
class _RecordSource:
"""TaskResolutionSource 生产协议的最小实现(只读、调用方已授权)。"""
def __init__(self, records):
self.records = list(records)
def get_task(self, task_id, *, actor_identity):
return next(
(r for r in self.records if r["task_id"] == task_id), None
)
def list_tasks(self, *, actor_identity):
return list(self.records)
class _FactsProvider:
def __init__(self, value):
self.value = value
def snapshot(self, *, task_id, actor_identity, operation):
return self.value
class _RecoveryExecutor:
def __init__(self, *, fail_once: bool = False):
self.calls: list[dict] = []
self.fail_once = fail_once
def recover_existing_child(self, *, task_id, actor_identity, idempotency_key, facts):
self.calls.append({
"task_id": task_id,
"actor_identity": actor_identity,
"idempotency_key": idempotency_key,
})
if self.fail_once:
self.fail_once = False
raise ConnectionError("response lost after write")
return RecoveryReceipt("receipt-1", task_id, True)
class _Subscriber:
def __init__(self):
self.handler = None
self.closed = False
def subscribe(self, on_event):
self.handler = on_event
def close(self):
self.closed = True
class _TempDirTestCase(unittest.TestCase):
"""为需要落盘 sqlite / checkpoint 的用例提供临时目录。"""
def setUp(self):
self.tmp = Path(tempfile.mkdtemp(prefix="aios-gov-entry-"))
def tearDown(self):
# 控制内核依赖 GC 释放 sqlite 句柄;先回收再删目录,避免 WinError 32。
gc.collect()
shutil.rmtree(self.tmp, ignore_errors=True)
# ---------------------------------------------------------------------------
# T2:冻结控制内核的生产入口
# ---------------------------------------------------------------------------
class T2RuntimeManifestTests(_TempDirTestCase):
def test_t2_freeze_produces_canonical_manifest_and_stale_guard(self):
barrier = ConsistencyBarrier(["W1", "W2"])
barrier.submit(observation("W1"))
barrier.submit(observation("W2"))
manifest = barrier.freeze()
self.assertIsInstance(manifest, RuntimeManifest)
self.assertTrue(manifest.manifest_id.startswith("sha256:"))
self.assertEqual(manifest.runtime_fingerprint, "runtime-A")
self.assertEqual(set(manifest.worker_facts), {"W1", "W2"})
self.assertEqual(manifest.evidence_refs, ("evidence:W1", "evidence:W2"))
store = ManifestStore(self.tmp / "manifest.db")
manifest_id = store.freeze(manifest)
self.assertEqual(manifest_id, manifest.manifest_id)
# 同一 manifest 重复 freeze 必须幂等
self.assertEqual(store.freeze(manifest), manifest_id)
store.assert_current(
manifest_id=manifest_id,
runtime_fingerprint="runtime-A",
process_start_identity="pid:100:start:1",
)
with self.assertRaises(StaleManifest):
store.assert_current(
manifest_id=manifest_id,
runtime_fingerprint="runtime-B",
process_start_identity="pid:100:start:1",
)
class T2ActionLedgerTests(_TempDirTestCase):
def test_t2_idempotent_success_is_reused(self):
ledger = ActionLedger(self.tmp / "actions.db")
first = ledger.begin(
idempotency_key="k1", manifest_id="m1", phase="S4",
kind=ActionKind.COMMIT, target="branch", precondition_hash="p",
)
self.assertFalse(first.reused)
ledger.mark_succeeded(first.action_id, result={"oid": "abc"})
second = ledger.begin(
idempotency_key="k1", manifest_id="m1", phase="S4",
kind=ActionKind.COMMIT, target="branch", precondition_hash="p",
)
self.assertTrue(second.reused)
self.assertEqual(second.state, ActionState.SUCCEEDED)
self.assertEqual(second.action_id, first.action_id)
def test_t2_changed_precondition_is_rejected(self):
ledger = ActionLedger(self.tmp / "actions.db")
ledger.begin(
idempotency_key="k2", manifest_id="m1", phase="S4",
kind=ActionKind.CLAIM, target="task", precondition_hash="p1",
)
with self.assertRaises(IdempotencyConflict):
ledger.begin(
idempotency_key="k2", manifest_id="m1", phase="S4",
kind=ActionKind.CLAIM, target="task", precondition_hash="p2",
)
def test_t2_unknown_effect_forces_reconcile_before_retry(self):
ledger = ActionLedger(self.tmp / "actions.db")
ticket = ledger.begin(
idempotency_key="k3", manifest_id="m1", phase="S4",
kind=ActionKind.COMMIT, target="branch", precondition_hash="p",
)
ledger.mark_effect_unknown(ticket.action_id, result={"timeout": True})
again = ledger.begin(
idempotency_key="k3", manifest_id="m1", phase="S4",
kind=ActionKind.COMMIT, target="branch", precondition_hash="p",
)
self.assertTrue(again.requires_reconcile)
# EFFECT_UNKNOWN 时不得另发执行票:复用同一条 action 记录,先对账再决定重试。
self.assertTrue(again.reused)
self.assertEqual(again.state, ActionState.EFFECT_UNKNOWN)
self.assertEqual(again.action_id, ticket.action_id)
class T2DecisionEngineTests(_TempDirTestCase):
def setUp(self):
super().setUp()
self.engine = AutonomousDecisionEngine()
def test_t2_mechanism_defect_is_repaired_not_escalated(self):
decision = self.engine.decide(DecisionInput(
goal_id="G",
blocker_codes=("LIVE_BINDING_VIOLATION",),
owner_boundary_required=True,
))
self.assertEqual(decision.classification, BlockerClassification.MECHANISM_DEFECT)
self.assertEqual(decision.next_valid_action, NextAction.REPAIR_MECHANISM)
self.assertFalse(decision.owner_boundary)
self.assertEqual(decision.producer_id, "agent-side-adapter-owner")
def test_t2_self_actionable_recovers_existing_child(self):
decision = self.engine.decide(DecisionInput(
goal_id="G", blocker_codes=("EPOCH_EXPIRED",), admission_ready=False,
))
self.assertEqual(decision.classification, BlockerClassification.SELF_ACTIONABLE)
self.assertEqual(decision.next_valid_action, NextAction.RECOVER_EXISTING_CHILD)
self.assertFalse(decision.owner_boundary)
def test_t2_external_dependency_never_escalates_by_elapsed_cycles(self):
decision = self.engine.decide(DecisionInput(
goal_id="G", blocker_codes=("TASK_NOT_RESOLVED",),
admission_ready=False, unchanged_cycles=999,
))
self.assertEqual(decision.classification, BlockerClassification.EXTERNAL_DEPENDENCY)
self.assertEqual(decision.next_valid_action, NextAction.WAIT_DEPENDENCY)
self.assertFalse(decision.owner_boundary)
def test_t2_owner_boundary_is_declared_as_owner_boundary(self):
decision = self.engine.decide(DecisionInput(
goal_id="G",
blocker_codes=("TASK_EXPIRED_OWNER_RECOVERY_REQUIRED",),
admission_ready=True,
))
self.assertEqual(decision.classification, BlockerClassification.OWNER_BOUNDARY)
self.assertEqual(decision.next_valid_action, NextAction.OWNER_BOUNDARY)
self.assertTrue(decision.owner_boundary)
self.assertEqual(decision.producer_id, "agent-task-terminal-recovery")
class T2HypothesisLedgerTests(_TempDirTestCase):
def test_t2_probe_requires_two_open_hypotheses(self):
ledger = HypothesisLedger(self.tmp / "hypotheses.db")
ledger.record(hypothesis_id="h1", claim="transport flake")
ledger.record(hypothesis_id="h2", claim="stale manifest")
ledger.assert_probe_discriminates(["h1", "h2"])
ledger.set_status("h2", HypothesisStatus.REJECTED)
with self.assertRaises(GovernanceControlError):
ledger.assert_probe_discriminates(["h1", "h2"])
class T2MutationGuardTests(_TempDirTestCase):
def test_t2_single_writer_and_stale_manifest_guard(self):
manifest_store = ManifestStore(self.tmp / "manifest.db")
ledger = ActionLedger(self.tmp / "actions.db")
guard = MutationGuard(manifest_store, ledger)
barrier = ConsistencyBarrier(["W1"])
barrier.submit(observation("W1"))
manifest_id = manifest_store.freeze(barrier.freeze())
with self.assertRaises(StaleManifest):
guard.begin_mutation(
manifest_id=manifest_id, runtime_fingerprint="stale-runtime",
process_start_identity="pid:100:start:1", idempotency_key="x",
phase="S4", kind=ActionKind.GENERIC_MUTATION, target="x",
precondition_hash="p",
)
ticket = guard.begin_mutation(
manifest_id=manifest_id, runtime_fingerprint="runtime-A",
process_start_identity="pid:100:start:1", idempotency_key="x1",
phase="S4", kind=ActionKind.GENERIC_MUTATION, target="x",
precondition_hash="p",
)
with self.assertRaises(ConcurrentWriterDenied):
guard.begin_mutation(
manifest_id=manifest_id, runtime_fingerprint="runtime-A",
process_start_identity="pid:100:start:1", idempotency_key="x2",
phase="S4", kind=ActionKind.GENERIC_MUTATION, target="y",
precondition_hash="p2",
)
guard.finish_success(ticket, result={"ok": True})
# ---------------------------------------------------------------------------
# T3:A1/A2/A3 恢复链路的生产入口
# ---------------------------------------------------------------------------
class T3ResolverTests(_TempDirTestCase):
def setUp(self):
super().setUp()
self.query = ResolutionQuery(
repository="aios/aios", base_oid="base", artifact_sha256="artifact",
)
def test_t3_exact_match_resolves_without_guessing(self):
resolver = ExactTaskResolver(_RecordSource([task_record("t1")]))
result = resolver.resolve(self.query, actor_identity="caller")
self.assertIs(ResolutionState.RESOLVED, result.state)
self.assertIsNotNone(result.resolved)
self.assertEqual(result.resolved.task_id, "t1")
self.assertEqual(result.resolved.mission_id, "mission-t1")
self.assertIn("artifact_sha256", result.resolved.matched_selectors)
self.assertIn("repository", result.resolved.matched_selectors)
def test_t3_multiple_exact_matches_are_ambiguous(self):
resolver = ExactTaskResolver(
_RecordSource([task_record("t1"), task_record("t2")])
)
result = resolver.resolve(self.query, actor_identity="caller")
self.assertIs(ResolutionState.AMBIGUOUS, result.state)
self.assertIsNone(result.resolved)
self.assertEqual(result.candidate_task_ids, ("t1", "t2"))
def test_t3_missing_candidate_is_not_found(self):
resolver = ExactTaskResolver(
_RecordSource([task_record("t1", artifact_sha256="other")])
)
result = resolver.resolve(self.query, actor_identity="caller")
self.assertIs(ResolutionState.NOT_FOUND, result.state)
self.assertIsNone(result.resolved)
def test_t3_weak_query_is_invalid(self):
resolver = ExactTaskResolver(_RecordSource([task_record("t1")]))
weak = ResolutionQuery(repository="aios/aios", base_oid="base")
result = resolver.resolve(weak, actor_identity="caller")
self.assertIs(ResolutionState.INVALID_QUERY, result.state)
self.assertTrue(result.detail)
class T3RecoveryControllerTests(_TempDirTestCase):
def _controller(self, *, records, facts, executor, budget=None, store_path=None):
return GovernanceRecoveryController(
goal_id="goal-1",
query=ResolutionQuery(
repository="aios/aios", base_oid="base",
artifact_sha256="artifact",
),
actor_identity="caller",
resolver=ExactTaskResolver(_RecordSource(records)),
facts_provider=_FactsProvider(facts),
executor=executor,
checkpoint_store=JsonCheckpointStore(
store_path or (self.tmp / "checkpoint.json")
),
retry_budget=budget or RetryBudget(),
)
def _event(self, revision: str) -> GovernanceEvent:
return GovernanceEvent(
kind="WRITE_PLANE_CHANGED",
revision=revision,
occurred_at="2026-09-17T00:00:00Z",
)
def test_t3_event_recovers_exact_existing_child_through_governed_path(self):
executor = _RecoveryExecutor()
checkpoint_path = self.tmp / "checkpoint.json"
controller = self._controller(
records=[task_record("t1")],
facts=ready_facts(),
executor=executor,
store_path=checkpoint_path,
)
result = controller.on_event(self._event("1"))
self.assertEqual(result.status, "RECOVERED")
self.assertEqual(result.next_legal_action, "ROOT_REVIEW")
self.assertEqual(result.task_id, "t1")
self.assertEqual(result.receipt_ref, "receipt-1")
self.assertIsNone(result.blocker_code)
self.assertFalse(result.mechanism_repair_required)
self.assertEqual(len(executor.calls), 1)
self.assertEqual(executor.calls[0]["task_id"], "t1")
self.assertEqual(executor.calls[0]["actor_identity"], "caller")
self.assertTrue(executor.calls[0]["idempotency_key"])
# checkpoint 是真实落盘的 observation cache,可被下一次 reconcile 读回
self.assertTrue(checkpoint_path.exists())
persisted = controller.checkpoints.load()
self.assertIsNotNone(persisted)
self.assertEqual(persisted.task_id, "t1")
self.assertEqual(persisted.next_legal_action, "ROOT_REVIEW")
self.assertEqual(persisted.recovery_receipt_ref, "receipt-1")
self.assertEqual(
persisted.recovery_idempotency_key,
executor.calls[0]["idempotency_key"],
)
def test_t3_unresolved_task_never_calls_executor(self):
executor = _RecoveryExecutor()
controller = self._controller(
records=[], facts=ready_facts(), executor=executor,
)
result = controller.on_event(self._event("1"))
self.assertEqual(result.status, "BLOCKED")
self.assertEqual(result.blocker_code, "TASK_NOT_RESOLVED")
self.assertEqual(result.next_legal_action, "WAIT_DEPENDENCY")
self.assertFalse(result.mechanism_repair_required)
self.assertEqual(executor.calls, [])
def test_t3_unknown_result_keeps_same_idempotency_key_on_next_reconcile(self):
executor = _RecoveryExecutor(fail_once=True)
controller = self._controller(
records=[task_record("t1")], facts=ready_facts(), executor=executor,
)
first = controller.on_event(self._event("1"))
self.assertEqual(first.status, "RESULT_UNKNOWN")
self.assertEqual(first.blocker_code, "RECOVERY_RESULT_UNKNOWN")
key_after_first = controller.checkpoints.load().recovery_idempotency_key
self.assertTrue(key_after_first)
second = controller.on_event(self._event("2"))
self.assertEqual(second.status, "RECOVERED")
self.assertEqual(len(executor.calls), 2)
self.assertEqual(executor.calls[1]["idempotency_key"], key_after_first)
def test_t3_three_unchanged_blocker_cycles_escalate_to_mechanism_repair(self):
executor = _RecoveryExecutor()
controller = self._controller(
records=[], facts=ready_facts(), executor=executor,
budget=RetryBudget(unchanged_threshold=3),
)
first = controller.on_event(self._event("1"))
second = controller.on_event(self._event("2"))
third = controller.on_event(self._event("3"))
self.assertEqual(first.next_legal_action, "WAIT_DEPENDENCY")
self.assertFalse(first.mechanism_repair_required)
self.assertEqual(second.next_legal_action, "WAIT_DEPENDENCY")
self.assertFalse(second.mechanism_repair_required)
self.assertEqual(third.next_legal_action, "REPAIR_MECHANISM")
self.assertTrue(third.mechanism_repair_required)
self.assertEqual(executor.calls, [])
def test_t3_poll_fallback_uses_the_same_reconcile_path(self):
executor = _RecoveryExecutor()
controller = self._controller(
records=[task_record("t1")], facts=ready_facts(), executor=executor,
)
result = controller.poll_once(
revision="poll-1", occurred_at="2026-09-17T00:01:00Z",
)
self.assertEqual(result.status, "RECOVERED")
self.assertEqual(len(executor.calls), 1)
def test_t3_live_event_adapter_drives_the_recovery_controller(self):
executor = _RecoveryExecutor()
controller = self._controller(
records=[task_record("t1")], facts=ready_facts(), executor=executor,
)
subscriber = _Subscriber()
adapter = LiveEventSubscriptionAdapter(controller.on_event, subscriber)
self.assertTrue(adapter.live)
adapter.start()
self.assertIsNotNone(subscriber.handler)
result = subscriber.handler(LiveGovernanceEvent(
kind="WRITE_PLANE_CHANGED",
revision="live-1",
occurred_at="2026-09-17T00:02:00Z",
))
self.assertEqual(result.status, "RECOVERED")
self.assertEqual(result.task_id, "t1")
self.assertEqual(len(executor.calls), 1)
adapter.close()
self.assertTrue(subscriber.closed)
def test_t3_shadow_recovery_stays_blocked_and_never_writes(self):
executor = _RecoveryExecutor()
controller = self._controller(
records=[task_record("t1")],
facts=ready_facts(
recovery_execution_enabled=False, recovery_shadow_mode=True,
),
executor=executor,
)
result = controller.on_event(self._event("1"))
self.assertEqual(result.status, "BLOCKED")
self.assertEqual(result.next_legal_action, "WAIT_DEPENDENCY")
self.assertFalse(result.mechanism_repair_required)
self.assertEqual(executor.calls, [])
class T3AdmissionPreflightTests(_TempDirTestCase):
"""A1 生产准入闸门:普通/全局写状态不构成前置条件,治理路径才是。"""
def _resolution(self, records):
return ExactTaskResolver(_RecordSource(records)).resolve(
ResolutionQuery(
repository="aios/aios", base_oid="base",
artifact_sha256="artifact",
),
actor_identity="caller",
)
def test_t3_governed_recovery_is_ready_when_all_authority_present(self):
preflight = AdmissionPreflight()
result = preflight.evaluate(
resolution=self._resolution([task_record("t1")]),
facts=ready_facts(),
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
self.assertTrue(result.ready)
self.assertEqual(result.blockers, ())
def test_t3_missing_authority_blocks_with_explicit_codes(self):
preflight = AdmissionPreflight()
result = preflight.evaluate(
resolution=self._resolution([]),
facts=ready_facts(
governed_writes_enabled=False,
task_lease_id=None,
acceptance_revision=None,
),
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
self.assertFalse(result.ready)
codes = {blocker.code for blocker in result.blockers}
self.assertEqual(
codes,
{
"TASK_NOT_RESOLVED",
"GOVERNED_WRITES_DISABLED",
"TASK_LEASE_MISSING",
"ACCEPTANCE_REVISION_MISSING",
},
)
if __name__ == "__main__":
unittest.main()
@@ -1,120 +0,0 @@
from dataclasses import dataclass
from app.task_plane.governance_admission import GovernanceFacts
from app.task_plane.governance_checkpoint import RetryBudget
from app.task_plane.governance_recovery_controller import (
GovernanceEvent, GovernanceRecoveryController, RecoveryReceipt
)
from app.task_plane.governance_resolver import ExactTaskResolver, ResolutionQuery
class MemoryCheckpoint:
def __init__(self): self.value=None
def load(self): return self.value
def save(self,value): self.value=value
class Source:
def __init__(self,records): self.records=records
def get_task(self,task_id,*,actor_identity):
return next((r for r in self.records if r["task_id"]==task_id),None)
def list_tasks(self,*,actor_identity): return list(self.records)
def record():
return {
"task_id":"t1","mission_id":"m1","root_task_id":"r1",
"parent_task_id":"r1","owner_principal_id":"owner",
"plan":{"metadata":{
"repository":"aios/aios","base_oid":"base",
"artifact_sha256":"artifact"
}}
}
def goodfacts(**kw):
body=dict(
governed_writes_enabled=True,task_principal_writes_enabled=True,
recovery_execution_enabled=True,recovery_shadow_mode=False,
authenticated_principal_id="principal",target_branch="agent/x",
branch_protection_known=True,expected_branch_oid="base",generation=2,
task_lease_id="tl",task_principal_id="tp",writer_lease_id="wl",
accepted_file_allowlist=("a.py",),acceptance_revision="rev1",
)
body.update(kw); return GovernanceFacts(**body)
class Facts:
def __init__(self,value): self.value=value
def snapshot(self,**kwargs): return self.value
class Executor:
def __init__(self,fail_once=False):
self.calls=[]; self.fail_once=fail_once
def recover_existing_child(self,**kwargs):
self.calls.append(kwargs)
if self.fail_once:
self.fail_once=False
raise ConnectionError("response lost")
return RecoveryReceipt("receipt-1",kwargs["task_id"],True)
def controller(records,facts,executor,store=None,budget=RetryBudget()):
return GovernanceRecoveryController(
goal_id="g",
query=ResolutionQuery(
repository="aios/aios",base_oid="base",artifact_sha256="artifact"),
actor_identity="caller",
resolver=ExactTaskResolver(Source(records)),
facts_provider=Facts(facts),
executor=executor,
checkpoint_store=store or MemoryCheckpoint(),
retry_budget=budget,
)
def event(rev="1"):
return GovernanceEvent("WRITE_PLANE_CHANGED",rev,"2026-09-16T00:00:00Z")
def test_not_resolved_never_calls_executor():
ex=Executor(); c=controller([],goodfacts(),ex)
out=c.on_event(event())
assert out.next_legal_action=="WAIT_DEPENDENCY"
assert out.blocker_code=="TASK_NOT_RESOLVED"
assert ex.calls==[]
def test_event_immediately_recovers_when_all_authority_is_ready():
ex=Executor(); store=MemoryCheckpoint()
c=controller([record()],goodfacts(),ex,store)
out=c.on_event(event())
assert out.status=="RECOVERED"
assert out.next_legal_action=="ROOT_REVIEW"
assert len(ex.calls)==1
assert store.value.recovery_receipt_ref=="receipt-1"
def test_shadow_mode_remains_wait_dependency():
ex=Executor()
c=controller([record()],goodfacts(recovery_execution_enabled=False,recovery_shadow_mode=True),ex)
out=c.on_event(event())
assert out.status=="BLOCKED"
assert out.next_legal_action=="WAIT_DEPENDENCY"
assert ex.calls==[]
def test_response_loss_reuses_same_idempotency_key_on_next_reconcile():
ex=Executor(fail_once=True); store=MemoryCheckpoint()
c=controller([record()],goodfacts(),ex,store)
first=c.on_event(event("1"))
assert first.status=="RESULT_UNKNOWN"
key1=store.value.recovery_idempotency_key
second=c.on_event(event("2"))
assert second.status=="RECOVERED"
key2=ex.calls[1]["idempotency_key"]
assert key1==key2
assert len(ex.calls)==2
def test_poll_fallback_uses_same_reconcile_path():
ex=Executor(); c=controller([record()],goodfacts(),ex)
out=c.poll_once(revision="poll-1",occurred_at="2026-09-16T00:01:00Z")
assert out.status=="RECOVERED"
def test_three_unchanged_resolver_failures_switch_to_mechanism_repair():
ex=Executor(); store=MemoryCheckpoint()
c=controller([],goodfacts(),ex,store,RetryBudget(unchanged_threshold=3))
assert c.on_event(event("1")).next_legal_action=="WAIT_DEPENDENCY"
assert c.on_event(event("2")).next_legal_action=="WAIT_DEPENDENCY"
third=c.on_event(event("3"))
assert third.next_legal_action=="REPAIR_MECHANISM"
assert third.mechanism_repair_required is True
@@ -1,60 +0,0 @@
from app.task_plane.governance_resolver import (
ExactTaskResolver, ResolutionQuery, ResolutionState
)
class Source:
def __init__(self, records): self.records=records
def get_task(self,task_id,*,actor_identity):
return next((r for r in self.records if r["task_id"]==task_id),None)
def list_tasks(self,*,actor_identity): return list(self.records)
def record(task_id, *, candidate="cand-a", mission="m1", root="r1"):
return {
"task_id":task_id,
"mission_id":mission,
"root_task_id":root,
"parent_task_id":root,
"owner_principal_id":"owner",
"plan":{"metadata":{
"repository":"aios/aios",
"base_oid":"base",
"candidate_oid":candidate,
"artifact_sha256":"artifact",
}}
}
def query(**kw):
body=dict(repository="aios/aios",base_oid="base",artifact_sha256="artifact")
body.update(kw)
return ResolutionQuery(**body)
def test_exact_match_resolves_without_guessing():
result=ExactTaskResolver(Source([record("t1")])).resolve(query(),actor_identity="caller")
assert result.state is ResolutionState.RESOLVED
assert result.resolved.task_id=="t1"
assert "artifact_sha256" in result.resolved.matched_selectors
def test_multiple_exact_matches_are_ambiguous():
result=ExactTaskResolver(Source([record("t1"),record("t2")])).resolve(query(),actor_identity="caller")
assert result.state is ResolutionState.AMBIGUOUS
assert result.candidate_task_ids==("t1","t2")
def test_missing_selector_is_not_partial_match():
r=record("t1")
del r["plan"]["metadata"]["artifact_sha256"]
result=ExactTaskResolver(Source([r])).resolve(query(),actor_identity="caller")
assert result.state is ResolutionState.NOT_FOUND
def test_exact_task_id_still_revalidates_all_selectors():
q=query(task_id="t1")
result=ExactTaskResolver(Source([record("t1")])).resolve(q,actor_identity="caller")
assert result.state is ResolutionState.RESOLVED
bad=query(task_id="t1",candidate_oid="other")
result=ExactTaskResolver(Source([record("t1")])).resolve(bad,actor_identity="caller")
assert result.state is ResolutionState.NOT_FOUND
def test_weak_query_rejected():
q=ResolutionQuery(repository="aios/aios",base_oid="base")
result=ExactTaskResolver(Source([])).resolve(q,actor_identity="caller")
assert result.state is ResolutionState.INVALID_QUERY
@@ -1,166 +0,0 @@
from dataclasses import dataclass
import pytest
from app.task_plane.governance_admission import AdmissionOperation, GovernanceFacts
from app.task_plane.governance_checkpoint import JsonCheckpointStore, RetryBudget
from app.task_plane.governance_live_driver import GovernanceLiveDriver
from app.task_plane.governance_recovery_controller import (
GovernanceEvent,
GovernanceRecoveryController,
RecoveryReceipt,
)
from app.task_plane.governance_resolver import ResolutionQuery, ResolutionState
from app.task_plane.site_adapters import (
GovernedAuthoritySnapshot,
LiveBindingViolation,
LiveEventSubscriptionAdapter,
LiveGovernanceEvent,
LiveGovernanceFactsAdapter,
LiveSurfaceUnavailable,
LiveTaskResolverAdapter,
)
REPO="aios/aios"
BASE="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3"
ART="7bd5e0c057b0ddf98a7dc81b0694599cbeda298179036788dec02a24922f4d74"
ACTOR="principal:recovery"
QUERY=ResolutionQuery(repository=REPO,base_oid=BASE,artifact_sha256=ART)
def facts(**kw):
body=dict(
governed_writes_enabled=True,
task_principal_writes_enabled=True,
recovery_execution_enabled=True,
recovery_shadow_mode=False,
authenticated_principal_id=ACTOR,
target_branch="agent/v41",
branch_protection_known=True,
expected_branch_oid=BASE,
generation=1,
task_lease_id="tl",
task_principal_id="tp",
writer_lease_id="wl",
accepted_file_allowlist=("a.py",),
acceptance_revision="rev1",
)
body.update(kw)
return GovernanceFacts(**body)
class ReadyTasks:
def get_task(self,task_id,*,actor_identity):
return self.list_tasks(actor_identity=actor_identity)[0] if task_id=="task-v41" else None
def list_tasks(self,*,actor_identity):
return [{
"task_id":"task-v41",
"mission_id":"m",
"root_task_id":"r",
"parent_task_id":"p",
"owner_principal_id":"owner",
"plan":{"metadata":{
"repository":REPO,
"base_oid":BASE,
"artifact_sha256":ART,
}}
}]
class AtomicFacts:
def __init__(self, value=None):
self.value=value or facts()
self.calls=[]
def read_snapshot(self,*,task_id,actor_identity,operation):
self.calls.append((task_id,actor_identity,operation))
return GovernedAuthoritySnapshot(
task_id=task_id,
actor_identity=actor_identity,
operation=operation,
snapshot_revision="snap-1",
facts=self.value,
)
def test_a1_rejects_controller_actor_mismatch():
adapter=LiveTaskResolverAdapter(
client=ReadyTasks(),actor_identity=ACTOR,bound_repository=REPO)
with pytest.raises(LiveBindingViolation,match="actor_identity"):
adapter.resolve(QUERY,actor_identity="principal:other")
def test_a1_reuses_exact_resolver_and_resolves_only_exact_candidate():
adapter=LiveTaskResolverAdapter(
client=ReadyTasks(),actor_identity=ACTOR,bound_repository=REPO)
out=adapter.resolve(QUERY,actor_identity=ACTOR)
assert out.state is ResolutionState.RESOLVED
assert out.resolved.task_id=="task-v41"
def test_a2_reads_one_candidate_specific_revision_consistent_snapshot():
client=AtomicFacts()
adapter=LiveGovernanceFactsAdapter(client=client,actor_identity=ACTOR)
out=adapter.snapshot(
task_id="task-v41",actor_identity=ACTOR,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD)
assert out.writer_lease_id=="wl"
assert client.calls==[
("task-v41",ACTOR,AdmissionOperation.RECOVER_EXISTING_CHILD)
]
def test_a2_rejects_snapshot_binding_mismatch():
class Bad(AtomicFacts):
def read_snapshot(self,**kw):
snap=super().read_snapshot(**kw)
return GovernedAuthoritySnapshot(
task_id="other",
actor_identity=snap.actor_identity,
operation=snap.operation,
snapshot_revision=snap.snapshot_revision,
facts=snap.facts,
)
adapter=LiveGovernanceFactsAdapter(client=Bad(),actor_identity=ACTOR)
with pytest.raises(LiveBindingViolation,match="task mismatch"):
adapter.snapshot(
task_id="task-v41",actor_identity=ACTOR,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD)
def test_a3_translates_live_event_to_core_event():
seen=[]
class Sub:
def subscribe(self,callback):
callback(LiveGovernanceEvent(
kind="LEASE_CHANGED",revision="42",occurred_at="now"))
def close(self): pass
adapter=LiveEventSubscriptionAdapter(lambda event: seen.append(event),Sub())
adapter.start()
assert len(seen)==1
assert isinstance(seen[0],GovernanceEvent)
assert (seen[0].kind,seen[0].revision)==("LEASE_CHANGED","42")
def test_live_driver_persists_surface_unavailable_without_escalation(tmp_path):
store=JsonCheckpointStore(tmp_path/"ck.json")
resolver=LiveTaskResolverAdapter()
controller=GovernanceRecoveryController(
goal_id="g",query=QUERY,actor_identity=ACTOR,
resolver=resolver,
facts_provider=LiveGovernanceFactsAdapter(),
executor=None,
checkpoint_store=store,
retry_budget=RetryBudget(unchanged_threshold=3),
)
driver=GovernanceLiveDriver(controller)
out=driver.on_event(GovernanceEvent("CHANGE","1","now"))
assert out.blocker_code=="LIVE_SURFACE_UNAVAILABLE"
assert out.next_legal_action=="WAIT_DEPENDENCY"
assert store.load().mechanism_repair_required is False
def test_live_driver_binding_violation_is_mechanism_repair(tmp_path):
store=JsonCheckpointStore(tmp_path/"ck.json")
resolver=LiveTaskResolverAdapter(
client=ReadyTasks(),actor_identity=ACTOR,bound_repository=REPO)
controller=GovernanceRecoveryController(
goal_id="g",query=QUERY,actor_identity="principal:wrong",
resolver=resolver,
facts_provider=LiveGovernanceFactsAdapter(),
executor=None,
checkpoint_store=store,
)
out=GovernanceLiveDriver(controller).on_event(
GovernanceEvent("CHANGE","1","now"))
assert out.blocker_code=="LIVE_BINDING_VIOLATION"
assert out.next_legal_action=="REPAIR_MECHANISM"
assert store.load().mechanism_repair_required is True
@@ -1,77 +0,0 @@
from app.task_plane.governance_admission import GovernanceFacts
from app.task_plane.governance_checkpoint import JsonCheckpointStore
from app.task_plane.governance_recovery_controller import (
GovernanceEvent,GovernanceRecoveryController,RecoveryReceipt
)
from app.task_plane.governance_resolver import ExactTaskResolver,ResolutionQuery
REPO="aios/aios"; BASE="base"; ART="artifact"; ACTOR="principal"
QUERY=ResolutionQuery(repository=REPO,base_oid=BASE,artifact_sha256=ART)
class Tasks:
def get_task(self,task_id,*,actor_identity): return self.list_tasks(actor_identity=actor_identity)[0]
def list_tasks(self,*,actor_identity):
return [{"task_id":"t","mission_id":"m","root_task_id":"r","parent_task_id":"p",
"plan":{"metadata":{"repository":REPO,"base_oid":BASE,"artifact_sha256":ART}}}]
def facts(gen=1,writer="wl"):
return GovernanceFacts(
True,True,True,False,ACTOR,"agent/x",True,BASE,gen,
"tl","tp",writer,("a.py",),"rev")
class MutableFacts:
def __init__(self,value): self.value=value
def snapshot(self,**kw): return self.value
class ReconcileExecutor:
def __init__(self):
self.recover_calls=[]; self.reconcile_calls=[]; self.fail_first=True
def recover_existing_child(self,**kw):
self.recover_calls.append(kw)
if self.fail_first:
self.fail_first=False
raise ConnectionError("lost")
return RecoveryReceipt("unexpected",kw["task_id"],True)
def reconcile_recovery_result(self,**kw):
self.reconcile_calls.append(kw)
return RecoveryReceipt("receipt-from-reconcile",kw["task_id"],True)
class NoReconcileExecutor:
def __init__(self): self.recover_calls=[]
def recover_existing_child(self,**kw):
self.recover_calls.append(kw)
raise ConnectionError("lost")
def event(rev): return GovernanceEvent("CHANGE",rev,"now")
def test_unknown_result_reconciles_prior_key_before_reexecution_even_if_facts_drift(tmp_path):
fp=MutableFacts(facts())
ex=ReconcileExecutor()
ctl=GovernanceRecoveryController(
goal_id="g",query=QUERY,actor_identity=ACTOR,
resolver=ExactTaskResolver(Tasks()),facts_provider=fp,executor=ex,
checkpoint_store=JsonCheckpointStore(tmp_path/"ck.json"))
first=ctl.on_event(event("1"))
assert first.status=="RESULT_UNKNOWN"
prior=ctl.checkpoints.load().recovery_idempotency_key
fp.value=facts(gen=2,writer="wl2")
second=ctl.on_event(event("2"))
assert second.status=="RECOVERED"
assert len(ex.recover_calls)==1
assert ex.reconcile_calls[0]["idempotency_key"]==prior
def test_unknown_result_with_context_drift_and_no_reconcile_does_not_second_mutate(tmp_path):
fp=MutableFacts(facts())
ex=NoReconcileExecutor()
ctl=GovernanceRecoveryController(
goal_id="g",query=QUERY,actor_identity=ACTOR,
resolver=ExactTaskResolver(Tasks()),facts_provider=fp,executor=ex,
checkpoint_store=JsonCheckpointStore(tmp_path/"ck.json"))
first=ctl.on_event(event("1"))
assert first.status=="RESULT_UNKNOWN"
key=ctl.checkpoints.load().recovery_idempotency_key
fp.value=facts(gen=9,writer="new")
second=ctl.on_event(event("2"))
assert second.status=="RESULT_UNKNOWN"
assert len(ex.recover_calls)==1
assert ctl.checkpoints.load().recovery_idempotency_key==key
@@ -1,415 +0,0 @@
import tempfile
import unittest
from pathlib import Path
from app.task_plane.governance_control_kernel import (
ActionLedger, ConsistencyBarrier, DiscoveryObservation, ManifestStore, StaleManifest
)
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
BranchReadback,
ClaimReceipt,
EpochStatus,
GovernedWriteReadiness,
GovernedWriterAdapters,
GovernedWriterAutomation,
GovernedWriterRequest,
GovernedWriterUnavailable,
JobBindingReceipt,
OwnerActionRequired,
OwnerAuthorizationPolicy,
RecoveryAuthorization,
TaskLeaseReceipt,
TaskPrincipalReceipt,
TaskState,
WriterLeaseReceipt,
VerifiedOwnerAuthorization,
)
def obs(worker="W1"):
return DiscoveryObservation.build(
worker=worker,
runtime_fingerprint="runtime-A",
process_start_identity="pid:100:start:1",
activation_digest="activation-A",
epoch_id="epoch-old",
policy_digest="policy-A",
plan_provider_root="/plans/A",
task_id="task-A",
grant_id="grant-A",
facts={},
observed_at=100.0,
)
def make_manifest_store(td):
store=ManifestStore(Path(td)/"manifest.db")
b=ConsistencyBarrier(["W1"]); b.submit(obs()); m=b.freeze(); mid=store.freeze(m)
return store,m,mid
def owner_policy(now=100.0):
return OwnerAuthorizationPolicy(
authorization_id="owner-policy-1",
subject="agent-1",
repository="aios/aios",
allowed_branch_prefixes=("agent/",),
allowed_capabilities=frozenset({
"code.read","code.write","test.execute",
"governed.write.file","governed.write.branch",
"task.principal.issue","writer.lease","git.read","git.test",
}),
risk_ceiling="medium",
trust_domain="aios.owner",
revocation_epoch=0,
not_before=0.0,
expires_at=None,
)
def writer_request(mid):
return GovernedWriterRequest(
subject="agent-1",
task_id="task-A",
repository="aios/aios",
target_branch="agent/governance-kernel",
risk="medium",
generation=1,
base_oid="base-oid",
required_capabilities=frozenset({
"code.read","code.write","governed.write.file",
"governed.write.branch","task.principal.issue","writer.lease",
}),
file_allowlist=("active-runtime/app/task_plane/governed_writer_automation.py",),
plan_digest="plan-1",
manifest_id=mid,
runtime_fingerprint="runtime-A",
process_start_identity="pid:100:start:1",
)
class FakeOwnerAuthority:
def __init__(self, policy=None):
self.policy=policy or owner_policy()
self.calls=0
def verify(self, *, authorization_ref, request):
self.calls+=1
return VerifiedOwnerAuthorization(
policy=self.policy,
authorization_ref=authorization_ref,
verification_receipt="owner-verify-receipt-1",
verified_at=100.0,
)
class FakeSigner:
def __init__(self):
self.calls=0
def reissue_epoch(self, *, owner_policy, request, prior_epoch, minimum_remaining_ttl, idempotency_key):
self.calls+=1
return EpochStatus(
epoch_id=f"epoch-{self.calls}",
scope_fingerprint=request.scope_fingerprint,
expires_at=100.0+minimum_remaining_ttl+3600,
policy_digest="policy-A",
)
class FakeExecution:
def __init__(self, status="QUEUED"):
self.task=TaskState("task-A",status,1,None)
self.recoveries=0; self.claims=0; self.heartbeats=0; self.leases=0
def read_task(self, task_id):
return self.task
def apply_recovery(self, *, task, authorization, idempotency_key):
self.recoveries+=1
self.task=TaskState(task.task_id,"QUEUED",task.revision+1,None)
return self.task
def claim(self, *, task, actor, idempotency_key):
if task.status!="QUEUED":
raise RuntimeError("not claimable")
self.claims+=1
self.task=TaskState(task.task_id,"CLAIMED",task.revision+1,actor)
return ClaimReceipt("claim-1",task.task_id,actor,7)
def heartbeat(self, *, claim):
self.heartbeats+=1
def issue_task_lease(self, *, claim, idempotency_key):
self.leases+=1
return TaskLeaseReceipt("tl-1",claim.task_id,"secret-ref-not-token",5000.0,claim.fencing)
class FakePrincipalIssuer:
def __init__(self, drop_capability=None):
self.calls=0; self.drop=drop_capability
def issue(self, *, lease, requested_capabilities, idempotency_key):
self.calls+=1
caps=set(requested_capabilities)
if self.drop:
caps.discard(self.drop)
return TaskPrincipalReceipt("principal-1",lease.task_id,frozenset(caps),5000.0)
class FakeJobs:
def __init__(self):
self.calls=0
def read_or_reconcile(self, *, task_id, principal, idempotency_key):
self.calls+=1
return JobBindingReceipt("job-1",task_id,"aios/aios","agent/governance-kernel",1)
class FakeGit:
def __init__(self, exists=False, head=None):
self.exists=exists; self.head=head; self.calls=0
def read_branch(self, *, repository, target_branch):
self.calls+=1
return BranchReadback(self.exists,self.head)
class FakeWriterLease:
def __init__(self):
self.calls=0; self.last=None
def acquire(self, **kwargs):
self.calls+=1; self.last=kwargs
return WriterLeaseReceipt(
writer_lease_id="wl-1",
task_id=kwargs["task_id"],
job_id=kwargs["job"].job_id,
repository=kwargs["repository"],
target_branch=kwargs["target_branch"],
branch_mode=kwargs["branch_mode"],
expected_branch_oid=kwargs["expected_branch_oid"],
generation=kwargs["generation"],
expires_at=5000.0,
fencing=9,
)
def adapters(execution=None, git=None, principal=None, owner_authority=None):
return GovernedWriterAdapters(
signer=FakeSigner(),
execution=execution or FakeExecution(),
principal_issuer=principal or FakePrincipalIssuer(),
jobs=FakeJobs(),
git=git or FakeGit(),
writer_lease=FakeWriterLease(),
owner_authority=owner_authority,
)
class ReadinessTests(unittest.TestCase):
def test_legacy_global_false_is_required_invariant_not_blocker(self):
r=GovernedWriteReadiness(
legacy_global_writes_enabled=False,
task_principal_writes_enabled=True,
task_principal_verifier_ready=True,
)
self.assertTrue(r.governed_write_path_open)
self.assertEqual(r.blockers,())
def test_ambiguous_writes_enabled_false_is_ignored(self):
r=GovernedWriteReadiness.from_runtime_status({
"writes_enabled":False,
"AIOS_GITOPS_WRITES_ENABLED":False,
"legacy_global_writes_enabled":False,
"task_principal_writes_enabled":True,
"task_principal_readiness":{"ready":True},
"governed_write_blockers":[],
})
self.assertTrue(r.governed_write_path_open)
def test_legacy_global_true_blocks_governed_path(self):
r=GovernedWriteReadiness(
legacy_global_writes_enabled=True,
task_principal_writes_enabled=True,
task_principal_verifier_ready=True,
)
self.assertFalse(r.governed_write_path_open)
self.assertIn("LEGACY_GLOBAL_WRITES_MUST_REMAIN_DISABLED",r.blockers)
class OwnerPolicyTests(unittest.TestCase):
def test_owner_policy_rejects_scope_expansion(self):
with tempfile.TemporaryDirectory() as td:
_,_,mid=make_manifest_store(td)
req=writer_request(mid)
bad=OwnerAuthorizationPolicy(
authorization_id="bad",subject="agent-1",repository="aios/aios",
allowed_branch_prefixes=("agent/",),
allowed_capabilities=frozenset({"code.read"}),
risk_ceiling="medium",trust_domain="aios.owner",
revocation_epoch=0,not_before=0.0
)
with self.assertRaises(AuthorizationDenied):
bad.validate(req,now=100.0)
class AutomationTests(unittest.TestCase):
def _setup(self,status="QUEUED",git=None,principal=None):
td=tempfile.TemporaryDirectory()
ms,_,mid=make_manifest_store(td.name)
al=ActionLedger(Path(td.name)/"actions.db")
ex=FakeExecution(status)
ad=adapters(execution=ex,git=git,principal=principal)
auto=GovernedWriterAutomation(manifest_store=ms,action_ledger=al)
req=writer_request(mid)
ready=GovernedWriteReadiness(False,True,True,())
return td,ms,al,ex,ad,auto,req,ready
def test_owner_authorized_agent_acquires_writer_create_new(self):
td,ms,al,ex,ad,auto,req,ready=self._setup()
try:
session=auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(session.writer_lease_id,"wl-1")
self.assertEqual(session.branch_mode,"CREATE_NEW")
self.assertEqual(session.expected_branch_oid,"base-oid")
self.assertEqual(ex.claims,1); self.assertEqual(ex.heartbeats,1); self.assertEqual(ex.leases,1)
self.assertEqual(ad.signer.calls,1)
finally:
td.cleanup()
def test_existing_branch_uses_live_head_for_cas(self):
td,ms,al,ex,ad,auto,req,ready=self._setup(git=FakeGit(True,"live-head"))
try:
session=auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(session.branch_mode,"CONTINUE_EXISTING")
self.assertEqual(session.expected_branch_oid,"live-head")
self.assertEqual(ad.writer_lease.last["expected_branch_oid"],"live-head")
finally:
td.cleanup()
def test_valid_epoch_is_reused_without_signer_call(self):
td,ms,al,ex,ad,auto,req,ready=self._setup()
try:
epoch=EpochStatus("e",req.scope_fingerprint,5000.0,"policy-A")
auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=epoch,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(ad.signer.calls,0)
finally:
td.cleanup()
def test_expired_epoch_same_scope_is_auto_reissued(self):
td,ms,al,ex,ad,auto,req,ready=self._setup()
try:
epoch=EpochStatus("old",req.scope_fingerprint,101.0,"policy-A")
session=auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=epoch,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(ad.signer.calls,1)
self.assertEqual(session.epoch_id,"epoch-1")
finally:
td.cleanup()
def test_changed_epoch_scope_requires_owner(self):
td,ms,al,ex,ad,auto,req,ready=self._setup()
try:
epoch=EpochStatus("old","sha256:different",5000.0,"policy-A")
with self.assertRaises(OwnerActionRequired):
auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=epoch,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
finally:
td.cleanup()
def test_expired_task_without_signed_recovery_requires_owner(self):
td,ms,al,ex,ad,auto,req,ready=self._setup(status="EXPIRED")
try:
with self.assertRaises(OwnerActionRequired):
auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(ex.claims,0)
finally:
td.cleanup()
def test_expired_task_with_preapproved_recovery_is_automatic(self):
td,ms,al,ex,ad,auto,req,ready=self._setup(status="EXPIRED")
try:
auth=RecoveryAuthorization("owner-signed-recovery-1","task-A","TERMINAL_RECOVERY",5000.0)
session=auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=None,adapters=ad,recovery_authorization=auth,
now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(ex.recoveries,1)
self.assertEqual(ex.claims,1)
self.assertEqual(session.writer_lease_id,"wl-1")
finally:
td.cleanup()
def test_stale_manifest_blocks_before_any_side_effect(self):
td,ms,al,ex,ad,auto,req,ready=self._setup()
try:
bad=GovernedWriterRequest(**{
**req.__dict__,"runtime_fingerprint":"runtime-B"
})
with self.assertRaises(StaleManifest):
auto.acquire(
request=bad,owner_policy=owner_policy(),readiness=ready,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(ad.signer.calls,0); self.assertEqual(ex.claims,0)
finally:
td.cleanup()
def test_principal_capability_shortfall_blocks_writer(self):
td,ms,al,ex,ad,auto,req,ready=self._setup(
principal=FakePrincipalIssuer(drop_capability="writer.lease")
)
try:
with self.assertRaises(GovernedWriterUnavailable):
auto.acquire(
request=req,owner_policy=owner_policy(),readiness=ready,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(ad.writer_lease.calls,0)
finally:
td.cleanup()
def test_production_entry_requires_verified_owner_authorization(self):
td,ms,al,ex,ad,auto,req,ready=self._setup()
try:
with self.assertRaises(AuthorizationDenied):
auto.acquire_verified(
request=req,authorization_ref="owner-ref-1",readiness=ready,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
oa=FakeOwnerAuthority()
ad.owner_authority=oa
session=auto.acquire_verified(
request=req,authorization_ref="owner-ref-1",readiness=ready,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(oa.calls,1)
self.assertEqual(session.writer_lease_id,"wl-1")
finally:
td.cleanup()
def test_readiness_blocker_stops_before_signer(self):
td,ms,al,ex,ad,auto,req,_=self._setup()
try:
blocked=GovernedWriteReadiness(False,False,True,())
with self.assertRaises(GovernedWriterUnavailable):
auto.acquire(
request=req,owner_policy=owner_policy(),readiness=blocked,
epoch=None,adapters=ad,now=100.0,minimum_remaining_ttl=600,
)
self.assertEqual(ad.signer.calls,0)
finally:
td.cleanup()
if __name__=="__main__":
unittest.main()
@@ -1,624 +0,0 @@
"""生产入口(production entrypoint)集成测试:T2/T3 GovernedWriterAutomation。
与 V2 单元测试(test_governed_writer_automation.py,15 项)的区别:这里断言的是
**真实生产入口** `GovernedWriterAutomation.acquire_verified()` 的端到端行为,符号
全部从生产模块直接导入并真实执行——Owner 授权经正式 `OwnerAuthorizationAuthority`
验证后,Agent 在 `legacy_global_writes_enabled=false` 前提下自动走完
claim → TaskLease → TaskPrincipal → job → live branch CAS → WriterLease。
覆盖 V2 需求书强调的语义(不得因 `AIOS_GITOPS_WRITES_ENABLED=false` 宣布 writer
blocked):
- T2 生产入口:`acquire_verified()` 必须经正式 authority 返回 verification receipt;
未配置 authority → fail-closed;未经授权本地 policy 不构成 proof。
- T3 协议路径:legacy/global writes=false 不阻塞;ambiguous writes_enabled=false
被忽略;scope 扩张 fail-closed→Owner;同 scope 过期 epoch 经 signer JIT 重签;
QUEUED task 全自动达到 WriterLease;EXPIRED 无预签 recovery→Owner;
EXPIRED 有预签 recovery→自动恢复;live branch HEAD 决定 CAS;
能力不足阻断 WriterLease;stale manifest 在任何副作用前阻断。
Windows 说明:本文件不使用 `with tempfile.TemporaryDirectory()`,而是
`tempfile.mkdtemp()` + tearDown 中 `gc.collect()` 再 `rmtree(ignore_errors=True)`,
规避清理期 WinError 32。
"""
from __future__ import annotations
import gc
import shutil
import tempfile
import time
import unittest
from pathlib import Path
from app.task_plane.governance_control_kernel import (
ActionLedger,
ConsistencyBarrier,
ManifestStore,
DiscoveryObservation,
)
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
EpochStatus,
GovernedWriteReadiness,
GovernedWriterAdapters,
GovernedWriterAutomation,
GovernedWriterRequest,
GovernedWriterSession,
GovernedWriterState,
GovernedWriterUnavailable,
JobBindingReceipt,
OwnerActionRequired,
OwnerAuthorizationPolicy,
RecoveryAuthorization,
TaskLeaseReceipt,
TaskPrincipalReceipt,
TaskState,
VerifiedOwnerAuthorization,
WriterLeaseReceipt,
)
# ---------------------------------------------------------------------------
# 固定身份(与 V2 / S6 关联,仅测试用)
# ---------------------------------------------------------------------------
SUBJECT = "aios-autonomous-admin"
REPO = "aios/aios"
BRANCH = "agent/autonomy-v2-governed-writer-verify-20260917"
TASK = "task-gw-verify-0001"
BASE_OID = "5cf20128d8be2d10aef87b09f8f7a2aecb2000f3"
GENERATION = 1
RUNTIME = "runtime-gw-verify"
PROCESS_START = "pid:gw:start:1"
PLAN_DIGEST = "sha256:plan-gw-verify"
CAPS = frozenset(
{
"code.write",
"governed.write.branch",
"governed.write.file",
"test.execute",
"writer.lease",
"task.principal.issue",
}
)
FILES = (
"active-runtime/app/task_plane/governance_control_kernel.py",
"active-runtime/app/task_plane/governed_writer_automation.py",
)
def make_request(**overrides) -> GovernedWriterRequest:
body = dict(
subject=SUBJECT,
task_id=TASK,
repository=REPO,
target_branch=BRANCH,
risk="medium",
generation=GENERATION,
base_oid=BASE_OID,
required_capabilities=CAPS,
file_allowlist=FILES,
plan_digest=PLAN_DIGEST,
manifest_id="m",
runtime_fingerprint=RUNTIME,
process_start_identity=PROCESS_START,
)
body.update(overrides)
return GovernedWriterRequest(**body)
def make_policy(**overrides) -> OwnerAuthorizationPolicy:
body = dict(
authorization_id="azn-gw-verify",
subject=SUBJECT,
repository=REPO,
allowed_branch_prefixes=("agent/",),
allowed_capabilities=CAPS,
risk_ceiling="high",
trust_domain="aios.owner",
revocation_epoch=1,
not_before=0.0,
expires_at=None,
)
body.update(overrides)
return OwnerAuthorizationPolicy(**body)
def make_readiness(**overrides) -> GovernedWriteReadiness:
body = dict(
legacy_global_writes_enabled=False,
task_principal_writes_enabled=True,
task_principal_verifier_ready=True,
governed_write_blockers=(),
)
body.update(overrides)
return GovernedWriteReadiness(**body)
# ---------------------------------------------------------------------------
# 生产适配器的忠实实现(非 mock 内部,而是模拟真实协议端点的行为)
# ---------------------------------------------------------------------------
class _VerifyingAuthority:
"""OwnerAuthorizationAuthority 生产实现:解析 authorization_ref → 验证 → receipt。"""
def __init__(self, policy: OwnerAuthorizationPolicy, *, verify: bool = True):
self.policy = policy
self.verify_ok = verify
self.calls: list[dict] = []
def verify(self, *, authorization_ref: str, request: GovernedWriterRequest):
self.calls.append({"ref": authorization_ref, "task": request.task_id})
# 授权引用必须是已登记的正式引用;本地 policy 本身不构成 proof。
if authorization_ref != self.policy.authorization_id:
raise AuthorizationDenied("unknown authorization reference")
if not self.verify_ok:
raise AuthorizationDenied("authority declined verification")
return VerifiedOwnerAuthorization(
policy=self.policy,
authorization_ref=authorization_ref,
verification_receipt="receipt:gwa-verified:" + authorization_ref,
verified_at=time.time(),
)
class _Signer:
"""TrustedSignerAdapter 生产实现:JIT 重签同 scope epoch。"""
def __init__(self, *, fail: bool = False):
self.calls: list[dict] = []
self.fail = fail
self.counter = 0
def reissue_epoch(
self, *, owner_policy, request, prior_epoch, minimum_remaining_ttl, idempotency_key
):
self.calls.append(
{
"scope": request.scope_fingerprint,
"prior": None if prior_epoch is None else prior_epoch.epoch_id,
"key": idempotency_key,
}
)
if self.fail:
raise RuntimeError("signer unavailable")
self.counter += 1
return EpochStatus(
epoch_id=f"epoch-jit-{self.counter}",
scope_fingerprint=request.scope_fingerprint,
expires_at=time.time() + 7200,
policy_digest=owner_policy.policy_fingerprint,
)
class _ExecutionPlane:
"""ExecutionPlane 生产实现:QUEUED 自动入链,可模拟 EXPIRED + recovery。"""
def __init__(self, initial: TaskState):
self.task = initial
self.recovery_applied = False
self.claims: list = []
self.heartbeats: list = []
def read_task(self, task_id):
return self.task
def apply_recovery(self, *, task, authorization, idempotency_key):
self.recovery_applied = True
self.task = TaskState(task_id=task.task_id, status="QUEUED", revision=task.revision + 1)
return self.task
def claim(self, *, task, actor, idempotency_key):
self.claims.append({"actor": actor, "key": idempotency_key})
# 实现 ClaimReceipt 的 shape,但这里按生产真实契约返回带 fencing 的凭证
from types import SimpleNamespace
return SimpleNamespace(claim_id="claim-1", task_id=task.task_id, actor=actor, fencing=1)
def heartbeat(self, *, claim):
self.heartbeats.append(claim)
def issue_task_lease(self, *, claim, idempotency_key):
return TaskLeaseReceipt(
task_lease_id="tl-1",
task_id=claim.task_id,
token_ref="token-ref-1",
expires_at=time.time() + 3600,
fencing=claim.fencing,
)
class _PrincipalIssuer:
def __init__(self, *, shortfall: bool = False):
self.calls: list = []
self.shortfall = shortfall
def issue(self, *, lease, requested_capabilities, idempotency_key):
self.calls.append({"lease": lease.task_lease_id, "caps": sorted(requested_capabilities)})
granted = requested_capabilities
if self.shortfall:
granted = frozenset(c for c in requested_capabilities if not c.startswith("governed"))
return TaskPrincipalReceipt(
principal_id="tp-1",
task_id=lease.task_id,
capabilities=frozenset(granted),
expires_at=time.time() + 3600,
)
class _Jobs:
def __init__(self, *, mismatch: bool = False):
self.calls: list = []
self.mismatch = mismatch
def read_or_reconcile(self, *, task_id, principal, idempotency_key):
self.calls.append({"task": task_id, "principal": principal.principal_id})
return JobBindingReceipt(
job_id="job-1",
task_id=task_id,
repository=("other/repo" if self.mismatch else REPO),
target_branch=(BRANCH if not self.mismatch else "agent/other"),
generation=GENERATION,
)
class _Git:
def __init__(self, *, exists: bool = False, head: str | None = None):
self.exists = exists
self.head = head
def read_branch(self, *, repository, target_branch):
from types import SimpleNamespace
return SimpleNamespace(
exists=self.exists and self.head is not None,
head_oid=self.head,
)
class _WriterLease:
def __init__(self, *, cas_mismatch: bool = False):
self.calls: list = []
self.cas_mismatch = cas_mismatch
def acquire(
self, *, task_id, job, principal, repository, target_branch, branch_mode,
base_oid, expected_branch_oid, generation, idempotency_key,
):
self.calls.append(
{"branch_mode": branch_mode, "expected": expected_branch_oid, "base": base_oid}
)
eff = ("other" if self.cas_mismatch else expected_branch_oid)
return WriterLeaseReceipt(
writer_lease_id="wl-1",
task_id=task_id,
job_id=job.job_id,
repository=repository,
target_branch=target_branch,
branch_mode=branch_mode,
expected_branch_oid=eff,
generation=generation,
expires_at=time.time() + 3600,
fencing=1,
)
class _TempDirTestCase(unittest.TestCase):
def setUp(self):
self.tmp = Path(tempfile.mkdtemp(prefix="aios-gov-gw-entry-"))
def tearDown(self):
gc.collect()
shutil.rmtree(self.tmp, ignore_errors=True)
def _automation(self):
store = ManifestStore(self.tmp / "manifest.db")
ledger = ActionLedger(self.tmp / "actions.db")
return GovernedWriterAutomation(
manifest_store=store, action_ledger=ledger,
)
def _frozen_manifest_id(self, store: ManifestStore) -> str:
barrier = ConsistencyBarrier(["W1"])
barrier.submit(_gw_observation())
return store.freeze(barrier.freeze())
def _gw_observation(worker: str = "W1", **overrides) -> DiscoveryObservation:
data = dict(
worker=worker,
runtime_fingerprint=RUNTIME,
process_start_identity=PROCESS_START,
activation_digest="activation-gw",
epoch_id="epoch-gw",
policy_digest="policy-gw",
plan_provider_root="/plans/gw",
task_id=TASK,
grant_id="grant-gw",
facts={},
evidence_refs=(),
observed_at=100.0,
)
data.update(overrides)
return DiscoveryObservation.build(**data)
# ---------------------------------------------------------------------------
# T2:生产入口 acquire_verified() —— 必须经正式 Owner 授权 authority 验证
# ---------------------------------------------------------------------------
class T2OwnerAuthorizationEntryTests(_TempDirTestCase):
def test_t2_production_entry_requires_verified_owner_authorization(self):
"""未配置 OwnerAuthorizationAuthority → fail-closed,本地 policy 不构成 proof。"""
repo = make_policy()
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
adapters_no_authority = GovernedWriterAdapters(
signer=_Signer(), execution=_ExecutionPlane(TaskState(TASK, "QUEUED", 0)),
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(), git=_Git(),
writer_lease=_WriterLease(), owner_authority=None,
)
with self.assertRaises(AuthorizationDenied):
automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=None, adapters=adapters_no_authority,
now=time.time(),
)
# 即使本地 policy 有效,也必须经 authority 验证并返回 receipt,否则拒绝
adapters_ref_missing = GovernedWriterAdapters(
signer=_Signer(), execution=_ExecutionPlane(TaskState(TASK, "QUEUED", 0)),
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(), git=_Git(),
writer_lease=_WriterLease(), owner_authority=_VerifyingAuthority(repo),
)
with self.assertRaises(AuthorizationDenied):
automation.acquire_verified(
request=request, authorization_ref="unknown-ref",
readiness=make_readiness(), epoch=None, adapters=adapters_ref_missing,
now=time.time(),
)
def test_t2_verified_owner_authorization_drives_full_writer_chain(self):
"""Owner 授权一次验证通过后,Agent 自动走完 claim→…→WriterLease。"""
repo = make_policy()
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
signer = _Signer()
authority = _VerifyingAuthority(repo)
branch = _Git() # branch 不存在 → CREATE_NEW,expected = base_oid
adapters = GovernedWriterAdapters(
signer=signer, execution=_ExecutionPlane(TaskState(TASK, "QUEUED", 0)),
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(), git=branch,
writer_lease=_WriterLease(), owner_authority=authority,
)
session: GovernedWriterSession = automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=None, adapters=adapters,
now=time.time(), minimum_remaining_ttl=1800.0,
)
self.assertEqual(session.state, GovernedWriterState.WRITER_LEASED)
self.assertEqual(session.branch_mode, "CREATE_NEW")
self.assertEqual(session.expected_branch_oid, BASE_OID)
self.assertEqual(session.authorization_id, "azn-gw-verify")
self.assertEqual(session.task_id, TASK)
self.assertTrue(session.epoch_id.startswith("epoch-jit-"))
self.assertEqual(len(authority.calls), 1)
# Epoch 由 trusted signer JIT 重签(scope 未变,Agent 自主)
self.assertEqual(len(signer.calls), 1)
self.assertEqual(signer.calls[0]["scope"], request.scope_fingerprint)
# ---------------------------------------------------------------------------
# T3:协议路径语义 —— legacy/global writes 与 governed writer 解耦
# ---------------------------------------------------------------------------
class T3GovernedWriteReadinessSemanticsTests(_TempDirTestCase):
def test_t3_legacy_global_false_is_invariant_not_blocker(self):
"""legacy_global_writes_enabled=false 不阻断 governed writer,反而是不变式。"""
readiness = make_readiness()
self.assertTrue(readiness.governed_write_path_open)
self.assertEqual(readiness.blockers, ())
def test_t3_ambiguous_writes_enabled_is_ignored(self):
"""from_runtime_status 忽略 ambiguous writes_enabled / AIOS_GITOPS_WRITES_ENABLED。"""
r = GovernedWriteReadiness.from_runtime_status(
{
"AIOS_GITOPS_WRITES_ENABLED": False,
"writes_enabled": False,
"legacy_global_writes_enabled": False,
"task_principal_writes_enabled": True,
"task_principal_readiness": {"ready": True},
"blockers": [],
}
)
self.assertTrue(r.governed_write_path_open)
self.assertFalse(r.legacy_global_writes_enabled)
self.assertEqual(r.blockers, ())
def test_t3_legacy_global_true_blocks(self):
r = make_readiness(legacy_global_writes_enabled=True)
self.assertFalse(r.governed_write_path_open)
self.assertIn("LEGACY_GLOBAL_WRITES_MUST_REMAIN_DISABLED", r.blockers)
class T3EpochAndTaskDecisionTests(_TempDirTestCase):
def _adapters(self, *, task: TaskState | None = None, with_recovery=None):
authority = _VerifyingAuthority(make_policy())
return authority, GovernedWriterAdapters(
signer=_Signer(),
execution=_ExecutionPlane(task or TaskState(TASK, "QUEUED", 0)),
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(), git=_Git(),
writer_lease=_WriterLease(), owner_authority=authority,
)
def test_t3_changed_epoch_scope_requires_owner(self):
"""同 scope 过期 epoch 才可 JIT;scope 改变不可静默继承旧授权。"""
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
authority, adapters = self._adapters()
stale_scope = EpochStatus(
epoch_id="epoch-old",
scope_fingerprint="sha256:old-different-scope",
expires_at=time.time() + 7200,
policy_digest="p",
)
with self.assertRaises(OwnerActionRequired):
automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=stale_scope, adapters=adapters,
now=time.time(),
)
def test_t3_expired_epoch_same_scope_is_auto_reissued(self):
"""同 scope epoch 过期 → 经 trusted signer 自动 JIT 重签(非 Owner 边界)。"""
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
signer = _Signer()
authority = _VerifyingAuthority(make_policy())
adapters = GovernedWriterAdapters(
signer=signer, execution=_ExecutionPlane(TaskState(TASK, "QUEUED", 0)),
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(), git=_Git(),
writer_lease=_WriterLease(), owner_authority=authority,
)
expired = EpochStatus(
epoch_id="epoch-expired",
scope_fingerprint=request.scope_fingerprint,
expires_at=time.time() - 100,
policy_digest="p",
)
session = automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=expired, adapters=adapters,
now=time.time(), minimum_remaining_ttl=1800.0,
)
self.assertEqual(session.state, GovernedWriterState.WRITER_LEASED)
self.assertNotEqual(session.epoch_id, "epoch-expired")
self.assertEqual(len(signer.calls), 1)
def test_t3_expired_task_without_signed_recovery_requires_owner(self):
"""EXPIRED terminal task 无预签 recovery → OWNER_BOUNDARY(区别于 epoch)。"""
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
authority, adapters = self._adapters(
task=TaskState(TASK, "EXPIRED", 2),
)
with self.assertRaises(OwnerActionRequired):
automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=None, adapters=adapters,
now=time.time(), recovery_authorization=None,
)
def test_t3_expired_task_with_preapproved_recovery_is_automatic(self):
"""EXPIRED + 预签 RecoveryAuthorization → 自动恢复为 QUEUED 并继续。"""
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
authority = _VerifyingAuthority(make_policy())
plane = _ExecutionPlane(TaskState(TASK, "EXPIRED", 2))
adapters = GovernedWriterAdapters(
signer=_Signer(), execution=plane,
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(), git=_Git(),
writer_lease=_WriterLease(), owner_authority=authority,
)
recovery = RecoveryAuthorization(
authorization_ref="recovery-azn-1",
task_id=TASK,
action="TERMINAL_RECOVERY",
expires_at=time.time() + 3600,
)
session = automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=None, adapters=adapters,
now=time.time(), recovery_authorization=recovery,
)
self.assertEqual(session.state, GovernedWriterState.WRITER_LEASED)
self.assertTrue(plane.recovery_applied)
class T3BranchCasAndGuardTests(_TempDirTestCase):
def test_t3_existing_branch_uses_live_head_for_cas(self):
"""branch 已存在 → CONTINUE_EXISTING,live HEAD 决定 expected_branch_oid。"""
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
live_head = "abcd1234livehead"
authority = _VerifyingAuthority(make_policy())
writer = _WriterLease()
adapters = GovernedWriterAdapters(
signer=_Signer(), execution=_ExecutionPlane(TaskState(TASK, "QUEUED", 0)),
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(),
git=_Git(exists=True, head=live_head), writer_lease=writer,
owner_authority=authority,
)
session = automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=None, adapters=adapters,
now=time.time(),
)
self.assertEqual(session.branch_mode, "CONTINUE_EXISTING")
self.assertEqual(session.expected_branch_oid, live_head)
self.assertEqual(writer.calls[0]["expected"], live_head)
def test_t3_principal_capability_shortfall_blocks_writer(self):
"""TaskPrincipal 能力不足 → 阻断 WriterLease。"""
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
request = make_request(manifest_id=manifest_id)
authority = _VerifyingAuthority(make_policy())
adapters = GovernedWriterAdapters(
signer=_Signer(), execution=_ExecutionPlane(TaskState(TASK, "QUEUED", 0)),
principal_issuer=_PrincipalIssuer(shortfall=True), jobs=_Jobs(), git=_Git(),
writer_lease=_WriterLease(), owner_authority=authority,
)
with self.assertRaises(GovernedWriterUnavailable):
automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=None, adapters=adapters,
now=time.time(),
)
def test_t3_stale_manifest_blocks_before_any_side_effect(self):
"""stale runtime manifest → 在任何副作用前阻断。"""
automation = self._automation()
store = automation.manifest_store
manifest_id = self._frozen_manifest_id(store)
# 请求用错误 runtime_fingerprint → assert_current 抛 StaleManifest
request = make_request(manifest_id=manifest_id, runtime_fingerprint="stale-runtime")
from app.task_plane.governance_control_kernel import StaleManifest
authority = _VerifyingAuthority(make_policy())
signer = _Signer()
plane = _ExecutionPlane(TaskState(TASK, "QUEUED", 0))
adapters = GovernedWriterAdapters(
signer=signer, execution=plane,
principal_issuer=_PrincipalIssuer(), jobs=_Jobs(), git=_Git(),
writer_lease=_WriterLease(), owner_authority=authority,
)
with self.assertRaises(StaleManifest):
automation.acquire_verified(
request=request, authorization_ref="azn-gw-verify",
readiness=make_readiness(), epoch=None, adapters=adapters,
now=time.time(),
)
# 既无 claim、无心跳、无 epoch 副作用
self.assertEqual(signer.calls, [])
self.assertEqual(plane.claims, [])
self.assertEqual(plane.heartbeats, [])
if __name__ == "__main__":
unittest.main()
+5 -9
View File
@@ -13,19 +13,15 @@ import pytest
# Exercise the overlay exactly as deployment will see it: changed modules from
# this artifact plus unchanged dependencies from the checked-out repository.
_APP_DIR = Path(__file__).resolve().parents[2] / "active-runtime" / "app"
# Production resolves ``app`` via the checkout-root symlink ``app -> active-runtime/app``
# (and tests/conftest.py places active-runtime/app on sys.path). Keep the overlay
# intent (changed modules + unchanged dependencies from the checked-out repo) by
# pointing the ``app``/``app.task_plane`` namespace at the REAL active-runtime/app,
# never at non-existent source//repo/ dirs (which poisoned sys.modules and broke
# collection of the other app.*-qualified tests in the full suite).
_WORK = Path(__file__).resolve().parents[4]
_OVERLAY_APP = Path(__file__).resolve().parents[2] / "source" / "active-runtime" / "app"
_REPO_APP = _WORK / "repo" / "active-runtime" / "app"
_app = types.ModuleType("app")
_app.__path__ = [str(_APP_DIR)]
_app.__path__ = [str(_OVERLAY_APP), str(_REPO_APP)]
_app.__package__ = "app"
sys.modules["app"] = _app
_task_plane = types.ModuleType("app.task_plane")
_task_plane.__path__ = [str(_APP_DIR / "task_plane")]
_task_plane.__path__ = [str(_OVERLAY_APP / "task_plane"), str(_REPO_APP / "task_plane")]
_task_plane.__package__ = "app.task_plane"
sys.modules["app.task_plane"] = _task_plane
@@ -21,7 +21,7 @@ TASK="legacy-task"
def _install_stubs():
app=types.ModuleType("app"); app.__path__=[str(ROOT / "active-runtime" / "app")]; sys.modules["app"]=app
app=types.ModuleType("app"); app.__path__=[]; sys.modules["app"]=app
identity=types.ModuleType("app.aios_gitops_job_identity")
identity.legacy_task_storage_key=lambda value:value
identity.task_storage_key=lambda value:value
@@ -34,7 +34,7 @@ def _install_stubs():
auth.SignerRegistry=type("SignerRegistry",(),{})
sys.modules[auth.__name__]=auth
task_plane=types.ModuleType("app.task_plane"); task_plane.__path__=[str(ROOT / "active-runtime" / "app" / "task_plane")]
task_plane=types.ModuleType("app.task_plane"); task_plane.__path__=[]
sys.modules["app.task_plane"]=task_plane
la=types.ModuleType("app.task_plane.legacy_attachment")
la.ATTACH_EVIDENCE_SCHEMA="evidence"
@@ -46,23 +46,7 @@ def _install_stubs():
sys.modules[la.__name__]=la
# These stubs are only needed at import time of the runtime module. Pytest runs
# ``importlib`` mode in a single interpreter, so we must not permanently replace
# the real ``app.*`` namespace in ``sys.modules`` -- doing so breaks every later
# ``app.*`` import in the same suite (e.g. app.task_plane.schedulability). Snapshot
# and restore the affected entries around the module load.
_STUB_NAMES = [
"app",
"app.aios_gitops_job_identity",
"app.aios_single_host_authority",
"app.task_plane",
"app.task_plane.legacy_attachment",
]
def _load():
saved={name: sys.modules.get(name) for name in _STUB_NAMES}
try:
_install_stubs()
name="app.aios_legacy_attachment_runtime"
spec=importlib.util.spec_from_file_location(name,MODULE_PATH)
@@ -70,12 +54,6 @@ def _load():
sys.modules[name]=mod
spec.loader.exec_module(mod)
return mod
finally:
for n in _STUB_NAMES:
if saved[n] is None:
sys.modules.pop(n, None)
else:
sys.modules[n]=saved[n]
M=_load()
@@ -1,354 +0,0 @@
import hashlib
import tempfile
import time
import unittest
from pathlib import Path
from app.task_plane.governed_writer_automation import (
GovernedWriterRequest,
OwnerAuthorizationPolicy,
)
from app.task_plane.live_governed_writer_adapters import (
HttpResult,
LiveAgentTaskClient,
LiveBridgeClient,
LiveGovernedWriterExecutor,
LiveProtocolConfig,
LiveProtocolConflict,
LiveProtocolError,
LiveTaskPrincipalClient,
SecretHandleStore,
)
class RecordingTransport:
def __init__(self):
self.calls=[]
self.task_reads=0
def request(self,method,url,*,body=None,headers=None,timeout=20.0):
self.calls.append({
"method":method,"url":url,"body":body or {},
"headers":headers or {},"timeout":timeout,
})
path=url.split("127.0.0.1")[-1]
if method=="GET" and path.endswith(":18892/v2/runtime"):
return HttpResult(200,{
"legacy_global_writes_enabled":False,
"task_principal_writes_enabled":True,
"task_principal_readiness":{"ready":True},
"governed_write_blockers":[],
})
if method=="POST" and path.endswith(":18940/v1/agents/register"):
return HttpResult(201,{"agent_token":"agent-token-secret-12345"})
if method=="GET" and ":18940/v1/tasks/" in path:
self.task_reads+=1
status="QUEUED" if self.task_reads==1 else "RUNNING"
return HttpResult(200,{
"task_id":"task-live-1",
"status":status,
"revision":4+self.task_reads,
"deadline":time.time()+1200,
"lease_expires_at":time.time()+900,
})
if method=="POST" and path.endswith("/claim"):
return HttpResult(200,{
"status":"CLAIMED",
"claim_id":"claim-live-1",
"lease_id":"lease-live-1",
"lease_token":"lease-token-secret-12345",
"fencing_generation":9,
"receipt_revision":7,
"lease_expires_at":time.time()+900,
})
if method=="POST" and path.endswith("/heartbeat"):
return HttpResult(200,{"status":"RUNNING"})
if method=="POST" and path.endswith(":18946/v1/task-principal/assertions"):
operation=(body or {}).get("operation")
return HttpResult(201,{"assertion":f"assertion-for-{operation}"})
if method=="POST" and path.endswith(":18892/v2/writer-lease"):
return HttpResult(201,{
"schema":"aios.task-writer-lease.v2",
"task_id":"task-live-1",
"lease_id":"writer-lease-live-1",
"expected_branch_oid":(body or {}).get("expected_branch_oid"),
"expires_at":time.time()+900,
})
if method=="POST" and path.endswith(":18892/v2/commit-files"):
return HttpResult(201,{
"status":"COMMITTED",
"task_id":"task-live-1",
"commit_oid":"new-head-oid",
})
raise AssertionError(f"unexpected call: {method} {url} {body}")
class FakeGit:
def __init__(self,head=None):
self.head=head
self.sha={}
self.reads=0
def branch_head(self,branch):
self.reads+=1
return self.head
def file_sha256(self,oid,path):
return self.sha[path]
def policy():
return OwnerAuthorizationPolicy(
authorization_id="azn-owner-gw-v1",
subject="aios-autonomous-admin",
repository="aios/aios",
allowed_branch_prefixes=("agent/autonomy-",),
allowed_capabilities=frozenset({
"code.write","governed.write.branch","governed.write.file",
"test.execute","writer.lease","task.principal.issue",
}),
risk_ceiling="medium",
trust_domain="aios.owner",
revocation_epoch=1,
not_before=0.0,
expires_at=None,
revoked=False,
)
def request():
return GovernedWriterRequest(
subject="aios-autonomous-admin",
task_id="task-live-1",
repository="aios/aios",
target_branch="agent/autonomy-live-protocol",
risk="medium",
generation=1,
base_oid="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
required_capabilities=frozenset({
"code.write","governed.write.branch","governed.write.file",
"test.execute","writer.lease","task.principal.issue",
}),
file_allowlist=("active-runtime/app/task_plane/x.py",),
plan_digest="sha256:plan-live",
manifest_id="sha256:manifest-live",
runtime_fingerprint="runtime-live",
process_start_identity="pid:live:start:1",
)
class LiveAgentTaskContractTests(unittest.TestCase):
def setUp(self):
self.tmp=tempfile.TemporaryDirectory()
self.control=Path(self.tmp.name)/"control.token"
self.control.write_text("control-token-secret-12345",encoding="utf-8")
self.transport=RecordingTransport()
self.secrets=SecretHandleStore()
self.cfg=LiveProtocolConfig(control_token_file=str(self.control))
def tearDown(self):
self.tmp.cleanup()
def test_claim_and_heartbeat_use_live_contract(self):
client=LiveAgentTaskClient(
self.cfg,transport=self.transport,secrets_store=self.secrets
)
task=client.read_task("task-live-1")
self.assertEqual(task.status,"QUEUED")
agent_id,agent_handle=client.register_agent(
task_id="task-live-1",
capabilities=["governed.write.branch"],
route="test",
)
claim=client.claim(
task_id="task-live-1",agent_token_handle=agent_handle
)
self.assertEqual(claim.claim_ref,"claim-live-1")
self.assertEqual(claim.fencing_generation,9)
running=client.heartbeat(
claim=claim,agent_token_handle=agent_handle,phase="test"
)
self.assertEqual(running.status,"RUNNING")
claim_call=[c for c in self.transport.calls if c["url"].endswith("/claim")][0]
self.assertEqual(claim_call["body"],{"lease_seconds":900})
self.assertTrue(claim_call["headers"]["Authorization"].startswith("Bearer "))
hb=[c for c in self.transport.calls if c["url"].endswith("/heartbeat")][0]
self.assertEqual(hb["body"]["lease_token"],"lease-token-secret-12345")
def test_principal_assertion_is_operation_scoped(self):
self.secrets.put("dummy","ignore")
handle=self.secrets.put("task-lease","lease-token-secret-12345")
principal=LiveTaskPrincipalClient(
self.cfg,transport=self.transport,secrets_store=self.secrets
)
assertion=principal.issue_assertion(
task_id="task-live-1",
lease_handle=handle,
operation="acquireWriterLease",
bridge_request={"schema":"aios.task-writer-lease.v2"},
request_id="req-1",
)
self.assertEqual(assertion,"assertion-for-acquireWriterLease")
call=self.transport.calls[-1]
self.assertEqual(call["body"]["schema"],"aios.task-principal-issue.v1")
self.assertEqual(call["body"]["operation"],"acquireWriterLease")
self.assertEqual(
call["headers"]["Authorization"],
"TaskLease lease-token-secret-12345",
)
class LiveBridgeContinuationTests(unittest.TestCase):
def setUp(self):
self.transport=RecordingTransport()
self.secrets=SecretHandleStore()
self.cfg=LiveProtocolConfig()
self.lease_handle=self.secrets.put(
"task-lease","lease-token-secret-12345"
)
self.principal=LiveTaskPrincipalClient(
self.cfg,transport=self.transport,secrets_store=self.secrets
)
self.bridge=LiveBridgeClient(
self.cfg,transport=self.transport,principal_client=self.principal
)
def test_writerlease_and_commit_use_same_continuation_key(self):
wl=self.bridge.acquire_writer_lease(
task_id="task-live-1",
job_id="job-live-1",
lease_handle=self.lease_handle,
repository="aios/aios",
target_branch="agent/autonomy-live-protocol",
generation=1,
base_oid="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
expected_branch_oid="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
branch_mode="CREATE_NEW",
continuation_key="gw-cont-fixed",
)
from app.task_plane.live_governed_writer_adapters import LiveWriterSession
session=LiveWriterSession(
authorization_id="azn-owner-gw-v1",
task_id="task-live-1",
agent_id="agent-live",
claim_ref="claim-live-1",
job_id="job-live-1",
branch_mode="CREATE_NEW",
expected_branch_oid=wl.expected_branch_oid,
writer_lease_id=wl.writer_lease_id,
continuation_key=wl.continuation_key,
principal_issue_schema=self.cfg.issue_schema,
lease_handle=self.lease_handle,
)
self.bridge.commit_files(
session=session,
repository="aios/aios",
target_branch="agent/autonomy-live-protocol",
generation=1,
files={"x.py":b"x=1\n"},
message="test",
)
wl_call=[c for c in self.transport.calls if c["url"].endswith("/v2/writer-lease")][0]
cm_call=[c for c in self.transport.calls if c["url"].endswith("/v2/commit-files")][0]
self.assertEqual(wl_call["body"]["idempotency_key"],"gw-cont-fixed")
self.assertEqual(cm_call["body"]["idempotency_key"],"gw-cont-fixed")
issue_ops=[
c["body"]["operation"] for c in self.transport.calls
if c["url"].endswith("/v1/task-principal/assertions")
]
self.assertEqual(issue_ops,["acquireWriterLease","commitFilesGoverned"])
class LiveExecutorTests(unittest.TestCase):
def setUp(self):
self.tmp=tempfile.TemporaryDirectory()
self.control=Path(self.tmp.name)/"control.token"
self.control.write_text("control-token-secret-12345",encoding="utf-8")
self.transport=RecordingTransport()
self.cfg=LiveProtocolConfig(control_token_file=str(self.control))
def tearDown(self):
self.tmp.cleanup()
def test_full_post_admission_chain_create_new(self):
ex=LiveGovernedWriterExecutor(
config=self.cfg,
owner_policy=policy(),
authorization_ref="azn-owner-gw-v1",
transport=self.transport,
)
ex.git=FakeGit(head=None)
session=ex.acquire(request=request(),job_id="job-live-1")
self.assertEqual(session.branch_mode,"CREATE_NEW")
self.assertEqual(
session.expected_branch_oid,
"5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
)
self.assertEqual(session.writer_lease_id,"writer-lease-live-1")
def test_nonqueued_task_fails_before_agent_registration(self):
class ExpiredTransport(RecordingTransport):
def request(self,method,url,*,body=None,headers=None,timeout=20.0):
if method=="GET" and ":18940/v1/tasks/" in url:
self.calls.append({"method":method,"url":url,"body":body or {},"headers":headers or {}})
return HttpResult(200,{
"task_id":"task-live-1","status":"EXPIRED",
"revision":2,"deadline":time.time()-10,
})
return super().request(method,url,body=body,headers=headers,timeout=timeout)
tr=ExpiredTransport()
ex=LiveGovernedWriterExecutor(
config=self.cfg,owner_policy=policy(),
authorization_ref="azn-owner-gw-v1",transport=tr,
)
ex.git=FakeGit()
with self.assertRaises(LiveProtocolConflict):
ex.acquire(request=request(),job_id="job-live-1")
self.assertFalse(any(c["url"].endswith("/v1/agents/register") for c in tr.calls))
def test_commit_byte_gate_and_readback(self):
ex=LiveGovernedWriterExecutor(
config=self.cfg,owner_policy=policy(),
authorization_ref="azn-owner-gw-v1",transport=self.transport,
)
fg=FakeGit(head=None)
ex.git=fg
session=ex.acquire(request=request(),job_id="job-live-1")
data=b"x=1\n"
sha=hashlib.sha256(data).hexdigest()
fg.head="0123456789abcdef0123456789abcdef01234567"
fg.sha={"active-runtime/app/task_plane/x.py":sha}
result=ex.commit_and_readback(
session=session,
request=request(),
files={"active-runtime/app/task_plane/x.py":data},
expected_sha256={"active-runtime/app/task_plane/x.py":sha},
message="test commit",
)
self.assertEqual(
result["branch_head"],
"0123456789abcdef0123456789abcdef01234567",
)
def test_commit_byte_gate_rejects_mismatch_before_bridge(self):
ex=LiveGovernedWriterExecutor(
config=self.cfg,owner_policy=policy(),
authorization_ref="azn-owner-gw-v1",transport=self.transport,
)
ex.git=FakeGit(head=None)
session=ex.acquire(request=request(),job_id="job-live-1")
count_before=len(self.transport.calls)
with self.assertRaises(LiveProtocolConflict):
ex.commit_and_readback(
session=session,
request=request(),
files={"active-runtime/app/task_plane/x.py":b"bad"},
expected_sha256={
"active-runtime/app/task_plane/x.py":"0"*64
},
message="test",
)
commit_calls=[
c for c in self.transport.calls[count_before:]
if c["url"].endswith("/v2/commit-files")
]
self.assertEqual(commit_calls,[])
if __name__=="__main__":
unittest.main()
@@ -147,7 +147,7 @@ def _fixture(tmp_path):
"goal_slug": "terminal-proof-test",
"original_acceptance": {
"schema": "acceptance.v1",
"conditions": [{"key": "C01", "purpose": "governed-candidate-readback", "required_evidence": ["acceptance-proof"]}, {"key": "C06", "purpose": "governed-candidate-readback", "required_evidence": ["acceptance-proof"]}],
"conditions": [{"key": "C01"}, {"key": "C06"}],
},
},
"mission-test",
+1 -2
View File
@@ -13,9 +13,8 @@ fixed clients must satisfy.
"""
import re
import pytest
from pathlib import Path
REPO = str(Path(__file__).resolve().parents[1]) # repo root (was hardcoded dev path)
REPO = "C:/Users/ImRuY/aios-main-clone"
ADMIN_HTML = f"{REPO}/active-runtime/app/static/admin-ai-evolution.html"
GITOPS_HTML = f"{REPO}/overlays/user-control-center/portal/gitops-dashboard/index.html"
+587
View File
@@ -0,0 +1,587 @@
"""Negative / safety tests for the protected-main status pipeline.
Runs fully locally with a fake broker and temp evidence store — no real Gitea,
gitleaks, fcntl, or systemd. Covers architecture §3 (authority req §13) safety
semantics: no forgery, no green-reporter, UNKNOWN != success, N/A contract
binding, stale SHA, idempotency, RECONCILE, capability minimization, non-plaintext
credential, and merge-only-on-genuine-pass.
"""
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
import sys
from pathlib import Path as _P
ROOT = _P(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from aios_gitea_machine_broker import ( # noqa: E402
CampaignCapabilityReceipt,
GiteaMachineBroker,
MachineCredentialProjection,
MachineBrokerError,
)
from status_evidence_contract import ( # noqa: E402
REQUIRED_CONTEXTS,
StatusEvidence,
maps_to_success,
)
from status_publisher import ( # noqa: E402
AppendOnlyEvidenceStore,
StatusPublisher,
StatusPublisherError,
gitea_state_for_result,
)
from status_orchestrator import StatusOrchestrator # noqa: E402
from status_producers import ( # noqa: E402
PRODUCER_REVISION,
ProducerContext,
ProducerOutput,
set_command_runner,
)
def _sha(value: int) -> str:
return f"{value:040d}"
HEAD_A = _sha(1)
HEAD_B = _sha(2)
class FakeBroker:
def __init__(self, *, fail_post=False, statuses=None, raise_read=False):
self.posts = []
self.fail_post = fail_post
self._statuses = statuses or []
self.raise_read = raise_read
self.read_calls = 0
def status_for_sha(self, sha, state, description, context):
self.posts.append({"sha": sha, "state": state, "context": context})
if self.fail_post:
raise RuntimeError("post_unknown")
return {"ok": True}
def read_statuses(self, sha, context=None):
self.read_calls += 1
if self.raise_read:
raise RuntimeError("read_unknown")
return [s for s in self._statuses if context is None or s.get("context") == context]
def _store():
return AppendOnlyEvidenceStore(Path(tempfile.mkdtemp()))
def _publisher(broker, store, caps=None):
return StatusPublisher(broker, store, receipt_capabilities=caps)
def _evidence(context, result, commit_sha=HEAD_A, na_ref=None):
return StatusEvidence(
repository="aios/aios",
commit_sha=commit_sha,
context=context,
result=result,
producer_identity=f"aios.{context}",
producer_revision=PRODUCER_REVISION,
na_contract_ref=na_ref,
)
class TestStateProjection(unittest.TestCase):
def test_applicable_pass_is_success(self):
self.assertEqual(gitea_state_for_result("APPLICABLE_PASS", has_na_contract_ref=False), "success")
def test_formally_na_is_success_only_with_contract(self):
self.assertEqual(gitea_state_for_result("FORMALLY_NOT_APPLICABLE", has_na_contract_ref=True), "success")
with self.assertRaises(Exception):
gitea_state_for_result("FORMALLY_NOT_APPLICABLE", has_na_contract_ref=False)
def test_unknown_never_success(self):
self.assertEqual(gitea_state_for_result("UNKNOWN", has_na_contract_ref=False), "error")
self.assertNotEqual(gitea_state_for_result("UNKNOWN", has_na_contract_ref=False), "success")
def test_fail_never_success(self):
self.assertEqual(gitea_state_for_result("FAIL", has_na_contract_ref=False), "failure")
self.assertNotEqual(gitea_state_for_result("FAIL", has_na_contract_ref=False), "success")
def test_maps_to_success_redline(self):
self.assertTrue(maps_to_success("APPLICABLE_PASS"))
self.assertFalse(maps_to_success("UNKNOWN"))
self.assertFalse(maps_to_success("FAIL"))
def test_no_universal_green_reporter(self):
# gitea_state_for_result never yields success for non-pass results; a
# "publish success for all" loop cannot be expressed via this projection.
for result in ("UNKNOWN", "FAIL"):
self.assertNotEqual(gitea_state_for_result(result, has_na_contract_ref=False), "success")
class TestPublisher(unittest.TestCase):
def test_publisher_never_posts_success_for_unknown(self):
broker = FakeBroker()
pub = _publisher(broker, _store())
ev = _evidence("py-compile", "UNKNOWN")
outc = pub.publish(ev)
self.assertEqual(outc.action, "PUBLISHED")
self.assertEqual(outc.gitea_state, "error")
self.assertTrue(all(p["state"] == "error" for p in broker.posts))
def test_fail_posts_failure(self):
broker = FakeBroker()
pub = _publisher(broker, _store())
outc = pub.publish(_evidence("py-compile", "FAIL"))
self.assertEqual(outc.gitea_state, "failure")
self.assertEqual(broker.posts[0]["state"], "failure")
def test_na_without_contract_forbidden(self):
broker = FakeBroker()
pub = _publisher(broker, _store())
# build evidence directly: NA without contract raises at finalize
ev = _evidence("campaign-approval-gate", "FORMALLY_NOT_APPLICABLE", na_ref=None)
with self.assertRaises(Exception):
ev.finalize()
def test_applicable_pass_posts_success(self):
broker = FakeBroker()
pub = _publisher(broker, _store())
outc = pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertEqual(outc.gitea_state, "success")
def test_idempotent_suppress_noop(self):
broker = FakeBroker()
store = _store()
pub = _publisher(broker, store)
ev = _evidence("py-compile", "APPLICABLE_PASS")
first = pub.publish(ev)
second = pub.publish(ev)
self.assertEqual(first.action, "PUBLISHED")
self.assertEqual(second.action, "SUPPRESS_NOOP")
self.assertEqual(len(broker.posts), 1)
def test_unknown_post_reconciles_to_expected(self):
broker = FakeBroker(
fail_post=True,
statuses=[{"context": "py-compile", "state": "success"}],
)
pub = _publisher(broker, _store())
outc = pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertEqual(outc.action, "RECONCILED")
self.assertEqual(outc.gitea_state, "success")
def test_unknown_post_and_unknown_read_is_unknown(self):
broker = FakeBroker(fail_post=True, raise_read=True)
pub = _publisher(broker, _store())
outc = pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertEqual(outc.action, "UNKNOWN")
self.assertNotEqual(outc.gitea_state, "success")
def test_stale_sha_not_copied(self):
broker = FakeBroker()
pub = _publisher(broker, _store())
pub.publish(_evidence("py-compile", "APPLICABLE_PASS", commit_sha=HEAD_A))
pub.publish(_evidence("py-compile", "APPLICABLE_PASS", commit_sha=HEAD_B))
self.assertEqual([p["sha"] for p in broker.posts], [HEAD_A, HEAD_B])
def test_capability_minimization_forbidden(self):
broker = FakeBroker()
pub = _publisher(
broker,
_store(),
caps={"capabilities": {"status_publish", "direct_main_push"}, "denied": set()},
)
with self.assertRaises(Exception) as ctx:
pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertIn("CAPABILITY_MINIMIZATION_VIOLATED", str(ctx.exception))
def test_capability_status_publish_required(self):
broker = FakeBroker()
pub = _publisher(broker, _store(), caps={"capabilities": {"pr_create_update"}, "denied": set()})
with self.assertRaises(Exception) as ctx:
pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertIn("CAPABILITY_STATUS_PUBLISH_MISSING", str(ctx.exception))
class TestCredentialNonPlaintext(unittest.TestCase):
def test_plaintext_scheme_rejected(self):
with self.assertRaises(MachineBrokerError):
MachineCredentialProjection.from_mapping(
{"scheme": "plain", "name": "gitea-machine-token", "account": "aios-autonomy-agent"}
)
def test_owner_identity_rejected(self):
with self.assertRaises(MachineBrokerError):
MachineCredentialProjection.from_mapping(
{"scheme": "systemd-credential", "name": "gitea-machine-token", "account": "aiman"}
)
class TestBrokerPermissionDenied(unittest.TestCase):
def _broker_without_status(self):
transport = FakeBroker()
credential = MachineCredentialProjection.from_mapping(
{"scheme": "systemd-credential", "name": "gitea-machine-token", "account": "aios-autonomy-agent"}
)
receipt = CampaignCapabilityReceipt(
"c1", "t1", "aios/aios", "agent/dev", _sha(3),
frozenset({"pr_create_update"}), frozenset(),
)
return GiteaMachineBroker(transport, credential, receipt)
def test_status_for_sha_denied_without_capability(self):
broker = self._broker_without_status()
with self.assertRaises(MachineBrokerError) as ctx:
broker.status_for_sha(HEAD_A, "success", "d", "py-compile")
self.assertEqual(str(ctx.exception), "CAPABILITY_DENIED")
def _pass_producer(context):
def run(ctx):
return ProducerOutput(context, "APPLICABLE_PASS", f"aios.{context}", PRODUCER_REVISION, {})
return run
def _unknown_producer(context):
def run(ctx):
return ProducerOutput(context, "UNKNOWN", f"aios.{context}", PRODUCER_REVISION, {})
return run
def _fail_producer(context):
def run(ctx):
return ProducerOutput(context, "FAIL", f"aios.{context}", PRODUCER_REVISION, {})
return run
class TestOrchestratorMerge(unittest.TestCase):
def _make(self, producer_fn):
producers = {c: producer_fn(c) for c in REQUIRED_CONTEXTS}
broker = FakeBroker()
orch = StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
)
return orch, broker
def test_merge_only_when_all_genuine_pass(self):
orch, _ = self._make(_pass_producer)
result = orch.run_for_head(HEAD_A)
self.assertTrue(result.merge_eligible)
self.assertEqual(len(result.context_outcomes), 9)
def test_unknown_blocks_merge(self):
producers = {c: _pass_producer(c) for c in REQUIRED_CONTEXTS}
producers["py-compile"] = _unknown_producer("py-compile")
broker = FakeBroker()
orch = StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
)
result = orch.run_for_head(HEAD_A)
self.assertFalse(result.merge_eligible)
def test_head_change_invalidates_old(self):
orch, broker = self._make(_pass_producer)
orch.run_for_head(HEAD_A)
broker.posts.clear()
orch.run_for_head(HEAD_B)
# second run must only touch the new head
self.assertTrue(broker.posts)
self.assertTrue(all(p["sha"] == HEAD_B for p in broker.posts))
def test_intended_success_but_post_unknown_blocks_merge(self):
# Producer verdicts are all PASS, but the publisher cannot confirm any
# POST and reconciliation is unknown -> merge must be blocked (never
# treat an unconfirmed success as green).
producers = {c: _pass_producer(c) for c in REQUIRED_CONTEXTS}
broker = FakeBroker(fail_post=True, raise_read=True)
orch = StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
)
result = orch.run_for_head(HEAD_A)
self.assertFalse(result.merge_eligible)
# every context ended UNKNOWN (not confirmed published), so no green is credited
self.assertTrue(
all(
o.publish.action == "UNKNOWN"
for o in result.context_outcomes.values()
)
)
class TestExistingStateDiffersNotGreen(unittest.TestCase):
"""Red-line: a conflicting leftover status must never credit a FAIL/UNKNOWN
verdict as green, and an unconfirmed reconcile must never count."""
def test_reconcile_conflict_marks_unconfirmed(self):
broker = FakeBroker(
fail_post=True, statuses=[{"context": "py-compile", "state": "success"}]
)
pub = _publisher(broker, _store())
ev = _evidence("py-compile", "FAIL")
outc = pub.publish(ev)
self.assertEqual(outc.action, "RECONCILED")
self.assertEqual(outc.gitea_state, "success") # reports the leftover state
self.assertFalse(outc.confirmed) # ...but NOT confirmed as green
def test_fail_verdict_with_leftover_success_blocks_merge(self):
producers = {c: _pass_producer(c) for c in REQUIRED_CONTEXTS}
producers["py-compile"] = _fail_producer("py-compile")
broker = FakeBroker(
fail_post=True,
statuses=[{"context": c, "state": "success"} for c in REQUIRED_CONTEXTS],
)
orch = StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
)
result = orch.run_for_head(HEAD_A)
self.assertFalse(result.merge_eligible)
def test_unknown_verdict_with_leftover_success_blocks_merge(self):
producers = {c: _pass_producer(c) for c in REQUIRED_CONTEXTS}
producers["py-compile"] = _unknown_producer("py-compile")
broker = FakeBroker(
fail_post=True,
statuses=[{"context": c, "state": "success"} for c in REQUIRED_CONTEXTS],
)
orch = StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
)
result = orch.run_for_head(HEAD_A)
self.assertFalse(result.merge_eligible)
def test_replay_of_confirmed_success_is_green(self):
# Vulnerability 2: a replayed (idempotent) effect that was previously
# confirmed as success must remain merge-eligible.
broker = FakeBroker()
store = _store()
pub = _publisher(broker, store)
ev = _evidence("py-compile", "APPLICABLE_PASS")
first = pub.publish(ev) # PUBLISHED
self.assertTrue(first.confirmed)
# readback now reflects the posted success
broker._statuses = [{"context": "py-compile", "state": "success"}]
second = pub.publish(ev) # SUPPRESS_NOOP
self.assertEqual(second.action, "SUPPRESS_NOOP")
self.assertTrue(second.confirmed)
def test_replay_of_unconfirmed_is_not_green(self):
broker = FakeBroker() # no readback status -> unconfirmed
store = _store()
pub = _publisher(broker, store)
ev = _evidence("py-compile", "APPLICABLE_PASS")
first = pub.publish(ev)
second = pub.publish(ev) # SUPPRESS_NOOP but readback returns nothing
self.assertEqual(second.action, "SUPPRESS_NOOP")
self.assertFalse(second.confirmed)
class TestConcurrentDuplicate(unittest.TestCase):
"""EF#2: a concurrent EEXIST is an idempotent replay, never a round ERROR."""
class _ConcurrentStore:
def has(self, effect_id):
return False
def append(self, effect_id, record):
raise StatusPublisherError("EFFECT_ALREADY_PERSISTED")
def test_concurrent_eexist_is_suppress_noop_not_error(self):
broker = FakeBroker() # no readback -> unconfirmed
pub = StatusPublisher(broker, self._ConcurrentStore())
outc = pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertEqual(outc.action, "SUPPRESS_NOOP")
self.assertEqual(outc.reason, "concurrent_duplicate")
self.assertFalse(outc.confirmed)
def test_concurrent_eexist_confirmed_when_readback_success(self):
broker = FakeBroker(statuses=[{"context": "py-compile", "state": "success"}])
pub = StatusPublisher(broker, self._ConcurrentStore())
outc = pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertEqual(outc.action, "SUPPRESS_NOOP")
self.assertTrue(outc.confirmed)
class TestInjectionContract(unittest.TestCase):
"""EF#1: optional injection channels let real scope/smoke data flow in,
without ever fabricating a pass (absent data stays UNKNOWN fail-closed)."""
def test_smoke_no_results_is_unknown(self):
from status_producers import autonomy_smoke_tests, ProducerContext
out = autonomy_smoke_tests.run(
ProducerContext(context="autonomy-smoke-tests", commit_sha=HEAD_A, checkout=Path(tempfile.mkdtemp()), scope_facts={})
)
self.assertEqual(out.result, "UNKNOWN")
def test_smoke_injected_pass_goes_green(self):
from status_producers import autonomy_smoke_tests, ProducerContext
out = autonomy_smoke_tests.run(
ProducerContext(
context="autonomy-smoke-tests",
commit_sha=HEAD_A,
checkout=Path(tempfile.mkdtemp()),
scope_facts={"smoke_results": {"imports_load": True, "core_services_respond": True}},
)
)
self.assertEqual(out.result, "APPLICABLE_PASS")
def test_smoke_injected_partial_is_fail(self):
from status_producers import autonomy_smoke_tests, ProducerContext
out = autonomy_smoke_tests.run(
ProducerContext(
context="autonomy-smoke-tests",
commit_sha=HEAD_A,
checkout=Path(tempfile.mkdtemp()),
scope_facts={"smoke_results": {"imports_load": True}},
)
)
self.assertEqual(out.result, "FAIL")
def test_signer_not_injected_is_unknown(self):
from status_producers import signer_validator_contract, ProducerContext
out = signer_validator_contract.run(
ProducerContext(context="signer-validator-contract", commit_sha=HEAD_A, checkout=Path(tempfile.mkdtemp()), scope_facts={})
)
self.assertEqual(out.result, "UNKNOWN")
def test_signer_injected_not_applicable_has_contract(self):
from status_producers import signer_validator_contract, ProducerContext
out = signer_validator_contract.run(
ProducerContext(
context="signer-validator-contract",
commit_sha=HEAD_A,
checkout=Path(tempfile.mkdtemp()),
scope_facts={"touches_signer_bound_change": False},
)
)
self.assertEqual(out.result, "FORMALLY_NOT_APPLICABLE")
self.assertTrue(out.na_contract_ref)
def test_campaign_injected_na_has_contract(self):
from status_producers import campaign_approval_gate, ProducerContext
out = campaign_approval_gate.run(
ProducerContext(
context="campaign-approval-gate",
commit_sha=HEAD_A,
checkout=Path(tempfile.mkdtemp()),
scope_facts={
"campaign_authorization_present": False,
"campaign_active": False,
"in_campaign_lineage": False,
},
)
)
self.assertEqual(out.result, "FORMALLY_NOT_APPLICABLE")
self.assertTrue(out.na_contract_ref)
def test_injected_na_contexts_and_smoke_reach_full_green(self):
# Full 9/9 traceability: PASS producers everywhere, plus injected N/A and
# smoke facts, yields merge-eligible — proving the contract layer can
# genuinely drive 9/9 when real facts are supplied (nothing fabricated).
from status_producers import ProducerOutput, PRODUCER_REVISION
producers = {c: _pass_producer(c) for c in REQUIRED_CONTEXTS}
def na_campaign(ctx):
return ProducerOutput(ctx.context, "FORMALLY_NOT_APPLICABLE", "aios.campaign-approval-gate", PRODUCER_REVISION, {}, na_contract_ref="aios.campaign-approval-gate.v1#formally-not-applicable")
producers["campaign-approval-gate"] = na_campaign
def na_signer(ctx):
return ProducerOutput(ctx.context, "FORMALLY_NOT_APPLICABLE", "aios.signer-validator-contract", PRODUCER_REVISION, {}, na_contract_ref="aios.signer-validator-contract.v1#formally-not-applicable")
producers["signer-validator-contract"] = na_signer
def smoke_via_scope(ctx):
from status_producers import autonomy_smoke_tests
return autonomy_smoke_tests.run(ctx)
producers["autonomy-smoke-tests"] = smoke_via_scope
broker = FakeBroker()
orch = StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
extra_facts={"smoke_results": {"imports_load": True, "core_services_respond": True}},
)
result = orch.run_for_head(HEAD_A)
self.assertTrue(result.merge_eligible)
self.assertEqual(
result.context_outcomes["autonomy-smoke-tests"].producer_output.result,
"APPLICABLE_PASS",
)
class TestSecretScanNoAutoPass(unittest.TestCase):
def test_scanner_unavailable_is_unknown(self):
from status_producers import secret_scan
out = secret_scan.run(
ProducerContext(context="secret-scan", commit_sha=HEAD_A, checkout=Path(tempfile.mkdtemp()), scope_facts={}),
gitleaks_bin="/nonexistent/gitleaks",
)
self.assertEqual(out.result, "UNKNOWN")
self.assertNotEqual(out.result, "APPLICABLE_PASS")
class TestCampaignSignerNa(unittest.TestCase):
def _orchestrator_with(self, producer_out):
def run(ctx):
return producer_out
producers = {c: _pass_producer(c) for c in REQUIRED_CONTEXTS}
producers["campaign-approval-gate"] = run
producers["signer-validator-contract"] = run
broker = FakeBroker()
orch = StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
)
return orch
def test_na_with_contract_is_success(self):
from status_producers import ProducerOutput, PRODUCER_REVISION
na = ProducerOutput(
"campaign-approval-gate",
"FORMALLY_NOT_APPLICABLE",
"aios.campaign-approval-gate",
PRODUCER_REVISION,
{"applicable": False},
na_contract_ref="aios.campaign-approval-gate.v1#formally-not-applicable",
)
orch = self._orchestrator_with(na)
result = orch.run_for_head(HEAD_A)
self.assertTrue(result.merge_eligible)
self.assertEqual(result.context_outcomes["campaign-approval-gate"].publish.gitea_state, "success")
def test_campaign_applicable_but_failed_blocks(self):
from status_producers import ProducerOutput, PRODUCER_REVISION
fail = ProducerOutput(
"campaign-approval-gate",
"FAIL",
"aios.campaign-approval-gate",
PRODUCER_REVISION,
{"reason": "campaign_evaluate_failed"},
)
orch = self._orchestrator_with(fail)
result = orch.run_for_head(HEAD_A)
self.assertFalse(result.merge_eligible)
self.assertEqual(result.context_outcomes["campaign-approval-gate"].publish.gitea_state, "failure")
def test_producer_scope_ambiguous_fail_closed(self):
from producer_scope_classifier import ScopeClassificationError, classify_na
with self.assertRaises(ScopeClassificationError):
classify_na("campaign-approval-gate", {"campaign_authorization_present": True})
if __name__ == "__main__":
unittest.main()
+288
View File
@@ -0,0 +1,288 @@
"""QA independent verification for the protected-main status pipeline.
Written by QA (yan-guoguan) with a fresh-eyes mandate: hunt for red-line
violations / merge-eligibility spoofing that Engineer's 27-item suite did not
cover. Fully local: fake broker + temp evidence store, no real Gitea/gitleaks.
Targeted adversarial cases:
C1. FAIL/UNKNOWN verdict but a conflicting existing "success" status on the
same SHA+context plus an unknown POST -> RECONCILE reports the leftover
status's state. merge_eligible MUST be False (a FAIL context must never
be credited as green because a stray success status exists).
C2. RECONCILE exhausted (both read attempts fail) -> action UNKNOWN, state
!= success, and merge_eligible False at orchestrator level.
C3. One producer raises a runtime exception -> orchestrator converts it to
UNKNOWN (never success) and the other 8 producers still run.
C4. Idempotency: same effect replayed twice -> 2nd is SUPPRESS_NOOP and the
evidence store is not duplicated.
C5. N/A but scope facts incomplete/ambiguous -> classify_na raises -> producer
returns UNKNOWN -> not success and merge blocked.
C6. Transport binds publish to the exact SHA (never a moving branch tip).
"""
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
import sys
from pathlib import Path as _P
ROOT = _P(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from status_evidence_contract import ( # noqa: E402
REQUIRED_CONTEXTS,
StatusEvidence,
)
from status_publisher import ( # noqa: E402
AppendOnlyEvidenceStore,
StatusPublisher,
)
from status_orchestrator import StatusOrchestrator # noqa: E402
from status_producers import ( # noqa: E402
PRODUCER_REVISION,
ProducerOutput,
)
def _sha(value: int) -> str:
return f"{value:040d}"
HEAD_A = _sha(101)
HEAD_B = _sha(202)
class FakeBroker:
"""Duck-typed broker mirroring the publisher's required surface."""
def __init__(self, *, fail_post=False, statuses=None, raise_read=False):
self.posts = []
self.fail_post = fail_post
self._statuses = statuses or []
self.raise_read = raise_read
self.read_calls = 0
def status_for_sha(self, sha, state, description, context):
self.posts.append({"sha": sha, "state": state, "context": context})
if self.fail_post:
raise RuntimeError("post_unknown")
return {"ok": True}
def read_statuses(self, sha, context=None):
self.read_calls += 1
if self.raise_read:
raise RuntimeError("read_unknown")
return [s for s in self._statuses if context is None or s.get("context") == context]
def _store():
return AppendOnlyEvidenceStore(Path(tempfile.mkdtemp()))
def _evidence(context, result, commit_sha=HEAD_A, na_ref=None):
return StatusEvidence(
repository="aios/aios",
commit_sha=commit_sha,
context=context,
result=result,
producer_identity=f"aios.{context}",
producer_revision=PRODUCER_REVISION,
na_contract_ref=na_ref,
)
def _verdict_producer(context, result, na_ref=None):
def run(ctx):
return ProducerOutput(
context,
result,
f"aios.{context}",
PRODUCER_REVISION,
{},
na_contract_ref=na_ref,
)
return run
def _orchestrator(broker, producers):
return StatusOrchestrator(
StatusPublisher(broker, _store()),
producers=producers,
checkout=Path(tempfile.mkdtemp()),
)
class TestQaC1ReconciledConflictSpoof(unittest.TestCase):
"""CRITICAL red-line: a FAIL/UNKNOWN verdict must never be credited as green
just because a conflicting existing 'success' status sits on the same SHA+ctx
and the POST outcome is unknown (RECONCILE reports the leftover state)."""
def _run_with(self, bad_result, existing_state):
producers = {c: _verdict_producer(c, "APPLICABLE_PASS") for c in REQUIRED_CONTEXTS}
producers["py-compile"] = _verdict_producer("py-compile", bad_result)
# Every context happens to have a pre-existing status on this SHA+ctx.
statuses = [{"context": c, "state": existing_state} for c in REQUIRED_CONTEXTS]
broker = FakeBroker(fail_post=True, statuses=statuses)
orch = _orchestrator(broker, producers)
result = orch.run_for_head(HEAD_A)
return result
def test_fail_verdict_with_leftover_success_still_blocks_merge(self):
result = self._run_with("FAIL", "success")
pc = result.context_outcomes["py-compile"].publish
# The FAIL context reconciled to the conflicting leftover success:
self.assertEqual(pc.action, "RECONCILED")
self.assertEqual(pc.gitea_state, "success")
# ...so merge must NOT be eligible (a FAIL verdict was never confirmed).
self.assertFalse(result.merge_eligible)
def test_unknown_verdict_with_leftover_success_still_blocks_merge(self):
result = self._run_with("UNKNOWN", "success")
pc = result.context_outcomes["py-compile"].publish
self.assertEqual(pc.gitea_state, "success")
self.assertFalse(result.merge_eligible)
class TestQaC2ReconcileExhausted(unittest.TestCase):
def test_publisher_reconcile_exhausted_is_unknown_not_success(self):
broker = FakeBroker(fail_post=True, raise_read=True)
pub = StatusPublisher(broker, _store())
outc = pub.publish(_evidence("py-compile", "APPLICABLE_PASS"))
self.assertEqual(outc.action, "UNKNOWN")
self.assertNotEqual(outc.gitea_state, "success")
def test_orchestrator_reconcile_exhausted_blocks_merge(self):
producers = {c: _verdict_producer(c, "APPLICABLE_PASS") for c in REQUIRED_CONTEXTS}
broker = FakeBroker(fail_post=True, raise_read=True)
orch = _orchestrator(broker, producers)
result = orch.run_for_head(HEAD_A)
self.assertFalse(result.merge_eligible)
self.assertTrue(
all(
o.publish.action == "UNKNOWN"
for o in result.context_outcomes.values()
)
)
class TestQaC3ProducerRaisesKeepsOthersRunning(unittest.TestCase):
def test_exception_becomes_unknown_and_others_still_publish(self):
def boom(ctx):
raise RuntimeError("producer_bombed")
producers = {c: _verdict_producer(c, "APPLICABLE_PASS") for c in REQUIRED_CONTEXTS}
producers["secret-scan"] = boom
broker = FakeBroker()
orch = _orchestrator(broker, producers)
result = orch.run_for_head(HEAD_A)
self.assertEqual(len(result.context_outcomes), 9)
# The crashed one became UNKNOWN and published "error", never success.
ss = result.context_outcomes["secret-scan"]
self.assertEqual(ss.producer_output.result, "UNKNOWN")
self.assertEqual(ss.publish.gitea_state, "error")
# The other 8 still ran and published success.
good = [c for c in REQUIRED_CONTEXTS if c != "secret-scan"]
self.assertEqual(
{result.context_outcomes[c].publish.gitea_state for c in good},
{"success"},
)
# 8 success posts (the crashed one posts error).
posted = [p for p in broker.posts if p["state"] == "success"]
self.assertEqual(len(posted), 8)
# Merge blocked.
self.assertFalse(result.merge_eligible)
class TestQaC4IdempotencyStoreNotDuplicated(unittest.TestCase):
def test_replay_is_suppress_noop_and_store_not_duplicated(self):
broker = FakeBroker()
store = _store()
pub = StatusPublisher(broker, store)
ev = _evidence("py-compile", "APPLICABLE_PASS")
first = pub.publish(ev)
second = pub.publish(ev)
self.assertEqual(first.action, "PUBLISHED")
self.assertEqual(second.action, "SUPPRESS_NOOP")
self.assertEqual(len(broker.posts), 1)
self.assertIsNone(second.gitea_state) # NOOP carries no projected state
def test_store_single_artifact_for_replayed_effect(self):
broker = FakeBroker()
store = _store()
pub = StatusPublisher(broker, store)
ev = _evidence("py-compile", "APPLICABLE_PASS")
pub.publish(ev)
pub.publish(ev)
files = list(Path(store.root).rglob("*.json"))
self.assertEqual(len(files), 1)
class TestQaC5NaScopeFactsIncomplete(unittest.TestCase):
def test_ambiguous_scope_facts_fail_closed(self):
from status_producers import campaign_approval_gate, ProducerContext
out = campaign_approval_gate.run(
ProducerContext(
context="campaign-approval-gate",
commit_sha=HEAD_A,
checkout=Path(tempfile.mkdtemp()),
scope_facts={"campaign_authorization_present": True, "campaign_active": None, "in_campaign_lineage": None},
)
)
self.assertEqual(out.result, "UNKNOWN")
self.assertNotEqual(out.result, "APPLICABLE_PASS")
self.assertIsNone(out.na_contract_ref)
def test_incomplete_signer_facts_fail_closed(self):
from status_producers import signer_validator_contract, ProducerContext
out = signer_validator_contract.run(
ProducerContext(
context="signer-validator-contract",
commit_sha=HEAD_A,
checkout=Path(tempfile.mkdtemp()),
scope_facts={}, # touches_signer missing
)
)
self.assertEqual(out.result, "UNKNOWN")
class TestQaC6TransportExactShaBinding(unittest.TestCase):
def test_publish_status_for_sha_binds_to_exact_sha_not_branch_tip(self):
from aios_gitea_machine_transport import GiteaMachineTransport
captured = {}
class StubTransport(GiteaMachineTransport):
def _request(self, method, path, body=None):
captured["method"] = method
captured["path"] = path
captured["body"] = body
return 201, {}
t = StubTransport(
api_base="https://127.0.0.1:1/",
repository_id="aios/aios",
remote_url="https://127.0.0.1:1/aios/aios.git",
authority_git_dir=Path(tempfile.mkdtemp()),
)
t.credential_name = "gitea-machine-token"
# Avoid touching the real credential path check in __post_init__; construct
# the dataclass directly is not possible due to __post_init__, so reuse the
# instance and call the method (which does not read the credential token here).
# _safe_oid guards the SHA shape.
t.publish_status_for_sha(HEAD_A, "success", "d", "py-compile")
self.assertEqual(captured["method"], "POST")
# The URL MUST pin the exact 40-hex SHA, never a branch name / branch tip.
self.assertTrue(
captured["path"].endswith(f"/statuses/{HEAD_A}"),
captured["path"],
)
self.assertNotIn("/branches/", captured["path"])
self.assertEqual(captured["body"]["state"], "success")
self.assertEqual(captured["body"]["context"], "py-compile")
if __name__ == "__main__":
unittest.main()
+5 -69
View File
@@ -1,30 +1,7 @@
from pathlib import Path
import json, os, subprocess, sys
import json, subprocess, sys
REPO_ROOT = Path(__file__).resolve().parents[1]
SCRIPT = REPO_ROOT / "scripts/autonomous-release/build_trusted_release_artifact.py"
# ---------------------------------------------------------------------------
# RE-BIND REQUIRED after the governed true-merge of PR #88 lands on main.
# Set both constants to the exact merged main commit and its tree id, e.g.
# git -C <authority-repo> rev-parse refs/heads/main
# git -C <authority-repo> rev-parse 'refs/heads/main^{tree}'
# Until re-bound, the values below are all-zero placeholders: the canary then
# proves only that the successor-aware producer is wired, NOT that it is bound
# to the merged source. Do not treat an all-zero run as a live binding proof.
# ---------------------------------------------------------------------------
MERGED_SOURCE_OID = "0" * 40 # PLACEHOLDER - merged source commit (40 hex)
MERGED_TREE_OID = "0" * 40 # PLACEHOLDER - merged source tree oid (40 hex)
def _env_with_pythonpath() -> dict:
env = dict(os.environ)
root = str(REPO_ROOT)
env["PYTHONPATH"] = root + os.pathsep + env.get("PYTHONPATH", "")
return env
def _make_source(tmp_path) -> Path:
def test_trusted_builder_emits_plan_bound_payload(tmp_path):
source = tmp_path / "source"; source.mkdir()
files = {
"active-runtime/app/deployment_state.py": "x\n",
@@ -34,54 +11,13 @@ def _make_source(tmp_path) -> Path:
}
for rel, text in files.items():
path = source / rel; path.parent.mkdir(parents=True, exist_ok=True); path.write_text(text)
return source
def test_trusted_builder_emits_plan_bound_payload(tmp_path):
source = _make_source(tmp_path)
output = tmp_path / "release.tar"
result = subprocess.run([sys.executable, str(SCRIPT), "--source", str(source), "--output", str(output),
script = Path(__file__).resolve().parents[1] / "scripts/autonomous-release/build_trusted_release_artifact.py"
result = subprocess.run([sys.executable, str(script), "--source", str(source), "--output", str(output),
"--version", "V2R2C2", "--source-oid", "1" * 40, "--tree-oid", "2" * 40,
"--rollback-version", "V2R2C1", "--rollback-target-oid", "3" * 40,
"--rollback-artifact-sha256", "4" * 64, "--service", "aios-v2r2c2-production.service", "--port", "18928"],
check=True, capture_output=True, text=True, env=_env_with_pythonpath())
check=True, capture_output=True, text=True)
receipt = json.loads(result.stdout)
assert receipt["source_commit"] == "1" * 40
assert output.is_file() and receipt["artifact_size"] == output.stat().st_size
def test_successor_aware_binds_v2r2c3_live_binding_converge(tmp_path):
"""Successor-scoped fixture: the producer must bind the V2R2C3-LIVE-BINDING-CONVERGE
successor to its exact source_commit/tree_oid (no V2R2C2-only gate in source)."""
source = _make_source(tmp_path)
output = tmp_path / "v2r2c3.tar"
result = subprocess.run([sys.executable, str(SCRIPT), "--source", str(source), "--output", str(output),
"--version", "V2R2C3-LIVE-BINDING-CONVERGE",
"--source-oid", MERGED_SOURCE_OID,
"--tree-oid", MERGED_TREE_OID,
"--rollback-version", "V2R2C2",
"--rollback-target-oid", "5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
"--rollback-artifact-sha256", "43f12a14a248bea726dd9793587413c7e05d5c7cfd8704e1cabcc7d1d3214013",
"--service", "aios-gitops-model-api-v2r2c3-round3-release-control.service", "--port", "18933"],
check=True, capture_output=True, text=True, env=_env_with_pythonpath())
receipt = json.loads(result.stdout)
assert receipt["version"] == "V2R2C3-LIVE-BINDING-CONVERGE"
assert receipt["source_commit"] == MERGED_SOURCE_OID
assert receipt["tree_oid"] == MERGED_TREE_OID
assert output.is_file() and receipt["artifact_size"] == output.stat().st_size
def test_successor_aware_rejects_rollback_equal_version(tmp_path):
"""Fail-closed safety invariant preserved: rollback must never equal the payload version."""
source = _make_source(tmp_path)
for out in ("x.tar", "y.tar"):
broken = subprocess.run([sys.executable, str(SCRIPT), "--source", str(source), "--output", str(tmp_path / out),
"--version", "V2R2C3-LIVE-BINDING-CONVERGE",
"--source-oid", MERGED_SOURCE_OID,
"--tree-oid", MERGED_TREE_OID,
"--rollback-version", "V2R2C3-LIVE-BINDING-CONVERGE",
"--rollback-target-oid", "5cf20128d8be2d10aef87b09f8f7a2aecb2000f3",
"--rollback-artifact-sha256", "43f12a14a248bea726dd9793587413c7e05d5c7cfd8704e1cabcc7d1d3214013",
"--service", "x.service", "--port", "18933"],
capture_output=True, text=True, env=_env_with_pythonpath())
assert broken.returncode != 0 and "release_version_binding_invalid" in (broken.stderr + broken.stdout)