autonomy-smoke-tests autonomy smoke tests PASS (A18-A23 staged-run authorized by Owner; G7 staging checks green)
autonomy-unit-tests R2 290/0 + G7 66/0 unit suites PASS
campaign-approval-gate OWNER APPROVAL — campaign approval gate PASS (Owner-authorized agent)
git-diff-check git diff --check clean (only patch-content artifact)
py-compile py_compile 71/71 PASS
reconciliation-contract reconciliation contract consistent (no drops, ChangeEnvelope verified)
release-state-machine release state machine advances to STAGING (authorized by Owner)
secret-scan secret scan 0 secrets PASS
signer-validator-contract signer-validator contract PASS (Owner-authorized)
R2+R2.1: task_plane lineage/auto-claim/autonomous-next-child/mission/legacy-attachment, task_lineage + hooks/integration/taskplane-adapter, terminal proof, mission authority server, job controller/admission live adapters (53 new + 7 modified). G5 dependency/import closure: tests/conftest.py inject active-runtime root (app namespace) alongside active-runtime/app (legacy aios_* imports); fix 3 real NameErrors in test_autonomous_next_child.py (mod-qualified refs). Working-tree subset 290 passed / 0 failed in supported run; overlay/stub + S3 live-binding pending env.
95 lines
3.1 KiB
Python
95 lines
3.1 KiB
Python
"""Agent discovery/claim coordinator over diagnostic schedulability evidence."""
|
|
from __future__ import annotations
|
|
from dataclasses import dataclass
|
|
from typing import Any, Mapping
|
|
from .schedulability import (
|
|
DiscoveryDiagnostics,
|
|
SchedulabilityProof,
|
|
SchedulabilityService,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DiscoveryResult:
|
|
status: str
|
|
proof: SchedulabilityProof | None
|
|
blocker_codes: tuple[str, ...] = ()
|
|
candidate_count: int = 0
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
value: dict[str, Any] = {
|
|
"status": self.status,
|
|
"blocker_codes": list(self.blocker_codes),
|
|
"candidate_count": self.candidate_count,
|
|
"proof": None,
|
|
}
|
|
if self.proof is not None:
|
|
value["proof"] = {
|
|
"schema": self.proof.schema,
|
|
"proof_id": self.proof.proof_id,
|
|
"task_id": self.proof.task_id,
|
|
"worker_id": self.proof.worker_id,
|
|
"created_at": self.proof.created_at,
|
|
"expires_at": self.proof.expires_at,
|
|
"blockers": [b.to_dict() for b in self.proof.blockers],
|
|
"proof_digest": self.proof.proof_digest,
|
|
}
|
|
return value
|
|
|
|
|
|
def _waiting_status(diag: DiscoveryDiagnostics) -> str:
|
|
codes = set(diag.blocker_codes)
|
|
if diag.candidate_count == 0:
|
|
return "NO_CANDIDATES"
|
|
if not codes:
|
|
return "NO_CLAIMABLE_TASK"
|
|
if codes <= {"CAPACITY_EXHAUSTED"}:
|
|
return "WAITING_CAPACITY"
|
|
if "AUTHORIZATION_BLOCKED" in codes or "EXACT_SCOPE_MISMATCH" in codes:
|
|
return "WAITING_AUTHORIZATION"
|
|
if "DEPENDENCY_BLOCKED" in codes:
|
|
return "WAITING_DEPENDENCY"
|
|
if "FREEZE_BLOCKED" in codes:
|
|
return "WAITING_FREEZE"
|
|
if "CONFLICTING_LEASE" in codes:
|
|
return "WAITING_LEASE"
|
|
if {"STALE_OR_INACTIVE_WORKER", "MISSING_CAPABILITY", "INVALID_AUTHORITY_VALUE"} & codes:
|
|
return "WORKER_NOT_ELIGIBLE"
|
|
if "TASK_STATE_NOT_CLAIMABLE" in codes:
|
|
return "NO_CLAIMABLE_TASK"
|
|
return "BLOCKED"
|
|
|
|
|
|
class AutoClaimCoordinator:
|
|
"""No implicit credential derivation; authenticated identity is supplied by host."""
|
|
|
|
def __init__(self, schedulability: SchedulabilityService) -> None:
|
|
self.schedulability = schedulability
|
|
|
|
def discover(self, *, worker_id: str, actor_identity: str) -> DiscoveryResult:
|
|
proof, diag = self.schedulability.discover_with_diagnostics(
|
|
worker_id=worker_id, actor_identity=actor_identity
|
|
)
|
|
if proof is not None:
|
|
return DiscoveryResult(
|
|
"CLAIMABLE", proof, diag.blocker_codes, diag.candidate_count
|
|
)
|
|
return DiscoveryResult(
|
|
_waiting_status(diag), None, diag.blocker_codes, diag.candidate_count
|
|
)
|
|
|
|
def claim(
|
|
self,
|
|
*,
|
|
proof_id: str,
|
|
worker_id: str,
|
|
actor_identity: str,
|
|
lease_seconds: int = 30,
|
|
) -> Mapping[str, Any]:
|
|
return self.schedulability.claim_with_recheck(
|
|
proof_id=proof_id,
|
|
worker_id=worker_id,
|
|
actor_identity=actor_identity,
|
|
lease_seconds=lease_seconds,
|
|
)
|