Files
aios/active-runtime/app/task_plane/governance_control_kernel.py
T

872 lines
36 KiB
Python

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()