2154 lines
87 KiB
Python
2154 lines
87 KiB
Python
"""Narrow adapters for the already-authorized Owner and Agent Task seams.
|
|
|
|
This file intentionally contains no credential lookup, HTTP client construction,
|
|
or fallback identity. The service host injects its authenticated durable-epoch
|
|
provider and its service-managed Agent Task transport; a request body can never
|
|
provide either authority.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timezone
|
|
import hashlib
|
|
import hmac
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
import re
|
|
import sqlite3
|
|
from pathlib import Path
|
|
import stat
|
|
import time
|
|
from typing import Any, Callable, Mapping, Protocol
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
from aios_task_admission_bootstrap import (
|
|
OwnerDelegationEpoch,
|
|
SCHEMA_EPOCH,
|
|
SCHEMA_EPOCH_V2,
|
|
TaskAdmissionError,
|
|
TaskAdmissionGrant,
|
|
canonical_json_bytes,
|
|
sha256_json,
|
|
)
|
|
from aios_gitops_job_controller import (
|
|
GitOpsJobController as _FLGitOpsJobController,
|
|
JobState as _FLJobState,
|
|
sha256_json as _fl_job_sha256_json,
|
|
)
|
|
from aios_gitops_job_identity import (
|
|
legacy_task_storage_key as _fl_legacy_task_storage_key,
|
|
task_storage_key as _fl_task_storage_key,
|
|
)
|
|
|
|
|
|
OWNER_NAMESPACE = "aios-owner-approval-v1"
|
|
OWNER_SIGNER_IDENTITY = "aios-task-grant-signer"
|
|
_EPOCH_V1_FIELDS = frozenset({
|
|
"epoch_id", "owner_identity", "trust_domain", "repository",
|
|
"allowed_branch_prefixes", "allowed_capabilities", "allowed_file_prefixes",
|
|
"risk_ceiling", "issued_at", "not_before", "expires_at",
|
|
"revocation_epoch", "policy_digest", "schema",
|
|
})
|
|
_EPOCH_V2_FIELDS = frozenset({
|
|
*_EPOCH_V1_FIELDS,
|
|
"business_goal", "production_scope", "destructive_policy",
|
|
"signature_namespace", "signature_key_id", "canonical_payload_digest",
|
|
"status",
|
|
})
|
|
_SSH_FINGERPRINT_RE = re.compile(r"^SHA256:[A-Za-z0-9+/]{43}$")
|
|
|
|
|
|
class OfficialTaskTransport(Protocol):
|
|
"""Service-managed transport; it owns the Task API control credential."""
|
|
|
|
def request(self, method: str, path: str, body: Mapping[str, Any] | None = None) -> Mapping[str, Any]:
|
|
...
|
|
|
|
|
|
class BoundOwnerAuthorizationBackend:
|
|
"""Bind epoch lookup to trusted transport identity, never caller JSON."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
authenticated_caller: Mapping[str, Any],
|
|
verified_epoch_provider: Callable[..., OwnerDelegationEpoch],
|
|
signature_verifier: Any,
|
|
) -> None:
|
|
if not isinstance(authenticated_caller, Mapping) or not authenticated_caller:
|
|
raise ValueError("AUTHENTICATED_CALLER_REQUIRED")
|
|
self._caller = dict(authenticated_caller)
|
|
self._epoch_provider = verified_epoch_provider
|
|
self._signature_verifier = signature_verifier
|
|
|
|
def verify_delegation_epoch(self, epoch_id: str) -> OwnerDelegationEpoch:
|
|
try:
|
|
epoch = self._epoch_provider(epoch_id=epoch_id, caller=dict(self._caller))
|
|
except TypeError as exc:
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_BACKEND_INVALID", 503) from exc
|
|
if not isinstance(epoch, OwnerDelegationEpoch):
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_BACKEND_INVALID", 503)
|
|
return epoch
|
|
|
|
def verify_owner_signature(
|
|
self, *, key_id: str, canonical_payload: bytes, signature_b64: str
|
|
) -> str:
|
|
try:
|
|
armored = base64.b64decode(signature_b64, validate=True).decode("utf-8")
|
|
except (ValueError, UnicodeDecodeError) as exc:
|
|
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403) from exc
|
|
verify = getattr(self._signature_verifier, "verify", None)
|
|
if not callable(verify) or verify(
|
|
namespace=OWNER_NAMESPACE,
|
|
payload=canonical_payload,
|
|
signature=armored,
|
|
key_id=key_id,
|
|
) is not True:
|
|
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403)
|
|
return OWNER_SIGNER_IDENTITY
|
|
|
|
|
|
class AgentTaskAdmissionPort:
|
|
"""Official Task API adapter with exact identity reconciliation.
|
|
|
|
It never exposes or returns the API control bearer. A timeout or 5xx is
|
|
reconciled by exact GET; if that cannot prove the state, the durable caller
|
|
receives UNKNOWN instead of a second blind POST.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
transport: OfficialTaskTransport,
|
|
*,
|
|
execution_plan_provider: Callable[[TaskAdmissionGrant], Mapping[str, Any]] | None = None,
|
|
) -> None:
|
|
if not callable(getattr(transport, "request", None)):
|
|
raise ValueError("OFFICIAL_TASK_TRANSPORT_REQUIRED")
|
|
self._transport = transport
|
|
self._execution_plan_provider = execution_plan_provider
|
|
|
|
def _execution_plan(self, grant: TaskAdmissionGrant) -> dict[str, Any]:
|
|
if self._execution_plan_provider is None:
|
|
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_PLAN_REQUIRED", 503)
|
|
try:
|
|
raw = self._execution_plan_provider(grant)
|
|
except TaskAdmissionError:
|
|
raise
|
|
except Exception as exc:
|
|
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_PLAN_UNAVAILABLE", 503) from exc
|
|
if not isinstance(raw, Mapping):
|
|
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_PLAN_INVALID", 403)
|
|
expected = {
|
|
"repository": grant.repository,
|
|
"target_branch": grant.target_branch,
|
|
"generation": grant.generation,
|
|
"execution_digest": grant.execution_digest,
|
|
}
|
|
if any(canonical_json_bytes(raw.get(key)) != canonical_json_bytes(value) for key, value in expected.items()):
|
|
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_PLAN_CONFLICT", 409)
|
|
external_job_id = raw.get("external_job_id")
|
|
if not isinstance(external_job_id, str) or not external_job_id.strip():
|
|
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_PLAN_INVALID", 403)
|
|
# The trusted provider resolves the immutable execution payload by its
|
|
# signed digest. Preserve that payload: returning just coordinates
|
|
# would discard the executable plan needed by the real worker.
|
|
return dict(raw)
|
|
|
|
@staticmethod
|
|
def _metadata(grant: TaskAdmissionGrant, execution: Mapping[str, Any]) -> dict[str, Any]:
|
|
return {
|
|
"task_admission_schema": "aios.task-admission-grant.v1",
|
|
"grant_id": grant.grant_id,
|
|
"grant_digest_sha256": sha256_json(grant.as_dict()),
|
|
"repository": grant.repository,
|
|
"authority_base_oid": grant.authority_base_oid,
|
|
"target_branch": grant.target_branch,
|
|
"generation": grant.generation,
|
|
"execution_digest": grant.execution_digest,
|
|
"file_allowlist": list(grant.file_allowlist),
|
|
"capabilities": list(grant.capabilities),
|
|
"risk": grant.risk,
|
|
"execution": dict(execution),
|
|
}
|
|
|
|
@staticmethod
|
|
def _deadline(epoch: int) -> str:
|
|
# TaskStore.iso preserves the explicit UTC offset; exact reconciliation
|
|
# compares against that stored representation rather than a Z variant.
|
|
return datetime.fromtimestamp(epoch, timezone.utc).isoformat()
|
|
|
|
def _body(self, grant: TaskAdmissionGrant, execution: Mapping[str, Any]) -> dict[str, Any]:
|
|
metadata = self._metadata(grant, execution)
|
|
immutable = canonical_json_bytes(metadata).decode("utf-8")
|
|
return {
|
|
"task_id": grant.task_id,
|
|
"goal": "Execute the immutable governed task admission grant.",
|
|
"instructions": "Use only the admitted immutable metadata; execution plan digest: " + grant.execution_digest,
|
|
"success_criteria": ["Task processing preserves the admitted immutable identity."],
|
|
"required_capabilities": sorted(grant.capabilities),
|
|
"constraints": {
|
|
"repository": grant.repository,
|
|
"authority_base_oid": grant.authority_base_oid,
|
|
"target_branch": grant.target_branch,
|
|
"generation": grant.generation,
|
|
"file_allowlist": list(grant.file_allowlist),
|
|
"immutable_metadata": immutable,
|
|
},
|
|
"metadata": metadata,
|
|
"deadline": self._deadline(grant.expires_at),
|
|
"priority": 0,
|
|
"max_attempts": 1,
|
|
"idempotency_key": "task-admission/" + grant.grant_id,
|
|
}
|
|
|
|
def _exact(
|
|
self, task: Mapping[str, Any], grant: TaskAdmissionGrant, execution: Mapping[str, Any]
|
|
) -> dict[str, Any]:
|
|
expected = self._body(grant, execution)
|
|
immutable_fields = (
|
|
"task_id", "goal", "instructions", "success_criteria",
|
|
"required_capabilities", "constraints", "metadata", "deadline",
|
|
"priority", "max_attempts",
|
|
)
|
|
def stable(field: str, value: Any) -> Any:
|
|
"""Exclude only re-admission provenance, never execution scope."""
|
|
if field == "metadata" and isinstance(value, Mapping):
|
|
return {
|
|
key: item
|
|
for key, item in value.items()
|
|
if key not in {"grant_id", "grant_digest_sha256"}
|
|
}
|
|
if field == "constraints" and isinstance(value, Mapping):
|
|
return {
|
|
key: item
|
|
for key, item in value.items()
|
|
if key != "immutable_metadata"
|
|
}
|
|
return value
|
|
|
|
if any(
|
|
canonical_json_bytes(stable(field, task.get(field)))
|
|
!= canonical_json_bytes(stable(field, expected[field]))
|
|
for field in immutable_fields
|
|
):
|
|
raise TaskAdmissionError("TASK_ADMISSION_TASK_IDENTITY_CONFLICT", 409)
|
|
state = str(task.get("state") or task.get("status") or "")
|
|
if state not in {"QUEUED", "CLAIMED", "RUNNING"}:
|
|
raise TaskAdmissionError("TASK_ADMISSION_PORT_STATE_INVALID", 502, details={"state": state})
|
|
# The public admission port reports only authoritative Task identity
|
|
# and state. The execution-plan identity is an internal capability
|
|
# used later by a trusted post-admission projection, never a caller
|
|
# visible part of this HTTP read model.
|
|
return {"task_id": grant.task_id, "state": state}
|
|
|
|
def _read(self, grant: TaskAdmissionGrant, execution: Mapping[str, Any]) -> dict[str, Any] | None:
|
|
try:
|
|
task = self._transport.request("GET", "/v1/tasks/" + grant.task_id)
|
|
except Exception as exc:
|
|
# A transport is expected to expose a status for normal 404s. A
|
|
# generic failure is ambiguous and must not be treated as absent.
|
|
statuses = [
|
|
getattr(exc, attribute, None)
|
|
for attribute in ("status", "http_status", "code")
|
|
if getattr(exc, attribute, None) is not None
|
|
]
|
|
if statuses and all(status == 404 for status in statuses):
|
|
return None
|
|
raise TaskAdmissionError("TASK_ADMISSION_TASK_STATE_UNKNOWN", 503) from exc
|
|
if not isinstance(task, Mapping):
|
|
raise TaskAdmissionError("TASK_ADMISSION_PORT_RESPONSE_INVALID", 502)
|
|
return self._exact(task, grant, execution)
|
|
|
|
def admit(self, grant: TaskAdmissionGrant) -> Mapping[str, Any]:
|
|
execution = self._execution_plan(grant)
|
|
existing = self._read(grant, execution)
|
|
if existing is not None:
|
|
return existing
|
|
try:
|
|
created = self._transport.request("POST", "/v1/tasks", self._body(grant, execution))
|
|
except Exception as exc:
|
|
recovered = self._read(grant, execution)
|
|
if recovered is not None:
|
|
return recovered
|
|
raise TaskAdmissionError("TASK_ADMISSION_TASK_STATE_UNKNOWN", 503) from exc
|
|
if not isinstance(created, Mapping):
|
|
raise TaskAdmissionError("TASK_ADMISSION_PORT_RESPONSE_INVALID", 502)
|
|
# Authoritative GET prevents a POST response from becoming the source
|
|
# of task identity/state and handles a task claimed before readback.
|
|
recovered = self._read(grant, execution)
|
|
if recovered is None:
|
|
raise TaskAdmissionError("TASK_ADMISSION_TASK_STATE_UNKNOWN", 503)
|
|
return recovered
|
|
|
|
def verify_active_task(
|
|
self, grant: TaskAdmissionGrant
|
|
) -> tuple[Mapping[str, Any], Mapping[str, Any]]:
|
|
"""Re-read and validate the authoritative Task for execution use.
|
|
|
|
Admission may complete while the Task is still QUEUED. A TaskPrincipal
|
|
may be recovered only after the Task authority reports CLAIMED/RUNNING.
|
|
The exact immutable metadata is revalidated on every call.
|
|
"""
|
|
execution = self._execution_plan(grant)
|
|
task = self._read(grant, execution)
|
|
if task is None:
|
|
raise TaskAdmissionError("TASK_PRINCIPAL_TASK_NOT_FOUND", 403)
|
|
state = str(task.get("state") or task.get("status") or "")
|
|
if state not in {"CLAIMED", "RUNNING"}:
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_TASK_NOT_CLAIMED",
|
|
409,
|
|
details={"state": state},
|
|
)
|
|
return task, execution
|
|
|
|
def projection_result(
|
|
self,
|
|
grant: TaskAdmissionGrant,
|
|
admitted: Mapping[str, Any],
|
|
) -> Mapping[str, Any]:
|
|
"""Return the server-side execution identity for a validated readback.
|
|
|
|
`admit()` intentionally keeps this identity out of its public result.
|
|
The bootstrap service calls this only for its local post-admission
|
|
projection; the value comes from the content-addressed execution-plan
|
|
provider, not from the HTTP caller or the Task API response.
|
|
"""
|
|
if str(admitted.get("task_id") or "") != grant.task_id:
|
|
raise TaskAdmissionError("TASK_ADMISSION_PORT_TASK_MISMATCH", 502)
|
|
state = str(admitted.get("state") or admitted.get("status") or "")
|
|
if state not in {"QUEUED", "CLAIMED", "RUNNING"}:
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_PORT_STATE_INVALID", 502, details={"state": state}
|
|
)
|
|
execution = self._execution_plan(grant)
|
|
external_job_id = execution.get("external_job_id")
|
|
if not isinstance(external_job_id, str) or not external_job_id.strip():
|
|
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_PLAN_INVALID", 403)
|
|
return {
|
|
"task_id": grant.task_id,
|
|
"state": state,
|
|
"external_job_id": external_job_id,
|
|
}
|
|
|
|
_BRIDGE_PRINCIPAL_CAPABILITIES = {
|
|
"acquireWriterLease": "writer.lease",
|
|
"commitFileGoverned": "governed.write.file",
|
|
"commitFilesGoverned": "governed.write.files",
|
|
"getGitOpsJob": "writer.lease",
|
|
"resumeGitOpsJob": "writer.lease",
|
|
"reconcileGitOpsJob": "writer.lease",
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class TaskAdmissionPrincipalProvider:
|
|
"""Recover a short-lived execution principal from durable admission state.
|
|
|
|
This provider is server-side only. It never accepts an Owner grant,
|
|
TaskPrincipal, or capability from caller JSON. Every use re-reads the
|
|
admitted grant, re-verifies its parent Owner epoch (including revocation),
|
|
re-reads the authoritative Task, and requires CLAIMED/RUNNING before a
|
|
mutating Bridge operation can continue.
|
|
"""
|
|
|
|
store: Any
|
|
authorization: Any
|
|
task_port: AgentTaskAdmissionPort
|
|
epoch_id: str
|
|
clock: Any = time.time
|
|
|
|
def readiness(self) -> Mapping[str, Any]:
|
|
try:
|
|
epoch = self.authorization.verify_delegation_epoch(self.epoch_id)
|
|
except Exception as exc:
|
|
return {
|
|
"ready": False,
|
|
"mode": "server-side-task-admission",
|
|
"error": str(exc) or type(exc).__name__,
|
|
}
|
|
return {
|
|
"ready": True,
|
|
"mode": "server-side-task-admission",
|
|
"epoch_id": epoch.epoch_id,
|
|
"repository": epoch.repository,
|
|
}
|
|
|
|
@staticmethod
|
|
def _request_value(
|
|
request: Mapping[str, Any], name: str, expected: object
|
|
) -> None:
|
|
if name in request and request[name] != expected:
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_REQUEST_BINDING_MISMATCH",
|
|
403,
|
|
details={"field": name},
|
|
)
|
|
|
|
def __call__(
|
|
self,
|
|
transport: Mapping[str, Any],
|
|
request: Mapping[str, Any],
|
|
) -> Mapping[str, Any]:
|
|
operation = str(transport.get("operation") or "")
|
|
capability = _BRIDGE_PRINCIPAL_CAPABILITIES.get(operation)
|
|
if capability is None:
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_BOOTSTRAP_ROUTE_REQUIRED", 403
|
|
)
|
|
task_id = request.get("task_id")
|
|
if not isinstance(task_id, str) or not task_id:
|
|
raise TaskAdmissionError("TASK_PRINCIPAL_TASK_ID_INVALID", 403)
|
|
|
|
grant = self.store.get_grant_by_task_id(task_id)
|
|
if grant is None:
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_AUTHORIZATION_REQUIRED", 403
|
|
)
|
|
if grant.parent_epoch_id is None:
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_PARENT_EPOCH_REQUIRED", 403
|
|
)
|
|
|
|
# Re-verify the exact parent on every use so revocation/expiry cannot be
|
|
# bypassed by a previously admitted Task.
|
|
epoch = self.authorization.verify_delegation_epoch(grant.parent_epoch_id)
|
|
if (
|
|
epoch.epoch_id != self.epoch_id
|
|
or epoch.repository != grant.repository
|
|
or epoch.trust_domain != grant.trust_domain
|
|
or not set(grant.capabilities).issubset(epoch.allowed_capabilities)
|
|
or not any(
|
|
grant.target_branch.startswith(prefix)
|
|
for prefix in epoch.allowed_branch_prefixes
|
|
)
|
|
):
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_PARENT_EPOCH_SCOPE_MISMATCH", 403
|
|
)
|
|
|
|
now = int(self.clock())
|
|
if not grant.issued_at <= now < grant.expires_at:
|
|
raise TaskAdmissionError("TASK_PRINCIPAL_GRANT_EXPIRED", 403)
|
|
if (
|
|
"task.principal.issue" not in grant.capabilities
|
|
or capability not in grant.capabilities
|
|
):
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_CAPABILITY_DENIED", 403
|
|
)
|
|
|
|
task, execution = self.task_port.verify_active_task(grant)
|
|
external_job_id = execution.get("external_job_id")
|
|
if not isinstance(external_job_id, str) or not external_job_id:
|
|
raise TaskAdmissionError(
|
|
"TASK_PRINCIPAL_EXECUTION_ID_INVALID", 503
|
|
)
|
|
|
|
self._request_value(request, "task_id", grant.task_id)
|
|
self._request_value(request, "job_id", external_job_id)
|
|
self._request_value(request, "repository", grant.repository)
|
|
self._request_value(request, "target_branch", grant.target_branch)
|
|
self._request_value(request, "generation", grant.generation)
|
|
if operation == "acquireWriterLease":
|
|
self._request_value(
|
|
request, "base_oid", grant.authority_base_oid
|
|
)
|
|
|
|
return {
|
|
"schema": "aios.task-principal-server-attestation.v1",
|
|
"task_id": grant.task_id,
|
|
"grant_id": grant.grant_id,
|
|
"parent_epoch_id": grant.parent_epoch_id,
|
|
"repository": grant.repository,
|
|
"target_branch": grant.target_branch,
|
|
"generation": grant.generation,
|
|
"job_id": external_job_id,
|
|
"capability": capability,
|
|
"task_state": str(task.get("state") or task.get("status") or ""),
|
|
"request_digest": sha256_json(dict(request)),
|
|
"verified_at": now,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class TaskAdmissionRuntimeBundle:
|
|
handlers: Mapping[str, Any]
|
|
principal_verifier: TaskAdmissionPrincipalProvider
|
|
service: Any | None = None
|
|
|
|
|
|
class TaskAdmissionHostError(RuntimeError):
|
|
"""Stable fail-closed host-composition error."""
|
|
|
|
|
|
class OfficialTaskHTTPError(RuntimeError):
|
|
def __init__(self, status: int, body: object | None = None) -> None:
|
|
super().__init__(f"AGENT_TASK_HTTP_{status}")
|
|
self.status = status
|
|
self.http_status = status
|
|
self.code = status
|
|
self.body = body
|
|
|
|
|
|
def _host_strict_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
value: dict[str, Any] = {}
|
|
for key, item in pairs:
|
|
if key in value:
|
|
raise TaskAdmissionHostError("DUPLICATE_JSON_FIELD")
|
|
value[key] = item
|
|
return value
|
|
|
|
|
|
def _host_read_json_object(path: Path) -> dict[str, Any]:
|
|
try:
|
|
value = json.loads(
|
|
path.read_text(encoding="utf-8"),
|
|
object_pairs_hook=_host_strict_object,
|
|
)
|
|
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
raise TaskAdmissionHostError("JSON_INPUT_INVALID") from exc
|
|
if not isinstance(value, dict):
|
|
raise TaskAdmissionHostError("JSON_OBJECT_REQUIRED")
|
|
return value
|
|
|
|
|
|
def _host_posix_metadata_enforced() -> bool:
|
|
"""Return whether POSIX ownership/mode bits are security-significant here.
|
|
|
|
The live AIOS authority/runtime is Linux, where mode/uid checks are a real
|
|
trust boundary. Windows development/test files expose synthesized POSIX
|
|
mode bits that do not represent ACL authority, so interpreting ``022``
|
|
there would reject every secure temporary directory without improving
|
|
live security.
|
|
"""
|
|
return os.name == "posix"
|
|
|
|
|
|
def _host_require_regular(path: Path, *, root_owned: bool) -> None:
|
|
try:
|
|
info = path.lstat()
|
|
except OSError as exc:
|
|
raise TaskAdmissionHostError("SECURE_FILE_MISSING") from exc
|
|
if path.is_symlink() or not stat.S_ISREG(info.st_mode):
|
|
raise TaskAdmissionHostError("SECURE_FILE_INVALID")
|
|
if _host_posix_metadata_enforced():
|
|
if stat.S_IMODE(info.st_mode) & 0o022:
|
|
raise TaskAdmissionHostError("SECURE_FILE_WRITABLE_BY_GROUP_OR_OTHER")
|
|
if root_owned and info.st_uid != 0:
|
|
raise TaskAdmissionHostError("SECURE_FILE_NOT_ROOT_OWNED")
|
|
|
|
|
|
def _host_require_directory(path: Path, *, root_owned: bool) -> None:
|
|
try:
|
|
info = path.lstat()
|
|
except OSError as exc:
|
|
raise TaskAdmissionHostError("SECURE_DIRECTORY_MISSING") from exc
|
|
if path.is_symlink() or not stat.S_ISDIR(info.st_mode):
|
|
raise TaskAdmissionHostError("SECURE_DIRECTORY_INVALID")
|
|
if _host_posix_metadata_enforced():
|
|
if stat.S_IMODE(info.st_mode) & 0o022:
|
|
raise TaskAdmissionHostError("SECURE_DIRECTORY_WRITABLE_BY_GROUP_OR_OTHER")
|
|
if root_owned and info.st_uid != 0:
|
|
raise TaskAdmissionHostError("SECURE_DIRECTORY_NOT_ROOT_OWNED")
|
|
|
|
|
|
def load_owner_approval_verifier(module_path: Path) -> Any:
|
|
"""Load the already-installed trusted public Owner verifier."""
|
|
_host_require_regular(module_path, root_owned=True)
|
|
spec = importlib.util.spec_from_file_location(
|
|
"aios_live_owner_approval_verifier",
|
|
module_path,
|
|
)
|
|
if spec is None or spec.loader is None:
|
|
raise TaskAdmissionHostError("OWNER_VERIFIER_IMPORT_INVALID")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
cls = getattr(module, "GrantRootOwnerApprovalVerifier", None)
|
|
if cls is None:
|
|
raise TaskAdmissionHostError("OWNER_VERIFIER_CLASS_MISSING")
|
|
verifier = cls()
|
|
if not callable(getattr(verifier, "verify", None)):
|
|
raise TaskAdmissionHostError("OWNER_VERIFIER_INVALID")
|
|
return verifier
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SignedEpochDirectoryProvider:
|
|
"""Re-verify the exact signed Owner epoch on every admission use."""
|
|
|
|
root: Path
|
|
verifier: Any
|
|
revocation_file: Path
|
|
expected_caller: Mapping[str, Any]
|
|
require_root_owner: bool = True
|
|
clock: Any = time.time
|
|
|
|
def _revoked_ids(self) -> set[str]:
|
|
_host_require_regular(
|
|
self.revocation_file,
|
|
root_owned=self.require_root_owner,
|
|
)
|
|
value = _host_read_json_object(self.revocation_file)
|
|
if (
|
|
set(value) != {"schema", "revoked_epoch_ids"}
|
|
or value.get("schema") != "aios.owner-delegation-revocations.v1"
|
|
or not isinstance(value.get("revoked_epoch_ids"), list)
|
|
or not all(
|
|
isinstance(item, str) and item
|
|
for item in value["revoked_epoch_ids"]
|
|
)
|
|
):
|
|
raise TaskAdmissionHostError(
|
|
"OWNER_EPOCH_REVOCATION_REGISTRY_INVALID"
|
|
)
|
|
return set(value["revoked_epoch_ids"])
|
|
|
|
def __call__(
|
|
self,
|
|
*,
|
|
epoch_id: str,
|
|
caller: Mapping[str, Any],
|
|
) -> OwnerDelegationEpoch:
|
|
if dict(caller) != dict(self.expected_caller):
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_CALLER_BINDING_MISMATCH",
|
|
403,
|
|
)
|
|
_host_require_directory(
|
|
self.root,
|
|
root_owned=self.require_root_owner,
|
|
)
|
|
canonical = self.root / "owner-delegation-epoch.canonical.json"
|
|
signature = self.root / "owner-delegation-epoch.sig"
|
|
key_id_file = self.root / "owner-delegation-epoch.key-id"
|
|
for path in (canonical, signature, key_id_file):
|
|
_host_require_regular(
|
|
path,
|
|
root_owned=self.require_root_owner,
|
|
)
|
|
|
|
payload = canonical.read_bytes()
|
|
value = _host_read_json_object(canonical)
|
|
if canonical_json_bytes(value) != payload:
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EPOCH_CANONICAL_MISMATCH",
|
|
403,
|
|
)
|
|
schema = value.get("schema")
|
|
if schema not in {SCHEMA_EPOCH, SCHEMA_EPOCH_V2}:
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EPOCH_SCHEMA_INVALID",
|
|
403,
|
|
)
|
|
expected_fields = _EPOCH_V1_FIELDS if schema == SCHEMA_EPOCH else _EPOCH_V2_FIELDS
|
|
if set(value) != expected_fields:
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_FIELDS_INVALID", 403)
|
|
if value.get("epoch_id") != epoch_id:
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EPOCH_ID_MISMATCH",
|
|
403,
|
|
)
|
|
if value.get("owner_identity") != OWNER_SIGNER_IDENTITY:
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EPOCH_OWNER_IDENTITY_INVALID",
|
|
403,
|
|
)
|
|
if value.get("revocation_epoch") != 0:
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REVOKED", 403)
|
|
if epoch_id in self._revoked_ids():
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REVOKED", 403)
|
|
|
|
# v2 is a producer-authored envelope. Validate its self-digest and
|
|
# signature binding before the cryptographic verifier sees the exact
|
|
# bytes from disk. The digest is over the payload with only the digest
|
|
# member removed; no schema translation is permitted.
|
|
if schema == SCHEMA_EPOCH_V2:
|
|
if (
|
|
value.get("status") != "ACTIVE"
|
|
or not isinstance(value.get("business_goal"), str)
|
|
or not value["business_goal"]
|
|
or not isinstance(value.get("production_scope"), str)
|
|
or not value["production_scope"]
|
|
or not isinstance(value.get("destructive_policy"), str)
|
|
or value["destructive_policy"] not in {"forbidden", "rollback-only"}
|
|
or value.get("signature_namespace") != OWNER_NAMESPACE
|
|
or not isinstance(value.get("signature_key_id"), str)
|
|
or not _SSH_FINGERPRINT_RE.fullmatch(value["signature_key_id"])
|
|
or not isinstance(value.get("canonical_payload_digest"), str)
|
|
or not re.fullmatch(r"[0-9a-f]{64}", value["canonical_payload_digest"])
|
|
):
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_V2_BINDING_INVALID", 403)
|
|
digest_value = dict(value)
|
|
supplied_digest = digest_value.pop("canonical_payload_digest", None)
|
|
if sha256_json(digest_value) != supplied_digest:
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_PAYLOAD_DIGEST_INVALID", 403)
|
|
|
|
key_id = key_id_file.read_text(encoding="utf-8").strip()
|
|
if schema == SCHEMA_EPOCH_V2 and key_id != value["signature_key_id"]:
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_KEY_BINDING_INVALID", 403)
|
|
armored = signature.read_text(encoding="utf-8")
|
|
if self.verifier.verify(
|
|
namespace=OWNER_NAMESPACE,
|
|
payload=payload,
|
|
signature=armored,
|
|
key_id=key_id,
|
|
) is not True:
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EPOCH_SIGNATURE_INVALID",
|
|
403,
|
|
)
|
|
|
|
now = int(self.clock())
|
|
not_before = value.get("not_before")
|
|
expires_at = value.get("expires_at")
|
|
if (
|
|
not isinstance(value.get("epoch_id"), str)
|
|
or not value["epoch_id"]
|
|
or not isinstance(value.get("owner_identity"), str)
|
|
or not value["owner_identity"]
|
|
or not isinstance(value.get("trust_domain"), str)
|
|
or not value["trust_domain"]
|
|
or not isinstance(value.get("repository"), str)
|
|
or not value["repository"]
|
|
or not isinstance(value.get("risk_ceiling"), str)
|
|
or value["risk_ceiling"] not in {"low", "medium", "engineering", "high"}
|
|
or not isinstance(value.get("issued_at"), int)
|
|
or isinstance(value.get("issued_at"), bool)
|
|
or not isinstance(value.get("revocation_epoch"), int)
|
|
or isinstance(value.get("revocation_epoch"), bool)
|
|
or not isinstance(value.get("policy_digest"), str)
|
|
or not value["policy_digest"]
|
|
or
|
|
not isinstance(not_before, int)
|
|
or isinstance(not_before, bool)
|
|
or not isinstance(expires_at, int)
|
|
or isinstance(expires_at, bool)
|
|
or not not_before <= now < expires_at
|
|
):
|
|
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_EXPIRED", 403)
|
|
|
|
branches = value.get("allowed_branch_prefixes")
|
|
capabilities = value.get("allowed_capabilities")
|
|
files = value.get("allowed_file_prefixes")
|
|
if (
|
|
not isinstance(branches, list)
|
|
or not branches
|
|
or not all(isinstance(item, str) and item for item in branches)
|
|
or not isinstance(capabilities, list)
|
|
or not capabilities
|
|
or not all(
|
|
isinstance(item, str) and item
|
|
for item in capabilities
|
|
)
|
|
or not isinstance(files, list)
|
|
or not files
|
|
or not all(isinstance(item, str) and item for item in files)
|
|
):
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EPOCH_SCOPE_INVALID",
|
|
403,
|
|
)
|
|
|
|
return OwnerDelegationEpoch(
|
|
epoch_id=str(value["epoch_id"]),
|
|
owner_identity=str(value["owner_identity"]),
|
|
trust_domain=str(value["trust_domain"]),
|
|
repository=str(value["repository"]),
|
|
allowed_branch_prefixes=tuple(branches),
|
|
allowed_capabilities=frozenset(capabilities),
|
|
allowed_file_prefixes=tuple(files),
|
|
risk_ceiling=str(value["risk_ceiling"]),
|
|
issued_at=int(value["issued_at"]),
|
|
not_before=not_before,
|
|
expires_at=expires_at,
|
|
revocation_epoch=int(value["revocation_epoch"]),
|
|
policy_digest=str(value["policy_digest"]),
|
|
schema=str(value["schema"]),
|
|
production_scope=(
|
|
str(value["production_scope"])
|
|
if schema == SCHEMA_EPOCH_V2 else None
|
|
),
|
|
destructive_policy=(
|
|
str(value["destructive_policy"])
|
|
if schema == SCHEMA_EPOCH_V2 else None
|
|
),
|
|
status=str(value.get("status", "")),
|
|
signature_namespace=str(value.get("signature_namespace", "")),
|
|
signature_key_id=str(value.get("signature_key_id", "")),
|
|
canonical_payload_digest=str(value.get("canonical_payload_digest", "")),
|
|
canonical_payload=payload,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ExecutionPlanDirectoryProvider:
|
|
"""Resolve a trusted execution plan by its canonical SHA256 digest."""
|
|
|
|
root: Path
|
|
require_root_owner: bool = True
|
|
|
|
def __call__(self, grant: TaskAdmissionGrant) -> Mapping[str, Any]:
|
|
digest = str(grant.execution_digest)
|
|
if (
|
|
len(digest) != 64
|
|
or any(ch not in "0123456789abcdef" for ch in digest)
|
|
):
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EXECUTION_PLAN_DIGEST_INVALID",
|
|
403,
|
|
)
|
|
_host_require_directory(
|
|
self.root,
|
|
root_owned=self.require_root_owner,
|
|
)
|
|
path = self.root / (digest + ".json")
|
|
_host_require_regular(
|
|
path,
|
|
root_owned=self.require_root_owner,
|
|
)
|
|
value = _host_read_json_object(path)
|
|
if hashlib.sha256(canonical_json_bytes(value)).hexdigest() != digest:
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EXECUTION_PLAN_DIGEST_MISMATCH",
|
|
403,
|
|
)
|
|
expected = {
|
|
"task_id": grant.task_id,
|
|
"repository": grant.repository,
|
|
"target_branch": grant.target_branch,
|
|
"generation": grant.generation,
|
|
}
|
|
if any(value.get(key) != item for key, item in expected.items()):
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EXECUTION_PLAN_CONFLICT",
|
|
409,
|
|
)
|
|
external_job_id = value.get("external_job_id")
|
|
if not isinstance(external_job_id, str) or not external_job_id.strip():
|
|
raise TaskAdmissionError(
|
|
"TASK_ADMISSION_EXECUTION_PLAN_INVALID",
|
|
403,
|
|
)
|
|
return {**value, "execution_digest": digest}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class LocalAgentTaskHTTPTransport:
|
|
"""Server-side localhost Task API transport; token never enters caller JSON."""
|
|
|
|
base_url: str
|
|
token_file: Path
|
|
timeout_seconds: float = 5.0
|
|
require_root_owner: bool = True
|
|
|
|
def _token(self) -> str:
|
|
_host_require_regular(
|
|
self.token_file,
|
|
root_owned=self.require_root_owner,
|
|
)
|
|
token = self.token_file.read_text(encoding="utf-8").strip()
|
|
if len(token) < 16 or any(ch.isspace() for ch in token):
|
|
raise TaskAdmissionHostError("AGENT_TASK_CONTROL_TOKEN_INVALID")
|
|
return token
|
|
|
|
def request(
|
|
self,
|
|
method: str,
|
|
path: str,
|
|
body: Mapping[str, Any] | None = None,
|
|
) -> Mapping[str, Any]:
|
|
if method not in {"GET", "POST"}:
|
|
raise TaskAdmissionHostError("AGENT_TASK_HTTP_METHOD_INVALID")
|
|
if not path.startswith("/v1/tasks"):
|
|
raise TaskAdmissionHostError("AGENT_TASK_HTTP_PATH_INVALID")
|
|
raw = None if body is None else canonical_json_bytes(dict(body))
|
|
request = urllib.request.Request(
|
|
self.base_url.rstrip("/") + path,
|
|
data=raw,
|
|
method=method,
|
|
headers={
|
|
"Accept": "application/json",
|
|
"Authorization": "Bearer " + self._token(),
|
|
**(
|
|
{"Content-Type": "application/json"}
|
|
if raw is not None
|
|
else {}
|
|
),
|
|
},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(
|
|
request,
|
|
timeout=self.timeout_seconds,
|
|
) as response:
|
|
value = json.loads(
|
|
response.read().decode("utf-8"),
|
|
object_pairs_hook=_host_strict_object,
|
|
)
|
|
except urllib.error.HTTPError as exc:
|
|
body_value: object | None = None
|
|
try:
|
|
body_value = json.loads(exc.read().decode("utf-8"))
|
|
except Exception:
|
|
body_value = None
|
|
raise OfficialTaskHTTPError(
|
|
exc.code,
|
|
body_value,
|
|
) from exc
|
|
if not isinstance(value, Mapping):
|
|
raise TaskAdmissionHostError("AGENT_TASK_HTTP_RESPONSE_INVALID")
|
|
return dict(value)
|
|
|
|
|
|
def task_admission_policy_from_epoch(
|
|
epoch: OwnerDelegationEpoch,
|
|
) -> Any:
|
|
"""Build a local ceiling exactly narrowed to the signed epoch."""
|
|
from aios_task_admission_bootstrap import TaskAdmissionPolicy
|
|
|
|
return TaskAdmissionPolicy(
|
|
repository=epoch.repository,
|
|
trust_domain=epoch.trust_domain,
|
|
branch_prefix="agent/",
|
|
allowed_capabilities=frozenset(epoch.allowed_capabilities),
|
|
max_file_count=min(
|
|
64,
|
|
max(1, len(epoch.allowed_file_prefixes)),
|
|
),
|
|
max_task_ttl_seconds=min(
|
|
3600,
|
|
max(60, epoch.expires_at - epoch.not_before),
|
|
),
|
|
max_envelope_ttl_seconds=900,
|
|
max_risk=epoch.risk_ceiling,
|
|
policy_digest=epoch.policy_digest,
|
|
)
|
|
|
|
|
|
def _build_task_admission_runtime(
|
|
*,
|
|
caller_service: str,
|
|
epoch_root: Path,
|
|
epoch_id: str,
|
|
revocation_file: Path,
|
|
execution_plan_dir: Path,
|
|
state_db: Path,
|
|
task_api_url: str,
|
|
task_control_token_file: Path,
|
|
owner_verifier_module: Path,
|
|
post_admission: Callable[[TaskAdmissionGrant, Mapping[str, Any]], Any] | None = None,
|
|
) -> TaskAdmissionRuntimeBundle:
|
|
"""Compose admission handlers plus automatic execution-principal recovery."""
|
|
from aios_task_admission_bootstrap import (
|
|
TaskAdmissionBootstrapService,
|
|
TaskAdmissionStore,
|
|
)
|
|
from aios_task_admission_bridge_contract import TaskAdmissionBridgeContract
|
|
|
|
verifier = load_owner_approval_verifier(owner_verifier_module)
|
|
caller = {
|
|
"service": caller_service,
|
|
"trust_domain": "aios.owner",
|
|
}
|
|
provider = SignedEpochDirectoryProvider(
|
|
root=epoch_root,
|
|
verifier=verifier,
|
|
revocation_file=revocation_file,
|
|
expected_caller=caller,
|
|
require_root_owner=True,
|
|
)
|
|
epoch = provider(epoch_id=epoch_id, caller=caller)
|
|
authorization = BoundOwnerAuthorizationBackend(
|
|
authenticated_caller=caller,
|
|
verified_epoch_provider=provider,
|
|
signature_verifier=verifier,
|
|
)
|
|
transport = LocalAgentTaskHTTPTransport(
|
|
base_url=task_api_url,
|
|
token_file=task_control_token_file,
|
|
require_root_owner=True,
|
|
)
|
|
task_port = AgentTaskAdmissionPort(
|
|
transport,
|
|
execution_plan_provider=ExecutionPlanDirectoryProvider(
|
|
root=execution_plan_dir,
|
|
require_root_owner=True,
|
|
),
|
|
)
|
|
store = TaskAdmissionStore(state_db)
|
|
service = TaskAdmissionBootstrapService(
|
|
store=store,
|
|
authorization=authorization,
|
|
task_port=task_port,
|
|
policy=task_admission_policy_from_epoch(epoch),
|
|
post_admission=post_admission,
|
|
)
|
|
return TaskAdmissionRuntimeBundle(
|
|
handlers=TaskAdmissionBridgeContract(service).handlers(),
|
|
principal_verifier=TaskAdmissionPrincipalProvider(
|
|
store=store,
|
|
authorization=authorization,
|
|
task_port=task_port,
|
|
epoch_id=epoch.epoch_id,
|
|
),
|
|
service=service,
|
|
)
|
|
|
|
|
|
def _build_task_admission_handlers(**kwargs: Any) -> Mapping[str, Any]:
|
|
"""Compatibility wrapper for callers that only need admission routes."""
|
|
return _build_task_admission_runtime(**kwargs).handlers
|
|
|
|
|
|
def build_task_admission_runtime_from_activation_file(
|
|
activation_file: Path,
|
|
*,
|
|
caller_service: str = "aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission: Callable[[TaskAdmissionGrant, Mapping[str, Any]], Any] | None = None,
|
|
) -> TaskAdmissionRuntimeBundle:
|
|
"""Load admission handlers plus server-side principal recovery."""
|
|
_host_require_regular(activation_file, root_owned=True)
|
|
value = _host_read_json_object(activation_file)
|
|
required = {
|
|
"schema",
|
|
"epoch_root",
|
|
"epoch_id",
|
|
"revocation_file",
|
|
"execution_plan_dir",
|
|
"state_db",
|
|
"task_api_url",
|
|
"task_control_token_file",
|
|
"owner_verifier_module",
|
|
}
|
|
if set(value) != required or value.get("schema") != "aios.task-admission-host-activation.v1":
|
|
raise TaskAdmissionHostError("TASK_ADMISSION_ACTIVATION_FILE_INVALID")
|
|
|
|
path_fields = (
|
|
"epoch_root",
|
|
"revocation_file",
|
|
"execution_plan_dir",
|
|
"state_db",
|
|
"task_control_token_file",
|
|
"owner_verifier_module",
|
|
)
|
|
paths: dict[str, Path] = {}
|
|
for key in path_fields:
|
|
raw = value.get(key)
|
|
if not isinstance(raw, str) or not raw.startswith("/"):
|
|
raise TaskAdmissionHostError("TASK_ADMISSION_ACTIVATION_PATH_INVALID")
|
|
path = Path(raw)
|
|
if ".." in path.parts:
|
|
raise TaskAdmissionHostError("TASK_ADMISSION_ACTIVATION_PATH_INVALID")
|
|
paths[key] = path
|
|
|
|
epoch_id = value.get("epoch_id")
|
|
task_api_url = value.get("task_api_url")
|
|
if not isinstance(epoch_id, str) or not epoch_id:
|
|
raise TaskAdmissionHostError("TASK_ADMISSION_ACTIVATION_EPOCH_INVALID")
|
|
if task_api_url != "http://127.0.0.1:18940":
|
|
raise TaskAdmissionHostError("TASK_ADMISSION_ACTIVATION_TASK_API_INVALID")
|
|
|
|
return _build_task_admission_runtime(
|
|
caller_service=caller_service,
|
|
epoch_root=paths["epoch_root"],
|
|
epoch_id=epoch_id,
|
|
revocation_file=paths["revocation_file"],
|
|
execution_plan_dir=paths["execution_plan_dir"],
|
|
state_db=paths["state_db"],
|
|
task_api_url=task_api_url,
|
|
task_control_token_file=paths["task_control_token_file"],
|
|
owner_verifier_module=paths["owner_verifier_module"],
|
|
post_admission=post_admission,
|
|
)
|
|
|
|
|
|
def build_task_admission_handlers_from_activation_file(
|
|
activation_file: Path,
|
|
*,
|
|
caller_service: str = "aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission: Callable[[TaskAdmissionGrant, Mapping[str, Any]], Any] | None = None,
|
|
) -> Mapping[str, Any]:
|
|
return build_task_admission_runtime_from_activation_file(
|
|
activation_file,
|
|
caller_service=caller_service,
|
|
post_admission=post_admission,
|
|
).handlers
|
|
|
|
|
|
def build_task_admission_runtime_from_environment(
|
|
*,
|
|
caller_service: str = "aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission: Callable[[TaskAdmissionGrant, Mapping[str, Any]], Any] | None = None,
|
|
) -> TaskAdmissionRuntimeBundle:
|
|
"""Compose admission plus automatic TaskPrincipal recovery from trusted environment."""
|
|
required = (
|
|
"AIOS_TASK_ADMISSION_EPOCH_ROOT",
|
|
"AIOS_TASK_ADMISSION_EPOCH_ID",
|
|
"AIOS_TASK_ADMISSION_REVOCATION_FILE",
|
|
"AIOS_TASK_ADMISSION_EXECUTION_PLAN_DIR",
|
|
)
|
|
missing = [
|
|
key
|
|
for key in required
|
|
if not os.environ.get(key, "").strip()
|
|
]
|
|
if missing:
|
|
raise TaskAdmissionHostError(
|
|
"TASK_ADMISSION_HOST_ENV_MISSING:"
|
|
+ ",".join(sorted(missing))
|
|
)
|
|
return _build_task_admission_runtime(
|
|
caller_service=caller_service,
|
|
epoch_root=Path(os.environ["AIOS_TASK_ADMISSION_EPOCH_ROOT"]),
|
|
epoch_id=os.environ["AIOS_TASK_ADMISSION_EPOCH_ID"],
|
|
revocation_file=Path(os.environ["AIOS_TASK_ADMISSION_REVOCATION_FILE"]),
|
|
execution_plan_dir=Path(os.environ["AIOS_TASK_ADMISSION_EXECUTION_PLAN_DIR"]),
|
|
state_db=Path(os.environ.get(
|
|
"AIOS_TASK_ADMISSION_STATE_DB",
|
|
"/var/lib/aios-gitops/bridge-v2-staging/task-admission.sqlite3",
|
|
)),
|
|
task_api_url=os.environ.get(
|
|
"AIOS_AGENT_TASK_API_URL",
|
|
"http://127.0.0.1:18940",
|
|
),
|
|
task_control_token_file=Path(os.environ.get(
|
|
"AIOS_AGENT_TASK_CONTROL_TOKEN_FILE",
|
|
"/etc/aios-gitops/agent-task-control.token",
|
|
)),
|
|
owner_verifier_module=Path(os.environ.get(
|
|
"AIOS_OWNER_APPROVAL_VERIFIER_MODULE",
|
|
"/opt/aios/agent-task-api/"
|
|
"914eb324f657a695e2635f80cf53202eba636eb350630b12883e2741ce67e535"
|
|
"-p0-verification/runtime/app/aios_owner_approval_verifier.py",
|
|
)),
|
|
post_admission=post_admission,
|
|
)
|
|
|
|
def build_task_admission_handlers_from_environment(
|
|
*,
|
|
caller_service: str = "aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission: Callable[[TaskAdmissionGrant, Mapping[str, Any]], Any] | None = None,
|
|
) -> Mapping[str, Any]:
|
|
return build_task_admission_runtime_from_environment(
|
|
caller_service=caller_service,
|
|
post_admission=post_admission,
|
|
).handlers
|
|
|
|
|
|
# ---- producer phase core v12r4 (hash-pinned sibling source) ----
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import stat
|
|
from typing import Any, Mapping
|
|
|
|
TARGET_TASK_ID="TASK-SINGLEHOST-LINEAGE-SUPERSESSION-P0-20260905"
|
|
TARGET_REPOSITORY="aios/aios"
|
|
TARGET_BASE_OID="91f104ef62a3b688a9259b96e2ca247e0363b564"
|
|
TARGET_BRANCH="agent/singlehost-lineage-supersession-p0-20260905"
|
|
TARGET_GENERATION=1
|
|
PLAN_SCHEMA_V3="aios.task-admission-execution-plan.v3"
|
|
PLAN_FIELDS_V3=frozenset({
|
|
"schema","task_id","repository","authority_base_oid","target_branch","generation",
|
|
"file_allowlist","file_sha256","capabilities","risk",
|
|
})
|
|
_SHA256=re.compile(r"^[0-9a-f]{64}$")
|
|
_OID=re.compile(r"^[0-9a-f]{40}$")
|
|
_JOB_ID=re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$")
|
|
_TASK_ID=re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
|
|
_TASK_BRANCH=re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
|
|
|
|
|
|
class ProducerReceiptError(RuntimeError):
|
|
def __init__(self,code:str):
|
|
super().__init__(code)
|
|
self.code=code
|
|
|
|
|
|
def fail(code:str):
|
|
raise ProducerReceiptError(code)
|
|
|
|
|
|
def canonical_json_bytes(value:object)->bytes:
|
|
return json.dumps(
|
|
value,ensure_ascii=False,sort_keys=True,separators=(",",":"),allow_nan=False
|
|
).encode("utf-8")
|
|
|
|
|
|
def _pairs_no_duplicates(pairs):
|
|
out={}
|
|
for key,value in pairs:
|
|
if key in out:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_DUPLICATE_KEY")
|
|
out[key]=value
|
|
return out
|
|
|
|
|
|
def strict_json_object(raw:bytes)->dict[str,Any]:
|
|
try:
|
|
value=json.loads(raw.decode("utf-8"),object_pairs_hook=_pairs_no_duplicates)
|
|
except ProducerReceiptError:
|
|
raise
|
|
except Exception:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_JSON_INVALID")
|
|
if not isinstance(value,dict):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_INVALID")
|
|
if canonical_json_bytes(value)!=raw:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_NONCANONICAL")
|
|
return value
|
|
|
|
|
|
def _gv(grant:object,name:str)->Any:
|
|
return grant.get(name) if isinstance(grant,Mapping) else getattr(grant,name,None)
|
|
|
|
|
|
def require_target_grant(grant:object)->None:
|
|
expected={
|
|
"task_id":TARGET_TASK_ID,
|
|
"repository":TARGET_REPOSITORY,
|
|
"authority_base_oid":TARGET_BASE_OID,
|
|
"target_branch":TARGET_BRANCH,
|
|
"generation":TARGET_GENERATION,
|
|
}
|
|
for name,value in expected.items():
|
|
if _gv(grant,name)!=value:
|
|
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_SCOPE_INVALID")
|
|
digest=_gv(grant,"execution_digest")
|
|
if not isinstance(digest,str) or not _SHA256.fullmatch(digest):
|
|
fail("TASK_ADMISSION_EXECUTION_DIGEST_INVALID")
|
|
|
|
|
|
def require_reservable_grant(grant:object)->None:
|
|
"""Validate the authenticated, persisted grant accepted by the producer.
|
|
|
|
The historical target keeps its exact tuple. Every other target is driven
|
|
by the grant persisted by TaskAdmissionBootstrap; no Mission metadata or
|
|
caller-supplied execution identity is consulted here.
|
|
"""
|
|
task_id=_gv(grant,"task_id")
|
|
if task_id==TARGET_TASK_ID:
|
|
require_target_grant(grant)
|
|
return
|
|
if not isinstance(task_id,str) or not _TASK_ID.fullmatch(task_id):
|
|
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_TASK_INVALID")
|
|
if _gv(grant,"repository")!=TARGET_REPOSITORY:
|
|
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_REPOSITORY_INVALID")
|
|
base=_gv(grant,"authority_base_oid")
|
|
if not isinstance(base,str) or not _OID.fullmatch(base):
|
|
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_BASE_INVALID")
|
|
branch=_gv(grant,"target_branch")
|
|
if not isinstance(branch,str) or not _TASK_BRANCH.fullmatch(branch):
|
|
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_BRANCH_INVALID")
|
|
generation=_gv(grant,"generation")
|
|
if isinstance(generation,bool) or not isinstance(generation,int) or generation<1:
|
|
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_GENERATION_INVALID")
|
|
digest=_gv(grant,"execution_digest")
|
|
if not isinstance(digest,str) or not _SHA256.fullmatch(digest):
|
|
fail("TASK_ADMISSION_EXECUTION_DIGEST_INVALID")
|
|
grant_id=_gv(grant,"grant_id")
|
|
if not isinstance(grant_id,str) or not grant_id:
|
|
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_GRANT_INVALID")
|
|
|
|
|
|
def _safe_path(value:object)->str:
|
|
if not isinstance(value,str) or not value or "\x00" in value:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_PATH_INVALID")
|
|
p=Path(value)
|
|
if p.is_absolute() or ".." in p.parts or "." in p.parts or p.as_posix()!=value:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_PATH_INVALID")
|
|
if value==".git" or value.startswith(".git/"):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_PATH_INVALID")
|
|
return value
|
|
|
|
|
|
def _unique_strings(value:object,code:str)->list[str]:
|
|
if (
|
|
not isinstance(value,list) or not value
|
|
or not all(isinstance(x,str) and x for x in value)
|
|
or len(set(value))!=len(value)
|
|
):
|
|
fail(code)
|
|
return list(value)
|
|
|
|
|
|
def validate_v3_plan(*,grant:object,raw:bytes)->dict[str,Any]:
|
|
require_reservable_grant(grant)
|
|
plan=strict_json_object(raw)
|
|
if set(plan)!=PLAN_FIELDS_V3:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_FIELDS_INVALID")
|
|
if plan["schema"]!=PLAN_SCHEMA_V3:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_SCHEMA_INVALID")
|
|
fields=(
|
|
("task_id","TASK_ADMISSION_EXECUTION_PLAN_TASK_MISMATCH"),
|
|
("repository","TASK_ADMISSION_EXECUTION_PLAN_REPOSITORY_MISMATCH"),
|
|
("authority_base_oid","TASK_ADMISSION_EXECUTION_PLAN_BASE_MISMATCH"),
|
|
("target_branch","TASK_ADMISSION_EXECUTION_PLAN_BRANCH_MISMATCH"),
|
|
("generation","TASK_ADMISSION_EXECUTION_PLAN_GENERATION_MISMATCH"),
|
|
)
|
|
for field,code in fields:
|
|
if plan[field]!=_gv(grant,field):
|
|
fail(code)
|
|
if not _OID.fullmatch(str(plan["authority_base_oid"])):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_BASE_INVALID")
|
|
|
|
allow=[_safe_path(x) for x in _unique_strings(
|
|
plan["file_allowlist"],"TASK_ADMISSION_EXECUTION_PLAN_ALLOWLIST_INVALID"
|
|
)]
|
|
if allow!=list(_gv(grant,"file_allowlist") or ()):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_ALLOWLIST_MISMATCH")
|
|
hashes=plan["file_sha256"]
|
|
if not isinstance(hashes,dict) or set(hashes)!=set(allow):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_HASH_SET_MISMATCH")
|
|
if any(not isinstance(v,str) or not _SHA256.fullmatch(v) for v in hashes.values()):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_HASH_INVALID")
|
|
|
|
caps=_unique_strings(
|
|
plan["capabilities"],"TASK_ADMISSION_EXECUTION_PLAN_CAPABILITIES_INVALID"
|
|
)
|
|
if caps!=list(_gv(grant,"capabilities") or ()):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_CAPABILITIES_MISMATCH")
|
|
if plan["risk"]!=_gv(grant,"risk"):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_RISK_MISMATCH")
|
|
if hashlib.sha256(raw).hexdigest()!=_gv(grant,"execution_digest"):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_DIGEST_MISMATCH")
|
|
return plan
|
|
|
|
|
|
def load_v3_plan(root:Path,grant:object)->dict[str,Any]:
|
|
require_reservable_grant(grant)
|
|
try:
|
|
rst=root.lstat()
|
|
except OSError:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_UNAVAILABLE")
|
|
if (
|
|
stat.S_ISLNK(rst.st_mode) or not stat.S_ISDIR(rst.st_mode)
|
|
or (
|
|
_host_posix_metadata_enforced()
|
|
and (rst.st_uid!=0 or rst.st_mode&0o022)
|
|
)
|
|
):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_DIRECTORY_INVALID")
|
|
path=root/(str(_gv(grant,"execution_digest"))+".json")
|
|
try:
|
|
st=path.lstat()
|
|
except OSError:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_UNAVAILABLE")
|
|
if (
|
|
stat.S_ISLNK(st.st_mode) or not stat.S_ISREG(st.st_mode)
|
|
or (
|
|
_host_posix_metadata_enforced()
|
|
and (st.st_uid!=0 or st.st_mode&0o022)
|
|
)
|
|
):
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_INVALID")
|
|
try:
|
|
raw=path.read_bytes()
|
|
except OSError:
|
|
fail("TASK_ADMISSION_EXECUTION_PLAN_UNAVAILABLE")
|
|
return validate_v3_plan(grant=grant,raw=raw)
|
|
|
|
|
|
def reservation_idempotency_key(grant:object)->str:
|
|
require_reservable_grant(grant)
|
|
material=(
|
|
str(_gv(grant,"grant_id"))+"\0"+str(_gv(grant,"execution_digest"))
|
|
).encode("utf-8")
|
|
return "task-admission-execution-reservation-"+hashlib.sha256(material).hexdigest()[:32]
|
|
|
|
|
|
def build_reservation_request(
|
|
grant:object,*,expected_branch_oid:str|None=None,
|
|
)->dict[str,Any]:
|
|
require_reservable_grant(grant)
|
|
request={
|
|
"task_id":str(_gv(grant,"task_id")),
|
|
"repository":str(_gv(grant,"repository")),
|
|
"base_oid":str(_gv(grant,"authority_base_oid")),
|
|
"target_branch":str(_gv(grant,"target_branch")),
|
|
"generation":int(_gv(grant,"generation")),
|
|
"risk":str(_gv(grant,"risk")),
|
|
"description":"Reserve one inert durable GitOps Job identity after exact Owner TaskAdmission authorization; no Agent Task claim or execution.",
|
|
"idempotency_key":reservation_idempotency_key(grant),
|
|
}
|
|
if expected_branch_oid is not None:
|
|
request["expected_branch_oid"]=expected_branch_oid
|
|
return request
|
|
|
|
|
|
def _mapping(value:object,code:str)->Mapping[str,Any]:
|
|
if not isinstance(value,Mapping):
|
|
fail(code)
|
|
return value
|
|
|
|
|
|
def validate_bootstrap_response(
|
|
*,
|
|
grant:object,
|
|
request:Mapping[str,Any],
|
|
response:object,
|
|
expected_side_effect_state:str,
|
|
expected_branch_mode:str="CONTINUE_EXISTING",
|
|
)->str:
|
|
require_reservable_grant(grant)
|
|
result=_mapping(response,"TASK_ADMISSION_EXECUTION_RESERVATION_RESPONSE_INVALID")
|
|
job_id=result.get("job_id")
|
|
if not isinstance(job_id,str) or not _JOB_ID.fullmatch(job_id):
|
|
fail("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID")
|
|
expected={
|
|
"task_id":_gv(grant,"task_id"),
|
|
"job_id":job_id,
|
|
"repository":_gv(grant,"repository"),
|
|
"base_oid":_gv(grant,"authority_base_oid"),
|
|
"expected_branch_oid":request.get("expected_branch_oid",_gv(grant,"authority_base_oid")),
|
|
"target_branch":_gv(grant,"target_branch"),
|
|
"branch_mode":expected_branch_mode,
|
|
"generation":_gv(grant,"generation"),
|
|
"state":"BOUND",
|
|
}
|
|
for key,value in expected.items():
|
|
if result.get(key)!=value:
|
|
fail("TASK_ADMISSION_EXECUTION_RESERVATION_BINDING_CONFLICT")
|
|
|
|
receipt=_mapping(
|
|
result.get("reservation_receipt"),
|
|
"TASK_ADMISSION_EXECUTION_RESERVATION_RECEIPT_MISSING",
|
|
)
|
|
expected_keys={
|
|
"job_id","state","side_effect_state",
|
|
"producer_idempotency_key","request_idempotency_key",
|
|
}
|
|
if set(receipt)!=expected_keys:
|
|
fail("TASK_ADMISSION_EXECUTION_RESERVATION_RECEIPT_FIELDS_INVALID")
|
|
producer_key=receipt.get("producer_idempotency_key")
|
|
if (
|
|
receipt.get("job_id")!=job_id
|
|
or receipt.get("state")!="REQUESTED"
|
|
or receipt.get("side_effect_state")!=expected_side_effect_state
|
|
or not isinstance(producer_key,str)
|
|
or not _SHA256.fullmatch(producer_key)
|
|
or job_id!="job-"+producer_key[:32]
|
|
or receipt.get("request_idempotency_key")!=request.get("idempotency_key")
|
|
):
|
|
fail("TASK_ADMISSION_EXECUTION_RESERVATION_RECEIPT_CONFLICT")
|
|
return job_id
|
|
|
|
|
|
def _approved_binding_fingerprint(response:Mapping[str,Any])->str:
|
|
keys=(
|
|
"task_id","job_id","repository","base_oid","expected_branch_oid",
|
|
"target_branch","branch_mode","generation","state","reservation_receipt",
|
|
)
|
|
return hashlib.sha256(canonical_json_bytes({k:response.get(k) for k in keys})).hexdigest()
|
|
|
|
|
|
def _stable_receipt_identity(response:Mapping[str,Any])->dict[str,Any]:
|
|
receipt=_mapping(response.get("reservation_receipt"),"TASK_ADMISSION_EXECUTION_RESERVATION_RECEIPT_MISSING")
|
|
return {
|
|
"task_id":response.get("task_id"),
|
|
"job_id":response.get("job_id"),
|
|
"repository":response.get("repository"),
|
|
"base_oid":response.get("base_oid"),
|
|
"expected_branch_oid":response.get("expected_branch_oid"),
|
|
"target_branch":response.get("target_branch"),
|
|
"branch_mode":response.get("branch_mode"),
|
|
"generation":response.get("generation"),
|
|
"binding_state":response.get("state"),
|
|
"receipt_state":receipt.get("state"),
|
|
"producer_idempotency_key":receipt.get("producer_idempotency_key"),
|
|
"request_idempotency_key":receipt.get("request_idempotency_key"),
|
|
}
|
|
|
|
def validate_bootstrap_replay(
|
|
*,
|
|
grant:object,
|
|
request:Mapping[str,Any],
|
|
first:object,
|
|
replay:object,
|
|
replay_side_effect_state:str,
|
|
first_side_effect_state:str="QUEUED",
|
|
expected_branch_mode:str="CONTINUE_EXISTING",
|
|
)->str:
|
|
if first_side_effect_state not in {"QUEUED","SAME_JOB"}:
|
|
fail("TASK_ADMISSION_EXECUTION_RESERVATION_PHASE_INVALID")
|
|
first_id=validate_bootstrap_response(
|
|
grant=grant,request=request,response=first,
|
|
expected_side_effect_state=first_side_effect_state,
|
|
expected_branch_mode=expected_branch_mode,
|
|
)
|
|
replay_id=validate_bootstrap_response(
|
|
grant=grant,request=request,response=replay,
|
|
expected_side_effect_state=replay_side_effect_state,
|
|
expected_branch_mode=expected_branch_mode,
|
|
)
|
|
if first_id!=replay_id:
|
|
fail("TASK_ADMISSION_EXECUTION_RESERVATION_REPLAY_JOB_CONFLICT")
|
|
first_map=_mapping(first,"TASK_ADMISSION_EXECUTION_RESERVATION_RESPONSE_INVALID")
|
|
replay_map=_mapping(replay,"TASK_ADMISSION_EXECUTION_RESERVATION_RESPONSE_INVALID")
|
|
if _stable_receipt_identity(first_map)!=_stable_receipt_identity(replay_map):
|
|
fail("TASK_ADMISSION_EXECUTION_RESERVATION_REPLAY_IDENTITY_CONFLICT")
|
|
return first_id
|
|
|
|
# ===== AIOS FORMAL LINEAGE POST-OWNER PRODUCER NATIVE V12 — BEGIN =====
|
|
import inspect as _fl_inspect
|
|
|
|
_FL_HARDENING_PROVIDER = ExecutionPlanDirectoryProvider
|
|
_FL_BUILD_FROM_ACTIVATION = build_task_admission_handlers_from_activation_file
|
|
_FL_BUILD_FROM_ENVIRONMENT = globals().get("build_task_admission_handlers_from_environment")
|
|
_FL_BUILD_RUNTIME_FROM_ACTIVATION = globals().get(
|
|
"build_task_admission_runtime_from_activation_file"
|
|
)
|
|
_FL_BUILD_RUNTIME_FROM_ENVIRONMENT = globals().get(
|
|
"build_task_admission_runtime_from_environment"
|
|
)
|
|
_FL_APPROVED_BOOTSTRAP_SHA256="758895a8407568890fd2f8eb2f232a3dbf2cfa369e5f65ae8c6975bf8d01e3b5"
|
|
REPLAY_SIDE_EFFECT_STATE="SAME_JOB"
|
|
|
|
def _fl_error(code:str,status:int=403):
|
|
cls=globals().get("TaskAdmissionError",RuntimeError)
|
|
try:
|
|
return cls(code,status)
|
|
except TypeError:
|
|
return cls(code)
|
|
|
|
def _fl_sha(path:Path)->str:
|
|
h=hashlib.sha256()
|
|
with path.open("rb") as f:
|
|
for chunk in iter(lambda:f.read(1024*1024),b""):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
def _fl_provider_root(provider:object)->Path:
|
|
found=[]
|
|
for name in ("root","directory","plan_dir","execution_plan_dir"):
|
|
raw=getattr(provider,name,None)
|
|
if isinstance(raw,(str,os.PathLike)):
|
|
p=Path(raw)
|
|
if p.is_absolute():
|
|
found.append(p)
|
|
unique={str(p) for p in found}
|
|
if len(unique)!=1:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_PROVIDER_ROOT_INVALID",503)
|
|
return Path(next(iter(unique)))
|
|
|
|
def _fl_assert_bootstrap_source(bootstrap:object)->None:
|
|
raw=_fl_inspect.getsourcefile(type(bootstrap))
|
|
if not raw:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BOOTSTRAP_SOURCE_UNKNOWN",503)
|
|
path=Path(raw)
|
|
if not path.is_file() or path.is_symlink():
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BOOTSTRAP_SOURCE_INVALID",503)
|
|
if _fl_sha(path)!=_FL_APPROVED_BOOTSTRAP_SHA256:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BOOTSTRAP_SOURCE_DRIFT",503)
|
|
|
|
|
|
def _fl_reservation_refs(runtime:object,grant:object)->tuple[str,str|None,str,str]:
|
|
require_reservable_grant(grant)
|
|
authority=getattr(runtime,"authority",None)
|
|
if authority is None or not callable(getattr(authority,"read_ref",None)):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
|
|
base=str(_gv(grant,"authority_base_oid"))
|
|
branch=str(_gv(grant,"target_branch"))
|
|
try:
|
|
main=authority.read_ref("refs/heads/main")
|
|
target=authority.read_ref("refs/heads/"+branch)
|
|
except Exception as exc:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_UNAVAILABLE",503) from exc
|
|
if main!=base:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_DRIFT",409)
|
|
if _gv(grant,"task_id")==TARGET_TASK_ID:
|
|
if target!=base:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_DRIFT",409)
|
|
return main,target,"CONTINUE_EXISTING",base
|
|
if target is None:
|
|
if _gv(grant,"generation")!=1:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_GENERATION_CONFLICT",409)
|
|
return main,target,"CREATE_NEW",base
|
|
if target==base:
|
|
return main,target,"CONTINUE_EXISTING",base
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_DRIFT",409)
|
|
|
|
|
|
def _fl_validate_durable_binding(
|
|
*,grant:object,request:Mapping[str,Any],durable:object,job_id:str,
|
|
expected_branch_mode:str,
|
|
)->None:
|
|
expected={
|
|
"task_id":_gv(grant,"task_id"),
|
|
"job_id":job_id,
|
|
"repository":_gv(grant,"repository"),
|
|
"base_oid":_gv(grant,"authority_base_oid"),
|
|
"expected_branch_oid":request.get("expected_branch_oid"),
|
|
"target_branch":_gv(grant,"target_branch"),
|
|
"branch_mode":expected_branch_mode,
|
|
"generation":_gv(grant,"generation"),
|
|
"risk":_gv(grant,"risk"),
|
|
"idempotency_key":request.get("idempotency_key"),
|
|
"description":request.get("description"),
|
|
}
|
|
if any(getattr(durable,field,None)!=value for field,value in expected.items()):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
|
|
|
|
|
|
def _fl_existing_reservation_readback(
|
|
*,bootstrap:object,grant:object,request:Mapping[str,Any],durable:object,
|
|
expected_branch_mode:str,
|
|
)->dict[str,Any]:
|
|
"""Verify an existing producer reservation without opening a mutating store.
|
|
|
|
``GovernedTaskBootstrap._store`` initializes missing directories and SQLite
|
|
state, while ``create`` may submit a new Job. Neither is valid after a
|
|
durable task binding already exists: missing producer state is corruption,
|
|
not crash recovery. Read the exact Job and idempotency rows through a
|
|
read-only SQLite URI and return an explicitly typed readback proof. This
|
|
is deliberately not a producer submit receipt: Job lifecycle state may
|
|
have advanced since the reservation was first created.
|
|
"""
|
|
task_id=str(_gv(grant,"task_id"))
|
|
job_id=getattr(durable,"job_id",None)
|
|
if not isinstance(job_id,str) or not _JOB_ID.fullmatch(job_id):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID",409)
|
|
key=request.get("idempotency_key")
|
|
if not isinstance(key,str) or not key:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_IDEMPOTENCY_INVALID",409)
|
|
try:
|
|
expected_job_id=_FLGitOpsJobController.internal_job_id_for_idempotency_key(key)
|
|
producer_key=_fl_job_sha256_json({"client_idempotency_key":key})
|
|
except Exception as exc:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_IDEMPOTENCY_INVALID",409) from exc
|
|
if job_id!=expected_job_id or job_id!="job-"+producer_key[:32]:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
|
|
|
|
job_root=getattr(bootstrap,"job_root",None)
|
|
if not isinstance(job_root,(str,os.PathLike)):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
|
|
job_root=Path(job_root)
|
|
store_root=job_root/_fl_task_storage_key(task_id)
|
|
legacy_root=job_root/_fl_legacy_task_storage_key(task_id)
|
|
try:
|
|
legacy_stat=None
|
|
try:
|
|
legacy_stat=legacy_root.lstat()
|
|
except FileNotFoundError:
|
|
pass
|
|
if legacy_root!=store_root and legacy_stat is not None:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_CONFLICT",409)
|
|
store_stat=store_root.lstat()
|
|
db_path=store_root/"state.sqlite3"
|
|
db_stat=db_path.lstat()
|
|
except FileNotFoundError as exc:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_MISSING",409) from exc
|
|
except OSError as exc:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_READ_FAILED",503) from exc
|
|
if (
|
|
stat.S_ISLNK(store_stat.st_mode) or not stat.S_ISDIR(store_stat.st_mode)
|
|
or stat.S_ISLNK(db_stat.st_mode) or not stat.S_ISREG(db_stat.st_mode)
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_INVALID",409)
|
|
|
|
values={
|
|
field:str(request[field]) for field in (
|
|
"task_id","repository","base_oid","target_branch","risk",
|
|
"description","idempotency_key",
|
|
)
|
|
}
|
|
payload=json.dumps(values,sort_keys=True,separators=(",",":")).encode()
|
|
payload_digest=hashlib.sha256(payload).hexdigest()
|
|
expected_branch_oid=str(
|
|
request.get("expected_branch_oid") or values["base_oid"]
|
|
)
|
|
producer_binding={
|
|
"campaign_id":task_id,
|
|
"repository":values["repository"],
|
|
"operation":"GOVERNED_TASK_CREATE",
|
|
"branch":values["target_branch"],
|
|
"expected_branch_oid":expected_branch_oid,
|
|
"payload_digest":payload_digest,
|
|
"idempotency_key":key,
|
|
"task_id":task_id,
|
|
"lineage_anchor_oid":values["base_oid"],
|
|
}
|
|
expected_request={
|
|
**producer_binding,
|
|
"binding":producer_binding,
|
|
"payload_ref":"sha256/"+payload_digest,
|
|
"payload_size":len(payload),
|
|
}
|
|
expected_job={
|
|
"job_id":job_id,
|
|
"idempotency_key":producer_key,
|
|
"campaign_id":task_id,
|
|
"repository":values["repository"],
|
|
"operation":"GOVERNED_TASK_CREATE",
|
|
"branch":values["target_branch"],
|
|
"expected_branch_oid":expected_branch_oid,
|
|
"payload_digest":payload_digest,
|
|
"payload_ref":"sha256/"+payload_digest,
|
|
"payload_size":len(payload),
|
|
}
|
|
# mode=ro prevents schema/data creation while still including committed WAL
|
|
# rows. query_only supplies a second SQLite-level write prohibition.
|
|
try:
|
|
resolved_store=store_root.resolve(strict=True)
|
|
resolved_db=db_path.resolve(strict=True)
|
|
if resolved_db.parent!=resolved_store:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_INVALID",409)
|
|
connection=sqlite3.connect(
|
|
resolved_db.as_uri()+"?mode=ro",
|
|
uri=True,timeout=5.0,
|
|
)
|
|
connection.row_factory=sqlite3.Row
|
|
try:
|
|
connection.execute("PRAGMA query_only=ON")
|
|
connection.execute("BEGIN")
|
|
job=connection.execute(
|
|
"SELECT job_id,idempotency_key,campaign_id,repository,operation,"
|
|
"branch,expected_branch_oid,payload_digest,payload_ref,payload_size,"
|
|
"state,side_effect_state,revision,request_json FROM jobs WHERE job_id=?",
|
|
(job_id,),
|
|
).fetchone()
|
|
idempotency=connection.execute(
|
|
"SELECT key,job_id,binding_json FROM idempotency_keys WHERE key=?",
|
|
(producer_key,),
|
|
).fetchone()
|
|
store_after=store_root.lstat()
|
|
db_after=db_path.lstat()
|
|
if (
|
|
(store_stat.st_dev,store_stat.st_ino)!=(store_after.st_dev,store_after.st_ino)
|
|
or (db_stat.st_dev,db_stat.st_ino)!=(db_after.st_dev,db_after.st_ino)
|
|
or stat.S_ISLNK(store_after.st_mode)
|
|
or not stat.S_ISDIR(store_after.st_mode)
|
|
or stat.S_ISLNK(db_after.st_mode)
|
|
or not stat.S_ISREG(db_after.st_mode)
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_CHANGED",409)
|
|
finally:
|
|
connection.close()
|
|
del connection
|
|
except (OSError,sqlite3.Error) as exc:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_READ_FAILED",503) from exc
|
|
if job is None:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_MISSING",409)
|
|
if idempotency is None:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_IDEMPOTENCY_MISSING",409)
|
|
if any(job[field]!=value for field,value in expected_job.items()):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_CONFLICT",409)
|
|
valid_states={item.value for item in _FLJobState}
|
|
if (
|
|
job["state"] not in valid_states
|
|
or not isinstance(job["side_effect_state"],str)
|
|
or not job["side_effect_state"]
|
|
or not isinstance(job["revision"],int)
|
|
or isinstance(job["revision"],bool)
|
|
or job["revision"]<0
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STATE_INVALID",409)
|
|
try:
|
|
recorded_request=json.loads(job["request_json"])
|
|
recorded_binding=json.loads(idempotency["binding_json"])
|
|
except (TypeError,ValueError) as exc:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_CONFLICT",409) from exc
|
|
if (
|
|
recorded_request!=expected_request
|
|
or idempotency["key"]!=producer_key
|
|
or idempotency["job_id"]!=job_id
|
|
or recorded_binding!=producer_binding
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_CONFLICT",409)
|
|
payload_path=store_root/"payloads"/payload_digest
|
|
descriptor=None
|
|
try:
|
|
descriptor=os.open(
|
|
payload_path,
|
|
os.O_RDONLY|getattr(os,"O_NOFOLLOW",0),
|
|
)
|
|
payload_stat=os.fstat(descriptor)
|
|
with os.fdopen(descriptor,"rb",closefd=False) as stream:
|
|
payload_bytes=stream.read(len(payload)+1)
|
|
payload_after=payload_path.lstat()
|
|
except FileNotFoundError as exc:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_PAYLOAD_MISSING",409) from exc
|
|
except OSError as exc:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_PAYLOAD_READ_FAILED",503) from exc
|
|
finally:
|
|
if descriptor is not None:
|
|
os.close(descriptor)
|
|
if (
|
|
not stat.S_ISREG(payload_stat.st_mode)
|
|
or stat.S_ISLNK(payload_after.st_mode)
|
|
or not stat.S_ISREG(payload_after.st_mode)
|
|
or (payload_stat.st_dev,payload_stat.st_ino)!=(payload_after.st_dev,payload_after.st_ino)
|
|
or len(payload_bytes)!=len(payload)
|
|
or not hmac.compare_digest(hashlib.sha256(payload_bytes).hexdigest(),payload_digest)
|
|
or payload_bytes!=payload
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_PAYLOAD_CONFLICT",409)
|
|
|
|
response=dict(durable.response(idempotent=True))
|
|
response["reservation_readback"]={
|
|
"schema":"aios.governed-task-reservation-readback.v1",
|
|
"job_id":job_id,
|
|
"state":job["state"],
|
|
"side_effect_state":job["side_effect_state"],
|
|
"revision":job["revision"],
|
|
"producer_idempotency_key":producer_key,
|
|
"request_idempotency_key":key,
|
|
}
|
|
return response
|
|
|
|
|
|
def _fl_validate_reservation_readback(
|
|
*,grant:object,request:Mapping[str,Any],durable:object,
|
|
response:object,expected_branch_mode:str,
|
|
)->str:
|
|
if not isinstance(response,Mapping):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_INVALID",409)
|
|
result=response
|
|
expected_response=dict(durable.response(idempotent=True))
|
|
if (
|
|
set(result)!=set(expected_response)|{"reservation_readback"}
|
|
or any(result.get(field)!=value for field,value in expected_response.items())
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_CONFLICT",409)
|
|
proof=result.get("reservation_readback")
|
|
if not isinstance(proof,Mapping):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_INVALID",409)
|
|
expected_keys={
|
|
"schema","job_id","state","side_effect_state","revision",
|
|
"producer_idempotency_key","request_idempotency_key",
|
|
}
|
|
key=request.get("idempotency_key")
|
|
try:
|
|
producer_key=_fl_job_sha256_json({"client_idempotency_key":key})
|
|
except Exception as exc:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_INVALID",409) from exc
|
|
job_id=getattr(durable,"job_id",None)
|
|
if (
|
|
set(proof)!=expected_keys
|
|
or proof.get("schema")!="aios.governed-task-reservation-readback.v1"
|
|
or proof.get("job_id")!=job_id
|
|
or proof.get("producer_idempotency_key")!=producer_key
|
|
or proof.get("request_idempotency_key")!=key
|
|
or proof.get("state") not in {item.value for item in _FLJobState}
|
|
or not isinstance(proof.get("side_effect_state"),str)
|
|
or not proof.get("side_effect_state")
|
|
or not isinstance(proof.get("revision"),int)
|
|
or isinstance(proof.get("revision"),bool)
|
|
or proof.get("revision")<0
|
|
or getattr(durable,"branch_mode",None)!=expected_branch_mode
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_CONFLICT",409)
|
|
return str(job_id)
|
|
|
|
class _FLProducerReceiptReserver:
|
|
def __init__(self,runtime:object):
|
|
self.runtime=runtime
|
|
self.readbacks:dict[str,dict[str,Any]]={}
|
|
|
|
def validate_v2_replay(self,grant:object,plan:Mapping[str,Any])->None:
|
|
"""Permit the old v2 contract only as an exact durable replay."""
|
|
require_reservable_grant(grant)
|
|
bootstrap=getattr(self.runtime,"bootstrap",None)
|
|
if bootstrap is None or not callable(getattr(bootstrap,"get_binding",None)):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
|
|
_fl_assert_bootstrap_source(bootstrap)
|
|
task_id=str(_gv(grant,"task_id"))
|
|
try:
|
|
durable=bootstrap.get_binding(task_id)
|
|
except Exception as exc:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_V2_REPLAY_BINDING_REQUIRED",409) from exc
|
|
expected={
|
|
"task_id":task_id,
|
|
"job_id":plan.get("external_job_id"),
|
|
"repository":_gv(grant,"repository"),
|
|
"base_oid":_gv(grant,"authority_base_oid"),
|
|
"expected_branch_oid":_gv(grant,"authority_base_oid"),
|
|
"target_branch":_gv(grant,"target_branch"),
|
|
"generation":_gv(grant,"generation"),
|
|
"risk":_gv(grant,"risk"),
|
|
"idempotency_key":"task-admission:"+task_id,
|
|
"description":"P0 governed execution projection for officially admitted Agent Task.",
|
|
}
|
|
if any(getattr(durable,field,None)!=value for field,value in expected.items()):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_V2_REPLAY_BINDING_CONFLICT",409)
|
|
if getattr(durable,"branch_mode",None) not in {"CREATE_NEW","CONTINUE_EXISTING"}:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_V2_REPLAY_BINDING_CONFLICT",409)
|
|
if not isinstance(plan.get("external_job_id"),str) or not _JOB_ID.fullmatch(plan["external_job_id"]):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID",409)
|
|
|
|
def existing_v3_job(self,grant:object,plan:Mapping[str,Any])->str|None:
|
|
"""Reuse an exact durable reservation without reinterpreting live refs.
|
|
|
|
Once reserved, CREATE_NEW versus CONTINUE_EXISTING is immutable input
|
|
lineage. A later TaskPrincipal read must not turn legitimate branch
|
|
creation or forward progress into a fresh admission decision.
|
|
"""
|
|
try:
|
|
validate_v3_plan(grant=grant,raw=canonical_json_bytes(dict(plan)))
|
|
except ProducerReceiptError as exc:
|
|
raise _fl_error(exc.code,403) from exc
|
|
bootstrap=getattr(self.runtime,"bootstrap",None)
|
|
bindings=getattr(self.runtime,"bindings",None)
|
|
if (
|
|
bootstrap is None
|
|
or not callable(getattr(bootstrap,"get_binding",None))
|
|
or not isinstance(bindings,dict)
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
|
|
_fl_assert_bootstrap_source(bootstrap)
|
|
task_id=str(_gv(grant,"task_id"))
|
|
try:
|
|
durable=bootstrap.get_binding(task_id)
|
|
except Exception as exc:
|
|
if str(exc)=="TASK_EXECUTION_PROJECTION_MISSING":
|
|
return None
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_READ_FAILED",503) from exc
|
|
request=build_reservation_request(
|
|
grant,expected_branch_oid=str(_gv(grant,"authority_base_oid")),
|
|
)
|
|
branch_mode=getattr(durable,"branch_mode",None)
|
|
if branch_mode not in {"CREATE_NEW","CONTINUE_EXISTING"}:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
|
|
job_id=getattr(durable,"job_id",None)
|
|
if not isinstance(job_id,str) or not _JOB_ID.fullmatch(job_id):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID",409)
|
|
_fl_validate_durable_binding(
|
|
grant=grant,request=request,durable=durable,job_id=job_id,
|
|
expected_branch_mode=branch_mode,
|
|
)
|
|
readback=_fl_existing_reservation_readback(
|
|
bootstrap=bootstrap,grant=grant,request=request,durable=durable,
|
|
expected_branch_mode=branch_mode,
|
|
)
|
|
_fl_validate_reservation_readback(
|
|
grant=grant,request=request,durable=durable,response=readback,
|
|
expected_branch_mode=branch_mode,
|
|
)
|
|
# Keep readback evidence separate from producer submit receipts. In
|
|
# particular, a principal re-read must not overwrite the real receipt
|
|
# retained by a fresh admission in this runtime.
|
|
self.readbacks[task_id]=dict(readback)
|
|
return job_id
|
|
|
|
def __call__(self,grant:object,plan:Mapping[str,Any])->str:
|
|
try:
|
|
validate_v3_plan(grant=grant,raw=canonical_json_bytes(dict(plan)))
|
|
except ProducerReceiptError as exc:
|
|
raise _fl_error(exc.code,403) from exc
|
|
authority=getattr(self.runtime,"authority",None)
|
|
bootstrap=getattr(self.runtime,"bootstrap",None)
|
|
bindings=getattr(self.runtime,"bindings",None)
|
|
if (
|
|
authority is None or bootstrap is None
|
|
or not callable(getattr(bootstrap,"create",None))
|
|
or not callable(getattr(bootstrap,"get_binding",None))
|
|
or not isinstance(bindings,dict)
|
|
):
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
|
|
|
|
_fl_assert_bootstrap_source(bootstrap)
|
|
|
|
main_before,branch_before,branch_mode,expected_branch_oid=_fl_reservation_refs(self.runtime,grant)
|
|
|
|
request=build_reservation_request(grant,expected_branch_oid=expected_branch_oid)
|
|
request_before=dict(request)
|
|
try:
|
|
first=bootstrap.create(request)
|
|
if request!=request_before:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_REQUEST_MUTATED",503)
|
|
first_phase=_mapping(first,"TASK_ADMISSION_EXECUTION_RESERVATION_RESPONSE_INVALID").get("reservation_receipt",{}).get("side_effect_state")
|
|
if first_phase not in {"QUEUED","SAME_JOB"}:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_PHASE_INVALID",409)
|
|
validate_bootstrap_response(
|
|
grant=grant,request=request,response=first,
|
|
expected_side_effect_state=first_phase,expected_branch_mode=branch_mode,
|
|
)
|
|
|
|
replay=bootstrap.create(request)
|
|
if request!=request_before:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_REQUEST_MUTATED",503)
|
|
job_id=validate_bootstrap_replay(
|
|
grant=grant,request=request,first=first,replay=replay,
|
|
replay_side_effect_state=REPLAY_SIDE_EFFECT_STATE,
|
|
first_side_effect_state=first_phase,expected_branch_mode=branch_mode,
|
|
)
|
|
durable=bootstrap.get_binding(str(_gv(grant,"task_id")))
|
|
_fl_validate_durable_binding(
|
|
grant=grant,request=request,durable=durable,job_id=job_id,
|
|
expected_branch_mode=branch_mode,
|
|
)
|
|
except ProducerReceiptError as exc:
|
|
raise _fl_error(exc.code,409) from exc
|
|
except Exception as exc:
|
|
task_error=globals().get("TaskAdmissionError")
|
|
if isinstance(task_error,type) and isinstance(exc,task_error):
|
|
raise
|
|
raise _fl_error(
|
|
str(exc) or "TASK_ADMISSION_EXECUTION_RESERVATION_FAILED",503
|
|
) from exc
|
|
|
|
# Existing runtime's public createGovernedTask projection performs this
|
|
# in-memory binding update after bootstrap.create(). Mirror it only
|
|
# after the exact producer receipt + replay proof has passed.
|
|
bindings[str(_gv(grant,"task_id"))]=dict(replay)
|
|
self.readbacks.pop(str(_gv(grant,"task_id")),None)
|
|
|
|
try:
|
|
main_after=authority.read_ref("refs/heads/main")
|
|
branch_after=authority.read_ref("refs/heads/"+str(_gv(grant,"target_branch")))
|
|
except Exception as exc:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_UNAVAILABLE",503) from exc
|
|
if main_after!=main_before or branch_after!=branch_before:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_REF_MUTATION",409)
|
|
return job_id
|
|
|
|
class V3ProducerReceiptExecutionPlanDirectoryProvider(_FL_HARDENING_PROVIDER):
|
|
_reserver:_FLProducerReceiptReserver|None=None
|
|
|
|
def _target(self,grant:object)->Mapping[str,Any]:
|
|
if self._reserver is None:
|
|
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
|
|
try:
|
|
plan=load_v3_plan(_fl_provider_root(self),grant)
|
|
job_id=self._reserver.existing_v3_job(grant,plan)
|
|
if job_id is None:
|
|
job_id=self._reserver(grant,plan)
|
|
except ProducerReceiptError as exc:
|
|
raise _fl_error(exc.code,403) from exc
|
|
return {
|
|
**plan,
|
|
"external_job_id":job_id,
|
|
"execution_digest":_gv(grant,"execution_digest"),
|
|
}
|
|
|
|
def __call__(self,grant:object)->Mapping[str,Any]:
|
|
try:
|
|
return self._target(grant)
|
|
except Exception as v3_error:
|
|
parent=getattr(super(),"__call__",None)
|
|
if not callable(parent) or self._reserver is None:
|
|
raise v3_error
|
|
try:
|
|
legacy=parent(grant)
|
|
except Exception:
|
|
raise v3_error
|
|
self._reserver.validate_v2_replay(grant,legacy)
|
|
return legacy
|
|
|
|
def load(self,grant:object)->Mapping[str,Any]:
|
|
return self(grant)
|
|
|
|
def _fl_runtime_from_post_admission(post_admission:object)->object:
|
|
runtime=getattr(post_admission,"__self__",None)
|
|
if runtime is None or not callable(getattr(runtime,"project_admitted_task",None)):
|
|
raise RuntimeError("TASK_ADMISSION_POST_OWNER_RUNTIME_BINDING_INVALID")
|
|
return runtime
|
|
|
|
def _fl_call(builder,*args,post_admission=None,**kwargs):
|
|
if post_admission is None:
|
|
raise RuntimeError("TASK_ADMISSION_POST_OWNER_POST_ADMISSION_REQUIRED")
|
|
reserver=_FLProducerReceiptReserver(_fl_runtime_from_post_admission(post_admission))
|
|
|
|
class BoundProvider(V3ProducerReceiptExecutionPlanDirectoryProvider):
|
|
_reserver=reserver
|
|
|
|
global ExecutionPlanDirectoryProvider
|
|
previous=ExecutionPlanDirectoryProvider
|
|
ExecutionPlanDirectoryProvider=BoundProvider
|
|
try:
|
|
def exact_post_admission(grant,result):
|
|
task_id=str(_gv(grant,"task_id"))
|
|
durable=reserver.runtime.bootstrap.get_binding(task_id)
|
|
request=build_reservation_request(
|
|
grant,expected_branch_oid=str(_gv(grant,"authority_base_oid")),
|
|
)
|
|
if getattr(durable,"idempotency_key",None)!=request["idempotency_key"]:
|
|
return post_admission(grant,result)
|
|
branch_mode=getattr(durable,"branch_mode",None)
|
|
if branch_mode not in {"CREATE_NEW","CONTINUE_EXISTING"}:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
|
|
binding=reserver.runtime.bindings.get(task_id)
|
|
if isinstance(binding,Mapping) and "reservation_receipt" in binding:
|
|
# Fresh admission must retain the actual producer submit replay
|
|
# receipt produced by GovernedTaskBootstrap.create().
|
|
job_id=validate_bootstrap_response(
|
|
grant=grant,request=request,response=binding,
|
|
expected_side_effect_state="SAME_JOB",expected_branch_mode=branch_mode,
|
|
)
|
|
else:
|
|
# A process restart may leave only durable producer state. Its
|
|
# separately typed readback proves immutable identity without
|
|
# claiming that a producer submit happened in this request.
|
|
binding=reserver.readbacks.get(task_id)
|
|
job_id=_fl_validate_reservation_readback(
|
|
grant=grant,request=request,durable=durable,response=binding,
|
|
expected_branch_mode=branch_mode,
|
|
)
|
|
if result.get("task_id")!=task_id or result.get("external_job_id")!=job_id or result.get("state",result.get("status")) not in {"QUEUED","CLAIMED","RUNNING"}:
|
|
raise _fl_error("TASK_ADMISSION_PRODUCER_TASK_BINDING_CONFLICT",409)
|
|
_fl_validate_durable_binding(
|
|
grant=grant,request=request,durable=durable,job_id=job_id,
|
|
expected_branch_mode=branch_mode,
|
|
)
|
|
return dict(binding)
|
|
return builder(*args,post_admission=exact_post_admission,**kwargs)
|
|
finally:
|
|
ExecutionPlanDirectoryProvider=previous
|
|
|
|
def build_task_admission_runtime_from_activation_file(
|
|
activation_file:Path,*,
|
|
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission=None,
|
|
):
|
|
if not callable(_FL_BUILD_RUNTIME_FROM_ACTIVATION):
|
|
raise RuntimeError("TASK_ADMISSION_RUNTIME_ACTIVATION_BUILDER_MISSING")
|
|
return _fl_call(
|
|
_FL_BUILD_RUNTIME_FROM_ACTIVATION,activation_file,
|
|
caller_service=caller_service,post_admission=post_admission,
|
|
)
|
|
|
|
|
|
def build_task_admission_handlers_from_activation_file(
|
|
activation_file:Path,*,
|
|
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission=None,
|
|
):
|
|
return build_task_admission_runtime_from_activation_file(
|
|
activation_file,
|
|
caller_service=caller_service,post_admission=post_admission,
|
|
).handlers
|
|
|
|
|
|
def build_task_admission_runtime_from_environment(
|
|
*,
|
|
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission=None,
|
|
):
|
|
if not callable(_FL_BUILD_RUNTIME_FROM_ENVIRONMENT):
|
|
raise RuntimeError("TASK_ADMISSION_RUNTIME_ENVIRONMENT_BUILDER_MISSING")
|
|
return _fl_call(
|
|
_FL_BUILD_RUNTIME_FROM_ENVIRONMENT,
|
|
caller_service=caller_service,post_admission=post_admission,
|
|
)
|
|
|
|
|
|
def build_task_admission_handlers_from_environment(
|
|
*,
|
|
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
|
|
post_admission=None,
|
|
):
|
|
return build_task_admission_runtime_from_environment(
|
|
caller_service=caller_service,post_admission=post_admission,
|
|
).handlers
|
|
|
|
# Keep the standalone legacy provider contract. The exported admission builders
|
|
# install a runtime-bound v3 subclass only while composing their trusted port;
|
|
# an unbound provider cannot prove a durable producer receipt.
|
|
ExecutionPlanDirectoryProvider=_FL_HARDENING_PROVIDER
|
|
# ===== AIOS FORMAL LINEAGE POST-OWNER PRODUCER NATIVE V12 — END =====
|