feat(governed): close P0 task admission bootstrap

This commit is contained in:
AIOS Governed Continuation
2026-09-03 21:40:32 +08:00
parent 91f104ef62
commit a7e835b9d3
16 changed files with 6464 additions and 335 deletions
+80 -15
View File
@@ -6,6 +6,12 @@ import hashlib
from pathlib import Path
from typing import Any, Callable, Mapping
from aios_task_admission_bridge_contract import (
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS,
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS,
TASK_ADMISSION_ROUTES,
)
BRIDGE_SCHEMA_VERSION = "aios.chatgpt-gitops-bridge.v2"
BRIDGE_OPERATIONS = (
@@ -29,15 +35,17 @@ BRIDGE_OPERATIONS = (
"getControlPlaneSnapshot",
)
TASK_ADMISSION_OPERATIONS = tuple(TASK_ADMISSION_OPERATION_REQUIRED_FIELDS)
OPERATION_REQUIRED_FIELDS = {
"createGovernedTask": ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key"),
"projectOnlineAgentExecution": ("task_id", "required_capabilities"),
"acquireWriterLease": ("schema", "task_id", "repository", "branch_mode", "base_oid", "expected_branch_oid", "target_branch", "generation", "lease_seconds"),
"commitFileGoverned": ("task_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "path", "content_base64", "message"),
"commitFilesGoverned": ("task_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "files", "message"),
"getGitOpsJob": ("job_id",),
"resumeGitOpsJob": ("job_id", "expected_revision"),
"reconcileGitOpsJob": ("job_id", "expected_revision"),
"acquireWriterLease": ("schema", "task_id", "job_id", "repository", "branch_mode", "base_oid", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "lease_seconds"),
"commitFileGoverned": ("task_id", "job_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "path", "content_base64", "message"),
"commitFilesGoverned": ("task_id", "job_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "files", "message"),
"getGitOpsJob": ("task_id", "job_id"),
"resumeGitOpsJob": ("task_id", "job_id", "expected_revision"),
"reconcileGitOpsJob": ("task_id", "job_id", "expected_revision"),
"createTagApprovalRequest": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "owner_approval", "execute"),
"requestOrCreateTag": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "execute"),
"getTagPublicationStatus": ("version",),
@@ -46,6 +54,7 @@ OPERATION_REQUIRED_FIELDS = {
"getReleaseConsistency": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"runReleaseAcceptance": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"getControlPlaneSnapshot": (),
**TASK_ADMISSION_OPERATION_REQUIRED_FIELDS,
}
_OPERATION_PATHS = {
@@ -60,19 +69,48 @@ _OPERATION_PATHS = {
"getTagPublicationStatus": "/v2/tag/status",
"getReleaseConsistency": "/v2/release/consistency",
"runReleaseAcceptance": "/v2/release/acceptance",
**{operation: path for path, operation in TASK_ADMISSION_ROUTES.items()},
}
def operation_contracts() -> dict[str, dict[str, object]]:
def _has_complete_task_admission_handlers(
handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None,
) -> bool:
return handlers is not None and all(
callable(handlers.get(operation)) for operation in TASK_ADMISSION_OPERATIONS
)
def operation_contracts(
handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None = None,
) -> dict[str, dict[str, object]]:
"""Return the runtime contract consumed by adapters and schema tooling."""
return {
contracts = {
operation: {
"method": "POST",
"path": _OPERATION_PATHS.get(operation),
"required_fields": list(fields),
}
for operation, fields in OPERATION_REQUIRED_FIELDS.items()
if operation not in TASK_ADMISSION_OPERATIONS
}
if _has_complete_task_admission_handlers(handlers):
contracts.update(
{
operation: {
"method": "GET" if operation == "getTaskAdmissionStatus" else "POST",
"path": _OPERATION_PATHS[operation],
"required_fields": list(
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS[operation]
),
"optional_fields": list(
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS[operation]
),
}
for operation in TASK_ADMISSION_OPERATIONS
}
)
return contracts
def render_openapi_contract_fragment() -> str:
@@ -85,6 +123,8 @@ def render_openapi_contract_fragment() -> str:
" post:\n"
" operationId: commitFilesGoverned\n"
" summary: Submit one atomic governed commit containing multiple files\n"
" security: [{taskPrincipalAuth: []}]\n"
" x-aios-task-principal: Bridge verifies a fresh aios.task-principal-assertion.v1 and exact task/job/repository/branch/generation/revision/attempt/execution-digest/capability binding; caller JSON cannot provide a principal and missing verification is denied.\n"
" requestBody:\n"
" required: true\n"
" content:\n"
@@ -116,8 +156,12 @@ def render_openapi_contract_fragment() -> str:
" message: {type: string, minLength: 1, maxLength: 200}\n"
" responses:\n"
" '200': {description: Governed commit job and verified branch result}\n"
" '202': {description: Existing external Job alias requires authoritative reconciliation; no replacement Job is created}\n"
" '401': {description: No verified Task principal is available}\n"
" '403': {description: Verified Task principal lacks exact task, external Job, or capability scope}\n"
" '400': {description: Contract or policy rejection}\n"
" '409': {description: Branch CAS or idempotency conflict}\n"
" '423': {description: Narrow P0 Task-principal mutation gate is frozen; assertion is not consumed}\n"
)
@@ -183,20 +227,38 @@ class ControlPlaneReleaseManifest:
class BridgeV2Adapter:
"""Thin operation adapter; all mutating handlers are injected seams."""
def __init__(self, *, runtime_status: Mapping[str, Any], handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None = None) -> None:
self.runtime_status = dict(runtime_status)
def __init__(self, *, runtime_status: Mapping[str, Any] | Callable[[], Mapping[str, Any]], handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None = None) -> None:
self._runtime_status_source = runtime_status if callable(runtime_status) else dict(runtime_status)
self.handlers = dict(handlers or {})
def _runtime_status(self) -> dict[str, Any]:
value = self._runtime_status_source() if callable(self._runtime_status_source) else self._runtime_status_source
if not isinstance(value, Mapping):
raise BridgeV2Error("RUNTIME_STATUS_INVALID")
return dict(value)
def getRuntimeStatus(self) -> dict[str, Any]:
return {"bridge_schema_version": BRIDGE_SCHEMA_VERSION, **self.runtime_status}
return {"bridge_schema_version": BRIDGE_SCHEMA_VERSION, **self._runtime_status()}
def getGovernedWriteCapabilities(self) -> dict[str, Any]:
runtime_status = self._runtime_status()
governed_writes_enabled = runtime_status.get("governed_writes_enabled") is True
return {
"bridge_schema_version": BRIDGE_SCHEMA_VERSION,
"legacy_writes_enabled": False,
"governed_writes_enabled": True,
"operations": list(BRIDGE_OPERATIONS),
"operation_contracts": operation_contracts(),
"governed_writes_enabled": governed_writes_enabled,
"governed_write_blockers": list(
runtime_status.get("governed_write_blockers") or []
),
"operations": [
*BRIDGE_OPERATIONS,
*(
TASK_ADMISSION_OPERATIONS
if _has_complete_task_admission_handlers(self.handlers)
else ()
),
],
"operation_contracts": operation_contracts(self.handlers),
"force_push": False,
"main_write": False,
"tag_write": False,
@@ -213,7 +275,7 @@ class BridgeV2Adapter:
@staticmethod
def legacy_write_disabled() -> dict[str, object]:
return {"error": "LEGACY_WRITE_DISABLED", "replacement": "createGovernedTask", "governed_writes_available": True}
return {"error": "LEGACY_WRITE_DISABLED", "replacement": "createGovernedTask", "governed_writes_available": False}
def _call(self, operation: str, request: Mapping[str, Any]) -> Any:
required = OPERATION_REQUIRED_FIELDS[operation]
@@ -239,5 +301,8 @@ class BridgeV2Adapter:
def executeApprovedProductionRelease(self, request: Mapping[str, Any]) -> Any: return self._call("executeApprovedProductionRelease", request)
def getReleaseConsistency(self, request: Mapping[str, Any]) -> Any: return self._call("getReleaseConsistency", request)
def runReleaseAcceptance(self, request: Mapping[str, Any]) -> Any: return self._call("runReleaseAcceptance", request)
def getTaskAdmissionEnvelope(self, request: Mapping[str, Any]) -> Any: return self._call("getTaskAdmissionEnvelope", request)
def submitTaskAdmissionAuthorization(self, request: Mapping[str, Any]) -> Any: return self._call("submitTaskAdmissionAuthorization", request)
def getTaskAdmissionStatus(self, request: Mapping[str, Any]) -> Any: return self._call("getTaskAdmissionStatus", request)
def getControlPlaneSnapshot(self, request: Mapping[str, Any] | None = None) -> Any: return self._call("getControlPlaneSnapshot", request or {})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,927 @@
"""Fail-closed bootstrap for narrow Owner-delegated Agent Task admission.
This module solves the authorization bootstrap problem without turning the
runtime anchor into a writer and without weakening TaskPrincipal semantics.
Security model:
* A task-admission envelope is created from a server-normalized request.
* The envelope is authorized either by an already-valid OwnerDelegationEpoch
or by an Owner signature verified by an injected trust backend.
* The derived TaskAdmissionGrant can only narrow the parent authorization.
* The grant is handed to the existing Agent Task control plane through an
injected TaskAdmissionPort. This module never writes the Task database.
* TaskPrincipal issuance remains downstream of a real Task claim/TaskLease.
* Exact idempotent replay returns the same admitted Task; conflicting reuse is
rejected.
"""
from __future__ import annotations
import base64
from contextlib import closing, contextmanager
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path, PurePosixPath
import re
import secrets
import sqlite3
import threading
from typing import Any, Callable, Mapping, Protocol, Sequence
SCHEMA_ENVELOPE = "aios.task-admission-envelope.v1"
SCHEMA_GRANT = "aios.task-admission-grant.v1"
SCHEMA_EPOCH = "aios.owner-delegation-epoch.v1"
SCHEMA_RECEIPT = "aios.task-admission-receipt.v1"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TASK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
IDEMPOTENCY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/-]{7,255}$")
CAPABILITY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$")
KEY_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@+-]{2,255}$")
PROHIBITED_CAPABILITIES = frozenset(
{
"git.global_write",
"git.main_write",
"git.force_push",
"tag.publish",
"baseline.finalize",
"production.write",
"production.switch",
"runtime_anchor.write",
"shell.arbitrary",
"credential.export",
}
)
class TaskAdmissionError(RuntimeError):
"""Stable fail-closed task-admission error."""
def __init__(
self,
code: str,
status: int = 400,
*,
details: Mapping[str, Any] | None = None,
) -> None:
super().__init__(code)
self.code = code
self.status = status
self.details = dict(details or {})
@dataclass(frozen=True, slots=True)
class TaskAdmissionPolicy:
repository: str = "aios/aios"
trust_domain: str = "aios.owner"
branch_prefix: str = "agent/"
allowed_capabilities: frozenset[str] = frozenset(
{
"governed.task.create",
"agent-task:control",
"task.principal.issue",
"writer.lease",
"governed.write.files",
"git.read",
"git.test",
"governed.write.branch",
"governed.write.file",
"testing",
"verification",
}
)
max_file_count: int = 64
max_task_ttl_seconds: int = 3600
max_envelope_ttl_seconds: int = 900
max_risk: str = "medium"
policy_digest: str = ""
def normalized_policy_digest(self) -> str:
if self.policy_digest:
return self.policy_digest
return sha256_json(
{
"repository": self.repository,
"trust_domain": self.trust_domain,
"branch_prefix": self.branch_prefix,
"allowed_capabilities": sorted(self.allowed_capabilities),
"max_file_count": self.max_file_count,
"max_task_ttl_seconds": self.max_task_ttl_seconds,
"max_envelope_ttl_seconds": self.max_envelope_ttl_seconds,
"max_risk": self.max_risk,
"prohibited_capabilities": sorted(PROHIBITED_CAPABILITIES),
}
)
@dataclass(frozen=True, slots=True)
class OwnerDelegationEpoch:
"""Already-verified durable Owner authorization.
The verifier/backend is responsible for authenticating the epoch before
returning it to this service.
"""
epoch_id: str
owner_identity: str
trust_domain: str
repository: str
allowed_branch_prefixes: tuple[str, ...]
allowed_capabilities: frozenset[str]
allowed_file_prefixes: tuple[str, ...]
risk_ceiling: str
issued_at: int
not_before: int
expires_at: int
revocation_epoch: int
policy_digest: str
schema: str = SCHEMA_EPOCH
def as_public_dict(self) -> dict[str, Any]:
value = asdict(self)
value["allowed_capabilities"] = sorted(self.allowed_capabilities)
value["allowed_branch_prefixes"] = list(self.allowed_branch_prefixes)
value["allowed_file_prefixes"] = list(self.allowed_file_prefixes)
return value
@dataclass(frozen=True, slots=True)
class TaskAdmissionEnvelope:
envelope_id: str
idempotency_key: str
task_id: str
repository: str
authority_base_oid: str
target_branch: str
generation: int
execution_digest: str
requested_capabilities: tuple[str, ...]
file_allowlist: tuple[str, ...]
risk: str
trust_domain: str
policy_digest: str
issued_at: int
not_before: int
expires_at: int
nonce: str
request_digest: str
schema: str = SCHEMA_ENVELOPE
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
value["requested_capabilities"] = list(self.requested_capabilities)
value["file_allowlist"] = list(self.file_allowlist)
return value
@dataclass(frozen=True, slots=True)
class TaskAdmissionGrant:
grant_id: str
envelope_id: str
task_id: str
repository: str
authority_base_oid: str
target_branch: str
generation: int
execution_digest: str
capabilities: tuple[str, ...]
file_allowlist: tuple[str, ...]
risk: str
trust_domain: str
policy_digest: str
authorization_kind: str
owner_identity: str
parent_epoch_id: str | None
issued_at: int
expires_at: int
one_time_use: bool = True
schema: str = SCHEMA_GRANT
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
value["capabilities"] = list(self.capabilities)
value["file_allowlist"] = list(self.file_allowlist)
return value
class OwnerAuthorizationBackend(Protocol):
"""Bridge to existing trusted Owner authorization.
No permissive default implementation is provided.
"""
def verify_delegation_epoch(self, epoch_id: str) -> OwnerDelegationEpoch:
...
def verify_owner_signature(
self,
*,
key_id: str,
canonical_payload: bytes,
signature_b64: str,
) -> str:
"""Return the verified Owner identity or raise."""
...
class TaskAdmissionPort(Protocol):
"""Existing Agent Task control-plane port."""
def admit(self, grant: TaskAdmissionGrant) -> Mapping[str, Any]:
...
def utc_seconds() -> int:
return int(datetime.now(timezone.utc).timestamp())
def canonical_json_bytes(value: Any) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def sha256_json(value: Any) -> str:
return hashlib.sha256(canonical_json_bytes(value)).hexdigest()
def _normalize_files(raw: Sequence[object], *, maximum: int) -> tuple[str, ...]:
if not isinstance(raw, (list, tuple)):
raise TaskAdmissionError("TASK_ADMISSION_FILE_ALLOWLIST_REJECTED", 403)
if not raw or len(raw) > maximum:
raise TaskAdmissionError("TASK_ADMISSION_FILE_ALLOWLIST_REJECTED", 403)
normalized: list[str] = []
seen: set[str] = set()
for item in raw:
if (
not isinstance(item, str)
or not item
or "\\" in item
or any(ord(char) < 32 or ord(char) == 127 for char in item)
):
raise TaskAdmissionError("TASK_ADMISSION_FILE_PATH_INVALID", 403)
path = PurePosixPath(item)
if path.is_absolute() or ".." in path.parts or "." in path.parts:
raise TaskAdmissionError("TASK_ADMISSION_FILE_PATH_INVALID", 403)
text = path.as_posix()
# Do not normalize a caller supplied path. A signature must bind the
# exact spelling, not a convenient equivalent such as ``a//b``.
if text != item:
raise TaskAdmissionError("TASK_ADMISSION_FILE_PATH_INVALID", 403)
if text.startswith(".git/") or text == ".git":
raise TaskAdmissionError("TASK_ADMISSION_FILE_PATH_INVALID", 403)
if text not in seen:
normalized.append(text)
seen.add(text)
if not normalized:
raise TaskAdmissionError("TASK_ADMISSION_FILE_ALLOWLIST_REJECTED", 403)
return tuple(sorted(normalized))
def _normalize_capabilities(
raw: Sequence[object],
*,
policy: TaskAdmissionPolicy,
) -> tuple[str, ...]:
if not raw:
raise TaskAdmissionError("TASK_ADMISSION_CAPABILITIES_REQUIRED", 403)
values: set[str] = set()
for item in raw:
if not isinstance(item, str) or not CAPABILITY_RE.fullmatch(item):
raise TaskAdmissionError("TASK_ADMISSION_CAPABILITY_INVALID", 403)
if "*" in item:
raise TaskAdmissionError("TASK_ADMISSION_WILDCARD_CAPABILITY_REJECTED", 403)
values.add(item)
if values & PROHIBITED_CAPABILITIES:
raise TaskAdmissionError("TASK_ADMISSION_PROHIBITED_CAPABILITY", 403)
if not values.issubset(policy.allowed_capabilities):
raise TaskAdmissionError("TASK_ADMISSION_CAPABILITY_OUT_OF_POLICY", 403)
return tuple(sorted(values))
def _risk_rank(value: str) -> int:
ranks = {"low": 0, "medium": 1, "high": 2}
try:
return ranks[value]
except KeyError:
raise TaskAdmissionError("TASK_ADMISSION_RISK_INVALID", 403) from None
def _request_for_digest(
*,
task_id: str,
repository: str,
authority_base_oid: str,
target_branch: str,
generation: int,
execution_digest: str,
requested_capabilities: tuple[str, ...],
file_allowlist: tuple[str, ...],
risk: str,
trust_domain: str,
policy_digest: str,
) -> dict[str, Any]:
return {
"task_id": task_id,
"repository": repository,
"authority_base_oid": authority_base_oid,
"target_branch": target_branch,
"generation": generation,
"execution_digest": execution_digest,
"requested_capabilities": list(requested_capabilities),
"file_allowlist": list(file_allowlist),
"risk": risk,
"trust_domain": trust_domain,
"policy_digest": policy_digest,
}
class TaskAdmissionStore:
"""Portable durable state; all replay decisions are transactionally stored."""
def __init__(self, path: Path) -> None:
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.lock_root = Path(str(self.path) + ".locks")
self.lock_root.mkdir(parents=True, exist_ok=True)
self._lock = threading.RLock()
with self._connection() as con:
con.executescript(
"""
PRAGMA journal_mode=WAL;
CREATE TABLE IF NOT EXISTS task_admission_envelopes(
idempotency_key TEXT PRIMARY KEY,
envelope_id TEXT NOT NULL UNIQUE,
request_digest TEXT NOT NULL,
envelope_json TEXT NOT NULL,
status TEXT NOT NULL,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS task_admission_grants(
envelope_id TEXT PRIMARY KEY,
grant_id TEXT NOT NULL UNIQUE,
grant_digest TEXT NOT NULL,
grant_json TEXT NOT NULL,
authorization_digest TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS task_admission_receipts(
envelope_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL UNIQUE,
receipt_json TEXT NOT NULL,
created_at INTEGER NOT NULL
);
"""
)
columns = {
row["name"] for row in con.execute("PRAGMA table_info(task_admission_grants)")
}
if "authorization_digest" not in columns:
con.execute(
"ALTER TABLE task_admission_grants ADD COLUMN authorization_digest TEXT NOT NULL DEFAULT ''"
)
def _connect(self) -> sqlite3.Connection:
con = sqlite3.connect(self.path, timeout=30, isolation_level=None)
con.row_factory = sqlite3.Row
return con
@contextmanager
def _connection(self):
"""Yield an owned main-store connection and close it deterministically."""
with closing(self._connect()) as con:
try:
yield con
finally:
if con.in_transaction:
con.rollback()
@contextmanager
def admission_lock(self, envelope_id: str, *, timeout_seconds: float = 5.0):
"""Hold a crash-releasing lock for one envelope, outside the main DB."""
lock_name = hashlib.sha256(envelope_id.encode("utf-8")).hexdigest() + ".sqlite3"
lock_path = self.lock_root / lock_name
with closing(sqlite3.connect(lock_path, timeout=timeout_seconds, isolation_level=None)) as con:
try:
con.execute("BEGIN EXCLUSIVE")
except sqlite3.OperationalError as exc:
raise TaskAdmissionError("TASK_ADMISSION_IN_PROGRESS", 503) from exc
try:
yield
con.execute("COMMIT")
finally:
if con.in_transaction:
con.rollback()
def get_by_idempotency(self, key: str) -> dict[str, Any] | None:
with self._connection() as con:
row = con.execute(
"SELECT * FROM task_admission_envelopes WHERE idempotency_key=?",
(key,),
).fetchone()
return dict(row) if row else None
def get_envelope(self, envelope_id: str) -> dict[str, Any] | None:
with self._connection() as con:
row = con.execute(
"SELECT * FROM task_admission_envelopes WHERE envelope_id=?",
(envelope_id,),
).fetchone()
return dict(row) if row else None
def save_envelope(
self,
*,
idempotency_key: str,
envelope_id: str,
request_digest: str,
envelope_json: str,
created_at: int,
) -> dict[str, Any]:
with self._lock, self._connection() as con:
con.execute("BEGIN IMMEDIATE")
row = con.execute(
"SELECT request_digest,envelope_id FROM task_admission_envelopes WHERE idempotency_key=?",
(idempotency_key,),
).fetchone()
if row is not None:
con.execute("ROLLBACK")
if row["request_digest"] != request_digest:
raise TaskAdmissionError("TASK_ADMISSION_IDEMPOTENCY_CONFLICT", 409)
return json.loads(
con.execute(
"SELECT envelope_json FROM task_admission_envelopes WHERE idempotency_key=?",
(idempotency_key,),
).fetchone()["envelope_json"]
)
con.execute(
"""
INSERT INTO task_admission_envelopes(
idempotency_key,envelope_id,request_digest,envelope_json,status,created_at
) VALUES(?,?,?,?,?,?)
""",
(
idempotency_key,
envelope_id,
request_digest,
envelope_json,
"PENDING_AUTHORIZATION",
created_at,
),
)
con.execute("COMMIT")
return json.loads(envelope_json)
def claim_grant(
self,
*,
envelope_id: str,
grant: TaskAdmissionGrant,
authorization_digest: str,
created_at: int,
) -> tuple[TaskAdmissionGrant, bool]:
"""Persist the immutable grant before the external Task side effect.
``BEGIN IMMEDIATE`` serializes only this SQLite writer transaction;
it does not serialize unrelated envelopes. A second proof cannot
replace the winning proof/grant for the same envelope.
"""
grant_json = canonical_json_bytes(grant.as_dict()).decode("utf-8")
grant_digest = hashlib.sha256(grant_json.encode("utf-8")).hexdigest()
with self._lock, self._connection() as con:
con.execute("BEGIN IMMEDIATE")
row = con.execute(
"SELECT grant_json,authorization_digest FROM task_admission_grants WHERE envelope_id=?",
(envelope_id,),
).fetchone()
if row is not None:
con.execute("ROLLBACK")
if row["authorization_digest"] != authorization_digest:
raise TaskAdmissionError("TASK_ADMISSION_AUTHORIZATION_CONFLICT", 409)
raw = json.loads(row["grant_json"])
return (
TaskAdmissionGrant(
**{
**raw,
"capabilities": tuple(raw["capabilities"]),
"file_allowlist": tuple(raw["file_allowlist"]),
}
),
False,
)
con.execute(
"INSERT INTO task_admission_grants(envelope_id,grant_id,grant_digest,grant_json,authorization_digest,created_at) VALUES(?,?,?,?,?,?)",
(envelope_id, grant.grant_id, grant_digest, grant_json, authorization_digest, created_at),
)
con.execute(
"UPDATE task_admission_envelopes SET status='ADMITTING' WHERE envelope_id=?",
(envelope_id,),
)
con.execute("COMMIT")
return grant, True
def receipt(self, envelope_id: str) -> dict[str, Any] | None:
with self._connection() as con:
row = con.execute(
"SELECT receipt_json FROM task_admission_receipts WHERE envelope_id=?",
(envelope_id,),
).fetchone()
return json.loads(row["receipt_json"]) if row else None
def save_grant_and_receipt(
self,
*,
envelope_id: str,
grant: TaskAdmissionGrant,
receipt: Mapping[str, Any],
created_at: int,
) -> dict[str, Any]:
grant_json = canonical_json_bytes(grant.as_dict()).decode("utf-8")
grant_digest = hashlib.sha256(grant_json.encode("utf-8")).hexdigest()
receipt_json = canonical_json_bytes(dict(receipt)).decode("utf-8")
with self._lock, self._connection() as con:
con.execute("BEGIN IMMEDIATE")
existing = con.execute(
"SELECT receipt_json FROM task_admission_receipts WHERE envelope_id=?",
(envelope_id,),
).fetchone()
if existing is not None:
con.execute("ROLLBACK")
return json.loads(existing["receipt_json"])
stored = con.execute(
"SELECT grant_id,grant_digest FROM task_admission_grants WHERE envelope_id=?",
(envelope_id,),
).fetchone()
if stored is None:
con.execute(
"INSERT INTO task_admission_grants(envelope_id,grant_id,grant_digest,grant_json,authorization_digest,created_at) VALUES(?,?,?,?,?,?)",
(envelope_id, grant.grant_id, grant_digest, grant_json, "", created_at),
)
elif stored["grant_id"] != grant.grant_id or stored["grant_digest"] != grant_digest:
con.execute("ROLLBACK")
raise TaskAdmissionError("TASK_ADMISSION_GRANT_CONFLICT", 409)
con.execute(
"""
INSERT INTO task_admission_receipts(
envelope_id,task_id,receipt_json,created_at
) VALUES(?,?,?,?)
""",
(
envelope_id,
grant.task_id,
receipt_json,
created_at,
),
)
con.execute(
"UPDATE task_admission_envelopes SET status='ADMITTED' WHERE envelope_id=?",
(envelope_id,),
)
con.execute("COMMIT")
return dict(receipt)
def status(self, envelope_id: str) -> dict[str, Any]:
row = self.get_envelope(envelope_id)
if row is None:
raise TaskAdmissionError("TASK_ADMISSION_ENVELOPE_NOT_FOUND", 404)
receipt = self.receipt(envelope_id)
return {
"envelope_id": envelope_id,
"status": row["status"],
"task": receipt,
}
class TaskAdmissionBootstrapService:
def __init__(
self,
*,
store: TaskAdmissionStore,
authorization: OwnerAuthorizationBackend,
task_port: TaskAdmissionPort,
policy: TaskAdmissionPolicy | None = None,
clock=utc_seconds,
post_admission: Callable[[TaskAdmissionGrant, Mapping[str, Any]], Any] | None = None,
) -> None:
self.store = store
self.authorization = authorization
self.task_port = task_port
self.policy = policy or TaskAdmissionPolicy()
self.clock = clock
# This is a narrow server-side projection boundary. The official
# Agent Task port remains the admission authority; a projection may
# only establish local execution state after its exact Task readback.
self.post_admission = post_admission
self._admit_lock = threading.RLock()
def create_envelope(self, raw: Mapping[str, Any]) -> dict[str, Any]:
required = {
"idempotency_key",
"task_id",
"repository",
"authority_base_oid",
"target_branch",
"generation",
"execution_digest",
"requested_capabilities",
"file_allowlist",
"risk",
}
optional = {"expires_in_seconds"}
if set(raw) - (required | optional) or not required.issubset(raw):
raise TaskAdmissionError("TASK_ADMISSION_REQUEST_FIELDS_INVALID")
idempotency_key = str(raw["idempotency_key"])
task_id = str(raw["task_id"])
repository = str(raw["repository"])
authority_base_oid = str(raw["authority_base_oid"])
target_branch = str(raw["target_branch"])
execution_digest = str(raw["execution_digest"])
if not IDEMPOTENCY_RE.fullmatch(idempotency_key):
raise TaskAdmissionError("TASK_ADMISSION_IDEMPOTENCY_KEY_INVALID")
if not TASK_RE.fullmatch(task_id):
raise TaskAdmissionError("TASK_ADMISSION_TASK_ID_INVALID")
if repository != self.policy.repository:
raise TaskAdmissionError("TASK_ADMISSION_REPOSITORY_REJECTED", 403)
if not OID_RE.fullmatch(authority_base_oid):
raise TaskAdmissionError("TASK_ADMISSION_BASE_OID_INVALID")
if (
not BRANCH_RE.fullmatch(target_branch)
or not target_branch.startswith(self.policy.branch_prefix)
or "//" in target_branch
or ".." in target_branch
or "@{" in target_branch
or target_branch.endswith(("/", "."))
or any(
not component
or component.startswith(".")
or component.endswith(".lock")
for component in target_branch.split("/")
)
):
raise TaskAdmissionError("TASK_ADMISSION_BRANCH_REJECTED", 403)
generation = raw["generation"]
if not isinstance(generation, int) or isinstance(generation, bool):
raise TaskAdmissionError("TASK_ADMISSION_GENERATION_INVALID")
if generation < 1:
raise TaskAdmissionError("TASK_ADMISSION_GENERATION_INVALID")
if not re.fullmatch(r"^[0-9a-f]{64}$", execution_digest):
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_DIGEST_INVALID")
requested_capabilities = _normalize_capabilities(
raw["requested_capabilities"],
policy=self.policy,
)
file_allowlist = _normalize_files(
raw["file_allowlist"],
maximum=self.policy.max_file_count,
)
risk = str(raw["risk"])
if _risk_rank(risk) > _risk_rank(self.policy.max_risk):
raise TaskAdmissionError("TASK_ADMISSION_RISK_OUT_OF_POLICY", 403)
ttl = raw.get("expires_in_seconds", self.policy.max_envelope_ttl_seconds)
if (
not isinstance(ttl, int)
or isinstance(ttl, bool)
or ttl < 60
or ttl > self.policy.max_envelope_ttl_seconds
):
raise TaskAdmissionError("TASK_ADMISSION_ENVELOPE_TTL_INVALID")
now = int(self.clock())
policy_digest = self.policy.normalized_policy_digest()
request_value = _request_for_digest(
task_id=task_id,
repository=repository,
authority_base_oid=authority_base_oid,
target_branch=target_branch,
generation=generation,
execution_digest=execution_digest,
requested_capabilities=requested_capabilities,
file_allowlist=file_allowlist,
risk=risk,
trust_domain=self.policy.trust_domain,
policy_digest=policy_digest,
)
request_digest = sha256_json(request_value)
nonce = secrets.token_urlsafe(24)
envelope_id = "task-admission-" + hashlib.sha256(
(idempotency_key + "\0" + request_digest + "\0" + nonce).encode("utf-8")
).hexdigest()[:32]
envelope = TaskAdmissionEnvelope(
envelope_id=envelope_id,
idempotency_key=idempotency_key,
task_id=task_id,
repository=repository,
authority_base_oid=authority_base_oid,
target_branch=target_branch,
generation=generation,
execution_digest=execution_digest,
requested_capabilities=requested_capabilities,
file_allowlist=file_allowlist,
risk=risk,
trust_domain=self.policy.trust_domain,
policy_digest=policy_digest,
issued_at=now,
not_before=now,
expires_at=now + ttl,
nonce=nonce,
request_digest=request_digest,
)
envelope_dict = envelope.as_dict()
persisted = self.store.save_envelope(
idempotency_key=idempotency_key,
envelope_id=envelope_id,
request_digest=request_digest,
envelope_json=canonical_json_bytes(envelope_dict).decode("utf-8"),
created_at=now,
)
won = persisted["envelope_id"] == envelope_id
return {
"schema": SCHEMA_ENVELOPE,
"envelope": persisted,
"canonical_payload_base64": base64.b64encode(
canonical_json_bytes(persisted)
).decode("ascii"),
"idempotent": not won,
}
def _load_envelope(self, envelope_id: str) -> TaskAdmissionEnvelope:
row = self.store.get_envelope(envelope_id)
if row is None:
raise TaskAdmissionError("TASK_ADMISSION_ENVELOPE_NOT_FOUND", 404)
raw = json.loads(row["envelope_json"])
return TaskAdmissionEnvelope(
**{
**raw,
"requested_capabilities": tuple(raw["requested_capabilities"]),
"file_allowlist": tuple(raw["file_allowlist"]),
}
)
def _verify_epoch(
self,
*,
epoch_id: str,
envelope: TaskAdmissionEnvelope,
now: int,
) -> OwnerDelegationEpoch:
epoch = self.authorization.verify_delegation_epoch(epoch_id)
if epoch.schema != SCHEMA_EPOCH:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_SCHEMA_INVALID", 403)
if epoch.epoch_id != epoch_id:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_ID_MISMATCH", 403)
if (
epoch.trust_domain != envelope.trust_domain
or epoch.repository != envelope.repository
or epoch.policy_digest != envelope.policy_digest
):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_BINDING_MISMATCH", 403)
if epoch.revocation_epoch != 0:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REVOKED", 403)
if not epoch.not_before <= now < epoch.expires_at:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_EXPIRED", 403)
if not any(
envelope.target_branch.startswith(prefix)
for prefix in epoch.allowed_branch_prefixes
):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_BRANCH_REJECTED", 403)
caps = set(envelope.requested_capabilities)
if not caps.issubset(epoch.allowed_capabilities):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_CAPABILITY_WIDENING", 403)
if _risk_rank(envelope.risk) > _risk_rank(epoch.risk_ceiling):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_RISK_WIDENING", 403)
for path in envelope.file_allowlist:
if not any(
path == prefix.rstrip("/")
or path.startswith(prefix.rstrip("/") + "/")
for prefix in epoch.allowed_file_prefixes
):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_FILE_WIDENING", 403)
return epoch
def authorize_and_admit(self, raw: Mapping[str, Any]) -> dict[str, Any]:
required = {"envelope_id", "authorization_kind"}
allowed = required | {
"delegation_epoch_id",
"owner_key_id",
"signature_base64",
}
if set(raw) - allowed or not required.issubset(raw):
raise TaskAdmissionError("TASK_ADMISSION_AUTH_FIELDS_INVALID")
envelope_id = str(raw["envelope_id"])
with self.store.admission_lock(envelope_id):
# Every replay revalidates policy, temporal bounds, revocation and
# proof after the crash-releasing lock is owned.
envelope = self._load_envelope(envelope_id)
now = int(self.clock())
if envelope.policy_digest != self.policy.normalized_policy_digest():
raise TaskAdmissionError("TASK_ADMISSION_POLICY_DIGEST_DRIFT", 403)
if now < envelope.not_before or now >= envelope.expires_at:
raise TaskAdmissionError("TASK_ADMISSION_ENVELOPE_EXPIRED", 403)
kind = str(raw["authorization_kind"])
parent_epoch: OwnerDelegationEpoch | None = None
if kind == "delegation_epoch":
epoch_id = str(raw.get("delegation_epoch_id") or "")
if not epoch_id:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REQUIRED")
parent_epoch = self._verify_epoch(epoch_id=epoch_id, envelope=envelope, now=now)
owner_identity = parent_epoch.owner_identity
parent_epoch_id: str | None = parent_epoch.epoch_id
elif kind == "owner_signature":
key_id = str(raw.get("owner_key_id") or "")
signature = str(raw.get("signature_base64") or "")
if not KEY_ID_RE.fullmatch(key_id) or not signature:
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_REQUIRED")
owner_identity = self.authorization.verify_owner_signature(
key_id=key_id,
canonical_payload=canonical_json_bytes(envelope.as_dict()),
signature_b64=signature,
)
if not isinstance(owner_identity, str) or not owner_identity:
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403)
parent_epoch_id = None
else:
raise TaskAdmissionError("TASK_ADMISSION_AUTHORIZATION_KIND_REJECTED", 403)
authorization_digest = sha256_json(dict(raw))
grant_id = "task-grant-" + hashlib.sha256(
(envelope.envelope_id + "\0" + kind + "\0" + owner_identity + "\0" + (parent_epoch_id or "")).encode("utf-8")
).hexdigest()[:32]
grant = TaskAdmissionGrant(
grant_id=grant_id, envelope_id=envelope.envelope_id, task_id=envelope.task_id,
repository=envelope.repository, authority_base_oid=envelope.authority_base_oid,
target_branch=envelope.target_branch, generation=envelope.generation,
execution_digest=envelope.execution_digest, capabilities=envelope.requested_capabilities,
file_allowlist=envelope.file_allowlist, risk=envelope.risk,
trust_domain=envelope.trust_domain, policy_digest=envelope.policy_digest,
authorization_kind=kind, owner_identity=owner_identity,
parent_epoch_id=parent_epoch_id, issued_at=now,
expires_at=min(
envelope.expires_at,
now + self.policy.max_task_ttl_seconds,
parent_epoch.expires_at if parent_epoch is not None else envelope.expires_at,
),
)
persisted_grant, _ = self.store.claim_grant(
envelope_id=envelope_id, grant=grant,
authorization_digest=authorization_digest, created_at=now,
)
if now >= persisted_grant.expires_at:
raise TaskAdmissionError("TASK_ADMISSION_GRANT_EXPIRED", 403)
if parent_epoch is not None and persisted_grant.expires_at > parent_epoch.expires_at:
raise TaskAdmissionError("TASK_ADMISSION_GRANT_PARENT_EXPIRY_CONFLICT", 409)
existing_receipt = self.store.receipt(envelope_id)
if existing_receipt is not None:
return {**existing_receipt, "idempotent": True}
try:
result = self.task_port.admit(persisted_grant)
except TaskAdmissionError:
raise
except Exception as exc:
raise TaskAdmissionError("TASK_ADMISSION_TASK_STATE_UNKNOWN", 503) from exc
if not isinstance(result, Mapping):
raise TaskAdmissionError("TASK_ADMISSION_PORT_RESPONSE_INVALID", 502)
state = str(result.get("state") or result.get("status") or "")
if str(result.get("task_id") or "") != persisted_grant.task_id:
raise TaskAdmissionError("TASK_ADMISSION_PORT_TASK_MISMATCH", 502)
if state not in {"QUEUED", "CLAIMED", "RUNNING"}:
raise TaskAdmissionError("TASK_ADMISSION_PORT_STATE_INVALID", 502, details={"state": state})
if self.post_admission is not None:
try:
projection = self.post_admission(persisted_grant, result)
except TaskAdmissionError:
raise
except Exception as exc:
raise TaskAdmissionError(
"TASK_ADMISSION_EXECUTION_PROJECTION_FAILED", 503
) from exc
if projection is not None and not isinstance(projection, Mapping):
raise TaskAdmissionError(
"TASK_ADMISSION_EXECUTION_PROJECTION_INVALID", 503
)
receipt = {
"schema": SCHEMA_RECEIPT, "envelope_id": persisted_grant.envelope_id,
"grant_id": persisted_grant.grant_id, "task_id": persisted_grant.task_id,
"task_state": state, "authorization_kind": persisted_grant.authorization_kind,
"owner_identity": persisted_grant.owner_identity, "parent_epoch_id": persisted_grant.parent_epoch_id,
"authority_base_oid": persisted_grant.authority_base_oid,
"target_branch": persisted_grant.target_branch, "generation": persisted_grant.generation,
"execution_digest": persisted_grant.execution_digest,
"grant_digest_sha256": sha256_json(persisted_grant.as_dict()),
"admitted_at": now, "idempotent": False,
}
return self.store.save_grant_and_receipt(
envelope_id=envelope_id, grant=persisted_grant, receipt=receipt, created_at=now,
)
def status(self, envelope_id: str) -> dict[str, Any]:
return self.store.status(envelope_id)
@@ -0,0 +1,87 @@
"""Bridge contract surface for Task Admission Bootstrap.
This module is intentionally transport-agnostic. The live Bridge may merge
these routes/operations into its existing authenticated/HTTPS surface, but the
authorization proof for the authorize operation is the verified delegation
epoch or Owner signature carried by the request itself.
No handler here can issue TaskPrincipal assertions or WriterLeases. Those remain
strictly downstream of a real admitted/claimed Agent Task.
"""
from __future__ import annotations
from typing import Any, Mapping
from aios_task_admission_bootstrap import TaskAdmissionBootstrapService
TASK_ADMISSION_ROUTES = {
"/v2/task-admission/envelope": "getTaskAdmissionEnvelope",
"/v2/task-admission/authorize": "submitTaskAdmissionAuthorization",
"/v2/task-admission/status": "getTaskAdmissionStatus",
}
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS = {
"getTaskAdmissionEnvelope": (
"idempotency_key",
"task_id",
"repository",
"authority_base_oid",
"target_branch",
"generation",
"execution_digest",
"requested_capabilities",
"file_allowlist",
"risk",
),
"submitTaskAdmissionAuthorization": (
"envelope_id",
"authorization_kind",
),
"getTaskAdmissionStatus": ("envelope_id",),
}
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS = {
"getTaskAdmissionEnvelope": ("expires_in_seconds",),
"submitTaskAdmissionAuthorization": (
"delegation_epoch_id",
"owner_key_id",
"signature_base64",
),
"getTaskAdmissionStatus": (),
}
class TaskAdmissionBridgeContract:
def __init__(self, service: TaskAdmissionBootstrapService) -> None:
self.service = service
@staticmethod
def _validate_fields(operation: str, request: Mapping[str, Any]) -> None:
required = set(TASK_ADMISSION_OPERATION_REQUIRED_FIELDS[operation])
allowed = required | set(TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS[operation])
if not required.issubset(request):
raise ValueError("TASK_ADMISSION_BRIDGE_REQUIRED_FIELDS_MISSING")
if set(request) - allowed:
raise ValueError("TASK_ADMISSION_BRIDGE_UNKNOWN_FIELDS")
def dispatch(self, operation: str, request: Mapping[str, Any]) -> dict[str, Any]:
if operation not in TASK_ADMISSION_OPERATION_REQUIRED_FIELDS:
raise ValueError("TASK_ADMISSION_BRIDGE_OPERATION_NOT_FOUND")
self._validate_fields(operation, request)
if operation == "getTaskAdmissionEnvelope":
return self.service.create_envelope(request)
if operation == "submitTaskAdmissionAuthorization":
return self.service.authorize_and_admit(request)
if operation == "getTaskAdmissionStatus":
return self.service.status(str(request["envelope_id"]))
raise ValueError("TASK_ADMISSION_BRIDGE_OPERATION_NOT_FOUND")
def handlers(self) -> dict[str, Any]:
return {
operation: (
lambda request, operation=operation: self.dispatch(operation, request)
)
for operation in TASK_ADMISSION_OPERATION_REQUIRED_FIELDS
}
@@ -0,0 +1,818 @@
"""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 importlib.util
import json
import os
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,
TaskAdmissionError,
TaskAdmissionGrant,
canonical_json_bytes,
sha256_json,
)
OWNER_NAMESPACE = "aios-owner-approval-v1"
OWNER_SIGNER_IDENTITY = "aios-task-grant-signer"
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})
# Preserve the immutable external execution identity for the
# post-admission Bridge projection. The Task API remains the source
# of this value; the caller cannot provide or override it.
execution_job_id = execution.get("external_job_id")
if not isinstance(execution_job_id, str) or not execution_job_id.strip():
raise TaskAdmissionError("TASK_ADMISSION_EXECUTION_PLAN_INVALID", 403)
return {
"task_id": grant.task_id,
"state": state,
"external_job_id": execution_job_id,
}
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
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,
)
if value.get("schema") != "aios.owner-delegation-epoch.v1":
raise TaskAdmissionError(
"TASK_ADMISSION_EPOCH_SCHEMA_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)
key_id = key_id_file.read_text(encoding="utf-8").strip()
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(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 all(isinstance(item, str) and item for item in branches)
or not isinstance(capabilities, list)
or not all(
isinstance(item, str) and item
for item in capabilities
)
or not isinstance(files, list)
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"]),
)
@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_handlers(
*,
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,
) -> Mapping[str, Any]:
"""Compose handlers from already-trusted server-side coordinates."""
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,
)
task_port = AgentTaskAdmissionPort(
LocalAgentTaskHTTPTransport(
base_url=task_api_url,
token_file=task_control_token_file,
require_root_owner=True,
),
execution_plan_provider=ExecutionPlanDirectoryProvider(
root=execution_plan_dir,
require_root_owner=True,
),
)
service = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(state_db),
authorization=authorization,
task_port=task_port,
policy=task_admission_policy_from_epoch(epoch),
post_admission=post_admission,
)
return TaskAdmissionBridgeContract(service).handlers()
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]:
"""Load one root-owned, fail-closed staging activation descriptor."""
_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_handlers(
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_environment(
*,
caller_service: str = "aios-chatgpt-bridge-v2-staging.service",
post_admission: Callable[[TaskAdmissionGrant, Mapping[str, Any]], Any] | None = None,
) -> Mapping[str, Any]:
"""Compose all three handlers from server-side trusted environment only."""
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_handlers(
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,
)
@@ -0,0 +1,642 @@
"""Generic append-only TaskPrincipal scope transition for P0 admission.
The live issuer currently imports an R10 overlay whose public supersession
function is intentionally bound to one historical recovery chain. This
module is a controlled staging import override for the next P0 task lines. It
loads that overlay by its immutable path, preserves its validation and issuer
implementation, and adds one small generic transition table.
The transition is deliberately narrower than a new trust bootstrap:
* the existing issuer key, public key, purpose, and Bridge identity remain
unchanged;
* the new scope must preserve the existing operation capability and audience
set, while binding to a distinct claimed Agent Task;
* the signed grant is verified by the registered Owner verifier;
* only INSERTs are performed, and SQLite UPDATE/DELETE triggers protect the
new table as well as the existing principal tables;
* TaskPrincipal issuance still authenticates the exact live TaskLease later.
No serving constructor performs the migration. Deployment calls
``offline_migrate_task_principal_schema`` once through this module before the
issuer is restarted.
"""
from __future__ import annotations
from contextlib import closing
import importlib.util
from pathlib import Path
import hashlib
import json
import sqlite3
from typing import Any, Callable, Mapping
_CURRENT_OVERLAY_PATH = Path(
"/opt/aios/stage9-r10-formal-lineage-20260902/app/"
"aios_task_principal_admission.py"
)
_SPEC = importlib.util.spec_from_file_location(
"aios_task_principal_admission_r10_overlay_for_p0_generic",
_CURRENT_OVERLAY_PATH,
)
if _SPEC is None or _SPEC.loader is None:
raise ImportError("TASK_PRINCIPAL_R10_OVERLAY_UNAVAILABLE")
_current = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(_current)
# Keep the public legacy API and the R10 implementation available to the
# issuer/Bridge. The generic functions below replace only the two explicit
# semantic-port entry points and the active-chain/readiness globals.
for _name, _value in _current.__dict__.items():
if not _name.startswith("__"):
globals()[_name] = _value
_legacy = _current._legacy
_BASE_R10_ACTIVE_SCOPE_CHAIN = _current._active_scope_chain
GENERIC_SCOPE_TABLE = "task_principal_p0_task_scope_supersession_grants"
GENERIC_SCOPE_SCHEMA = "aios.task-principal-task-scope-supersession.v1"
GENERIC_SIGNER_NAMESPACE = "aios-owner-approval-v1"
GENERIC_SIGNER_IDENTITY = "aios-task-grant-signer"
GENERIC_SUPERSESSION_REASON = "P0_TASK_ADMISSION_SCOPE_SUPERSESSION"
MAX_GENERIC_TRANSITIONS = 8
STABLE_OPERATION_CAPABILITIES = (
"git.read",
"git.test",
"governed.write.branch",
"governed.write.file",
)
def _table_statement() -> str:
return """CREATE TABLE IF NOT EXISTS task_principal_p0_task_scope_supersession_grants(
supersession_id TEXT PRIMARY KEY, grant_sha256 TEXT NOT NULL UNIQUE,
previous_task_id TEXT NOT NULL, previous_external_job_id TEXT NOT NULL,
previous_generation INTEGER NOT NULL, previous_bridge_id TEXT NOT NULL,
previous_trust_manifest_sha256 TEXT NOT NULL,
previous_scope_manifest_sha256 TEXT NOT NULL, previous_scope_id TEXT NOT NULL,
task_id TEXT NOT NULL, external_job_id TEXT NOT NULL, repository TEXT NOT NULL,
target_branch TEXT NOT NULL, generation INTEGER NOT NULL,
execution_digest TEXT NOT NULL, capabilities_json TEXT NOT NULL,
audiences_json TEXT NOT NULL, new_bridge_id TEXT NOT NULL,
new_trust_manifest_sha256 TEXT NOT NULL UNIQUE,
new_scope_manifest_sha256 TEXT NOT NULL, new_scope_id TEXT NOT NULL UNIQUE,
signer_namespace TEXT NOT NULL, signer_identity TEXT NOT NULL,
owner_key_id TEXT NOT NULL, issued_at INTEGER NOT NULL,
not_before INTEGER NOT NULL, expires_at INTEGER NOT NULL,
one_time_use INTEGER NOT NULL, supersession_reason TEXT NOT NULL,
signature TEXT NOT NULL, applied_at INTEGER NOT NULL
)"""
def _trigger_name(event: str) -> str:
return "aios_append_only_%s_%s" % (GENERIC_SCOPE_TABLE, event.lower())
def _trigger_statement(event: str, *, if_not_exists: bool = False) -> str:
qualifier = " IF NOT EXISTS" if if_not_exists else ""
return (
"CREATE TRIGGER%s %s BEFORE %s ON %s "
"BEGIN SELECT RAISE(ABORT,'TASK_PRINCIPAL_APPEND_ONLY'); END"
) % (qualifier, _trigger_name(event), event.upper(), GENERIC_SCOPE_TABLE)
def _normalized_schema_sql(value: Any) -> str:
if not isinstance(value, str):
return ""
return (
" ".join(value.strip().split()).lower()
.replace("create table if not exists ", "create table ")
.replace("create trigger if not exists ", "create trigger ")
)
def _validate_generic_schema(con: sqlite3.Connection) -> None:
table = con.execute(
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
(GENERIC_SCOPE_TABLE,),
).fetchone()
if table is None or _normalized_schema_sql(table[0]) != _normalized_schema_sql(_table_statement()):
_legacy._fail("P0_GENERIC_SCOPE_SCHEMA_INVALID")
expected = {
_trigger_name("UPDATE"): _trigger_statement("UPDATE"),
_trigger_name("DELETE"): _trigger_statement("DELETE"),
}
observed = {
str(row[0]): row[1]
for row in con.execute(
"SELECT name,sql FROM sqlite_master WHERE type='trigger' AND tbl_name=?",
(GENERIC_SCOPE_TABLE,),
)
}
if set(observed) != set(expected) or any(
_normalized_schema_sql(observed[name]) != _normalized_schema_sql(sql)
for name, sql in expected.items()
):
_legacy._fail("P0_GENERIC_SCOPE_APPEND_ONLY_GUARD_INVALID")
def offline_migrate_task_principal_schema(db_path: str | Path) -> None:
"""Run the existing offline migration and add the generic table safely."""
_legacy.offline_migrate_task_principal_schema(db_path)
with closing(_legacy._db(db_path)) as con:
con.execute("BEGIN IMMEDIATE")
existing = con.execute(
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
(GENERIC_SCOPE_TABLE,),
).fetchone()
if existing is not None and _normalized_schema_sql(existing[0]) != _normalized_schema_sql(_table_statement()):
_legacy._fail("P0_GENERIC_SCOPE_SCHEMA_INVALID")
observed = {
str(row[0]): row[1]
for row in con.execute(
"SELECT name,sql FROM sqlite_master WHERE type='trigger' AND tbl_name=?",
(GENERIC_SCOPE_TABLE,),
)
}
expected = {
_trigger_name("UPDATE"): _trigger_statement("UPDATE"),
_trigger_name("DELETE"): _trigger_statement("DELETE"),
}
if set(observed) - set(expected):
_legacy._fail("P0_GENERIC_SCOPE_TRIGGER_INVENTORY_INVALID")
con.execute(_table_statement())
for event, statement in (("UPDATE", expected[_trigger_name("UPDATE")]), ("DELETE", expected[_trigger_name("DELETE")])):
name = _trigger_name(event)
if name not in observed:
con.execute(_trigger_statement(event, if_not_exists=True))
else:
if _normalized_schema_sql(observed[name]) != _normalized_schema_sql(statement):
_legacy._fail("P0_GENERIC_SCOPE_APPEND_ONLY_GUARD_INVALID")
_validate_generic_schema(con)
con.commit()
def _text(value: Any, field: str, *, max_len: int = 512) -> str:
return _legacy._text(value, field, max_len=max_len)
def _hex(value: Any, field: str) -> str:
return _legacy._hex(value, field)
def _integer(value: Any, field: str, low: int = 0) -> int:
return _legacy._integer(value, field, low, 2**63 - 1)
def _scope_from_row(row: sqlite3.Row) -> dict[str, Any]:
return _legacy._scope_manifest({
"scope_id": row["scope_id"],
"task_id": row["task_id"],
"external_job_id": row["external_job_id"],
"repository": row["repository"],
"target_branch": row["target_branch"],
"generation": int(row["generation"]),
"execution_digest": row["execution_digest"],
"capabilities": _legacy._load_json_text(row["capabilities_json"], "P0_GENERIC_SCOPE_CAPABILITIES_INVALID"),
"audiences": _legacy._load_json_text(row["audiences_json"], "P0_GENERIC_SCOPE_AUDIENCES_INVALID"),
})
def _register_trust(con: sqlite3.Connection, scope: sqlite3.Row, digest: str) -> dict[str, Any]:
event = con.execute(
"SELECT * FROM task_principal_trust_events "
"WHERE event_type='REGISTER' AND scope_id=? AND trust_manifest_sha256=?",
(scope["scope_id"], digest),
).fetchone()
if event is None:
_legacy._fail("P0_GENERIC_SCOPE_TRUST_EVENT_MISSING")
trust = {
"schema": _legacy.ASSERTION_SCHEMA,
"issuer_id": event["issuer_id"],
"key_id": event["key_id"],
"public_key": event["public_key"],
"purpose": "aios.task-principal.p0",
"bridge_id": _bridge_for_scope(con, scope["scope_id"], digest),
"scope_manifest_sha256": _legacy.sha256_canonical(_scope_from_row(scope)),
}
if _legacy.sha256_canonical(trust) != digest:
_legacy._fail("P0_GENERIC_SCOPE_TRUST_EVENT_INVALID")
return trust
def _bridge_for_scope(con: sqlite3.Connection, scope_id: str, digest: str) -> str:
"""Resolve the bridge identity from its immutable transition record."""
row = con.execute(
"SELECT new_bridge_id FROM " + GENERIC_SCOPE_TABLE +
" WHERE new_scope_id=? AND new_trust_manifest_sha256=?",
(scope_id, digest),
).fetchone()
if row is not None:
return str(row[0])
rebind = con.execute(
"SELECT new_bridge_id FROM task_principal_artifact_rebind_grants "
"WHERE new_scope_id=? AND new_trust_manifest_sha256=?",
(scope_id, digest),
).fetchone()
if rebind is not None:
return str(rebind[0])
rollover = con.execute(
"SELECT new_bridge_id FROM task_principal_execution_scope_rollover_grants "
"WHERE new_scope_id=? AND new_trust_manifest_sha256=?",
(scope_id, digest),
).fetchone()
if rollover is not None:
return str(rollover[0])
bootstrap = con.execute(
"SELECT bridge_id FROM task_principal_owner_bootstrap_grants "
"WHERE trust_manifest_sha256=?",
(digest,),
).fetchone()
if bootstrap is not None:
return str(bootstrap[0])
# The base/R10 chain has already authenticated this scope, but it does not
# expose a bridge column on scope rows. A matching REGISTER event without
# a transition record is only acceptable for the initial bootstrap row;
# anything else is fail-closed.
_legacy._fail("P0_GENERIC_SCOPE_BRIDGE_UNRESOLVED")
return ""
def _grant_payload(value: Mapping[str, Any]) -> dict[str, Any]:
x = _legacy._json_object(value, "P0_GENERIC_SCOPE_GRANT_INVALID")
fields = {
"schema", "supersession_id", "previous_task_id", "previous_external_job_id",
"previous_generation", "previous_bridge_id", "previous_trust_manifest_sha256",
"previous_scope_manifest_sha256", "previous_scope_id", "task_id",
"external_job_id", "repository", "target_branch", "generation",
"execution_digest", "capabilities", "audiences", "new_bridge_id",
"new_trust_manifest_sha256", "new_scope_manifest_sha256", "new_scope_id",
"signer_namespace", "signer_identity", "owner_key_id", "issued_at",
"not_before", "expires_at", "one_time_use", "supersession_reason", "signature",
}
if set(x) != fields or x.get("schema") != GENERIC_SCOPE_SCHEMA or x.get("one_time_use") is not True:
_legacy._fail("P0_GENERIC_SCOPE_GRANT_SCHEMA_INVALID")
for key in (
"supersession_id", "previous_task_id", "previous_external_job_id", "previous_bridge_id",
"previous_scope_id", "task_id", "external_job_id", "repository", "target_branch",
"new_bridge_id", "new_scope_id", "signer_namespace", "signer_identity", "owner_key_id",
"supersession_reason",
):
_text(x.get(key), key)
for key in (
"previous_trust_manifest_sha256", "previous_scope_manifest_sha256",
"execution_digest", "new_trust_manifest_sha256", "new_scope_manifest_sha256",
):
_hex(x.get(key), key)
for key in ("previous_generation", "generation", "issued_at", "not_before", "expires_at"):
_integer(x.get(key), key)
if not 1 <= int(x["expires_at"]) - int(x["issued_at"]) <= 3600:
_legacy._fail("P0_GENERIC_SCOPE_GRANT_TIME_INVALID")
if x["issued_at"] != x["not_before"]:
_legacy._fail("P0_GENERIC_SCOPE_GRANT_TIME_INVALID")
if not str(x["supersession_id"]).startswith("p0-task-scope-supersession-"):
_legacy._fail("P0_GENERIC_SCOPE_ID_INVALID")
if x["signer_namespace"] != GENERIC_SIGNER_NAMESPACE or x["signer_identity"] != GENERIC_SIGNER_IDENTITY:
_legacy._fail("P0_GENERIC_SCOPE_SIGNER_INVALID")
if x["supersession_reason"] != GENERIC_SUPERSESSION_REASON:
_legacy._fail("P0_GENERIC_SCOPE_REASON_INVALID")
if not isinstance(x["capabilities"], list) or not x["capabilities"]:
_legacy._fail("P0_GENERIC_SCOPE_CAPABILITIES_INVALID")
if not isinstance(x["audiences"], list) or not x["audiences"]:
_legacy._fail("P0_GENERIC_SCOPE_AUDIENCES_INVALID")
if not isinstance(x["signature"], str) or not x["signature"] or len(x["signature"]) > 32768:
_legacy._fail("P0_GENERIC_SCOPE_SIGNATURE_INVALID")
return x
def _verify_grant_signature(grant: Mapping[str, Any], owner_verifier: Any) -> None:
verifier = getattr(owner_verifier, "verify", owner_verifier)
if not callable(verifier):
_legacy._fail("P0_GENERIC_SCOPE_VERIFIER_REQUIRED")
try:
verified = verifier(
namespace=GENERIC_SIGNER_NAMESPACE,
payload=_legacy.canonical_json({key: value for key, value in grant.items() if key != "signature"}),
signature=grant["signature"],
key_id=grant["owner_key_id"],
)
except Exception as exc:
raise _legacy.TaskPrincipalError("TASK_PRINCIPAL_P0_GENERIC_SCOPE_SIGNATURE_INVALID") from exc
if verified is not True:
_legacy._fail("P0_GENERIC_SCOPE_SIGNATURE_INVALID")
def _row_payload(record: sqlite3.Row) -> dict[str, Any]:
return {
"schema": GENERIC_SCOPE_SCHEMA,
"supersession_id": record["supersession_id"],
"previous_task_id": record["previous_task_id"],
"previous_external_job_id": record["previous_external_job_id"],
"previous_generation": int(record["previous_generation"]),
"previous_bridge_id": record["previous_bridge_id"],
"previous_trust_manifest_sha256": record["previous_trust_manifest_sha256"],
"previous_scope_manifest_sha256": record["previous_scope_manifest_sha256"],
"previous_scope_id": record["previous_scope_id"],
"task_id": record["task_id"],
"external_job_id": record["external_job_id"],
"repository": record["repository"],
"target_branch": record["target_branch"],
"generation": int(record["generation"]),
"execution_digest": record["execution_digest"],
"capabilities": _legacy._load_json_text(record["capabilities_json"], "P0_GENERIC_SCOPE_CAPABILITIES_INVALID"),
"audiences": _legacy._load_json_text(record["audiences_json"], "P0_GENERIC_SCOPE_AUDIENCES_INVALID"),
"new_bridge_id": record["new_bridge_id"],
"new_trust_manifest_sha256": record["new_trust_manifest_sha256"],
"new_scope_manifest_sha256": record["new_scope_manifest_sha256"],
"new_scope_id": record["new_scope_id"],
"signer_namespace": record["signer_namespace"],
"signer_identity": record["signer_identity"],
"owner_key_id": record["owner_key_id"],
"issued_at": int(record["issued_at"]),
"not_before": int(record["not_before"]),
"expires_at": int(record["expires_at"]),
"one_time_use": bool(record["one_time_use"]),
"supersession_reason": record["supersession_reason"],
"signature": record["signature"],
}
def _validate_generic_record(
con: sqlite3.Connection,
record: sqlite3.Row,
previous_digest: str,
previous_scope: sqlite3.Row,
previous_bridge: str,
previous_applied_at: int | None,
) -> tuple[str, sqlite3.Row, str, int]:
if int(record["one_time_use"]) != 1:
_legacy._fail("P0_GENERIC_SCOPE_CHAIN_INVALID")
grant = _grant_payload(_row_payload(record))
grant_sha = _legacy.sha256_canonical(grant)
if record["grant_sha256"] != grant_sha:
_legacy._fail("P0_GENERIC_SCOPE_GRANT_DIGEST_INVALID")
if (
grant["previous_trust_manifest_sha256"] != previous_digest
or grant["previous_scope_id"] != previous_scope["scope_id"]
or grant["previous_bridge_id"] != previous_bridge
or grant["previous_task_id"] != previous_scope["task_id"]
or grant["previous_external_job_id"] != previous_scope["external_job_id"]
or int(grant["previous_generation"]) != int(previous_scope["generation"])
or grant["previous_scope_manifest_sha256"] != _legacy.sha256_canonical(_legacy._scope_material(previous_scope))
or record["applied_at"] < grant["not_before"]
or record["applied_at"] >= grant["expires_at"]
or (previous_applied_at is not None and record["applied_at"] < previous_applied_at)
):
_legacy._fail("P0_GENERIC_SCOPE_CHAIN_INVALID")
scope = con.execute(
"SELECT * FROM task_principal_admission_scopes WHERE scope_id=?",
(grant["new_scope_id"],),
).fetchone()
if scope is None:
_legacy._fail("P0_GENERIC_SCOPE_CHAIN_INVALID")
material = _scope_from_row(scope)
new_digest = str(grant["new_trust_manifest_sha256"])
new_trust = _register_trust(con, scope, new_digest)
if (
grant["new_scope_manifest_sha256"] != _legacy.sha256_canonical(material)
or grant["task_id"] != material["task_id"]
or grant["external_job_id"] != material["external_job_id"]
or grant["repository"] != material["repository"]
or grant["target_branch"] != material["target_branch"]
or int(grant["generation"]) != int(material["generation"])
or grant["execution_digest"] != material["execution_digest"]
or grant["capabilities"] != material["capabilities"]
or grant["audiences"] != material["audiences"]
or grant["new_bridge_id"] != new_trust["bridge_id"]
or new_trust["bridge_id"] != previous_bridge
):
_legacy._fail("P0_GENERIC_SCOPE_CHAIN_INVALID")
if set(material["capabilities"]) != set(STABLE_OPERATION_CAPABILITIES):
_legacy._fail("P0_GENERIC_SCOPE_CAPABILITY_EXPANSION")
if _legacy._scope_suffixes(material, new_trust["bridge_id"]) != _legacy._scope_suffixes(
_legacy._scope_material(previous_scope), previous_bridge
):
_legacy._fail("P0_GENERIC_SCOPE_AUDIENCE_EXPANSION")
if con.execute(
"SELECT 1 FROM task_principal_revocations WHERE kind='SCOPE' AND target=?",
(previous_scope["scope_id"],),
).fetchone() is None:
_legacy._fail("P0_GENERIC_SCOPE_PREVIOUS_NOT_REVOKED")
return new_digest, scope, new_trust["bridge_id"], int(record["applied_at"])
def _active_scope_chain(con: sqlite3.Connection) -> tuple[str, sqlite3.Row]:
_validate_generic_schema(con)
current_digest, current_scope = _BASE_R10_ACTIVE_SCOPE_CHAIN(con)
first_event = con.execute(
"SELECT bridge_id FROM task_principal_owner_bootstrap_grants WHERE trust_manifest_sha256=?",
(current_digest,),
).fetchone()
current_bridge = str(first_event[0]) if first_event is not None else _bridge_for_scope(con, current_scope["scope_id"], current_digest)
records = con.execute(
"SELECT * FROM " + GENERIC_SCOPE_TABLE + " ORDER BY applied_at,supersession_id"
).fetchall()
if len(records) > MAX_GENERIC_TRANSITIONS:
_legacy._fail("P0_GENERIC_SCOPE_TRANSITION_LIMIT_EXCEEDED")
seen_digests = {current_digest}
seen_scopes = {str(current_scope["scope_id"])}
previous_applied_at: int | None = None
for record in records:
new_digest, new_scope, current_bridge, previous_applied_at = _validate_generic_record(
con, record, current_digest, current_scope, current_bridge, previous_applied_at
)
if new_digest in seen_digests or str(new_scope["scope_id"]) in seen_scopes:
_legacy._fail("P0_GENERIC_SCOPE_CHAIN_REUSE")
seen_digests.add(new_digest)
seen_scopes.add(str(new_scope["scope_id"]))
current_digest, current_scope = new_digest, new_scope
return current_digest, current_scope
def _task_is_currently_claimed(task: sqlite3.Row, now: int) -> None:
if (
task["status"] not in ("CLAIMED", "RUNNING")
or int(task["attempt"]) <= 0
or not isinstance(task["claimed_by"], str)
or not task["claimed_by"]
or not isinstance(task["lease_token_hash"], str)
or not task["lease_token_hash"]
or task["result_json"] not in (None, "")
):
_legacy._fail("P0_GENERIC_SCOPE_TASK_NOT_CLAIMED")
lease_expires = _legacy._lease_expiry(task["lease_expires_at"])
if lease_expires <= now:
_legacy._fail("P0_GENERIC_SCOPE_TASK_LEASE_EXPIRED")
def supersede_p0_scope(
db_path: str | Path,
old_trust_manifest: Mapping[str, Any],
old_scope_manifest: Mapping[str, Any],
new_trust_manifest: Mapping[str, Any],
new_scope_manifest: Mapping[str, Any],
*,
owner_grant: Mapping[str, Any],
owner_verifier: Any,
clock: Callable[[], Any] | None = None,
) -> dict[str, Any]:
"""Append one generic scope transition for a real claimed Task."""
old_trust = _legacy._manifest(old_trust_manifest)
old_scope = _legacy._scope_manifest(old_scope_manifest)
new_trust = _legacy._manifest(new_trust_manifest)
new_scope = _legacy._scope_manifest(new_scope_manifest)
grant = _grant_payload(owner_grant)
now = _legacy._now_epoch(clock)
if now < int(grant["not_before"]) or now >= int(grant["expires_at"]):
_legacy._fail("P0_GENERIC_SCOPE_GRANT_EXPIRED")
_verify_grant_signature(grant, owner_verifier)
old_digest = _legacy.sha256_canonical(old_trust)
new_digest = _legacy.sha256_canonical(new_trust)
old_scope_digest = _legacy.sha256_canonical(old_scope)
new_scope_digest = _legacy.sha256_canonical(new_scope)
if (
old_trust["issuer_id"] != new_trust["issuer_id"]
or old_trust["key_id"] != new_trust["key_id"]
or old_trust["public_key"] != new_trust["public_key"]
or old_trust["purpose"] != new_trust["purpose"]
or old_trust["bridge_id"] != new_trust["bridge_id"]
or old_scope["scope_id"] == new_scope["scope_id"]
or old_trust["scope_manifest_sha256"] != old_scope_digest
or new_trust["scope_manifest_sha256"] != new_scope_digest
or set(new_scope["capabilities"]) != set(STABLE_OPERATION_CAPABILITIES)
or _legacy._scope_suffixes(new_scope, new_trust["bridge_id"]) != _legacy._scope_suffixes(old_scope, old_trust["bridge_id"])
):
_legacy._fail("P0_GENERIC_SCOPE_IDENTITY_INVALID")
if any(
grant.get(key) != value
for key, value in {
"previous_task_id": old_scope["task_id"],
"previous_external_job_id": old_scope["external_job_id"],
"previous_generation": int(old_scope["generation"]),
"previous_bridge_id": old_trust["bridge_id"],
"previous_trust_manifest_sha256": old_digest,
"previous_scope_manifest_sha256": old_scope_digest,
"previous_scope_id": old_scope["scope_id"],
"task_id": new_scope["task_id"],
"external_job_id": new_scope["external_job_id"],
"repository": new_scope["repository"],
"target_branch": new_scope["target_branch"],
"generation": int(new_scope["generation"]),
"execution_digest": new_scope["execution_digest"],
"capabilities": new_scope["capabilities"],
"audiences": new_scope["audiences"],
"new_bridge_id": new_trust["bridge_id"],
"new_trust_manifest_sha256": new_digest,
"new_scope_manifest_sha256": new_scope_digest,
"new_scope_id": new_scope["scope_id"],
}.items()
):
_legacy._fail("P0_GENERIC_SCOPE_BINDING_MISMATCH")
with closing(_legacy._db(db_path)) as con:
con.execute("PRAGMA synchronous=FULL")
con.execute("BEGIN IMMEDIATE")
_legacy._ready(con)
_validate_generic_schema(con)
existing = con.execute(
"SELECT * FROM " + GENERIC_SCOPE_TABLE + " WHERE supersession_id=?",
(grant["supersession_id"],),
).fetchone()
grant_sha = _legacy.sha256_canonical(grant)
if existing is not None:
if existing["grant_sha256"] != grant_sha:
_legacy._fail("P0_GENERIC_SCOPE_REPLAY_CONFLICT")
active_digest, active_scope = _active_scope_chain(con)
if active_digest != new_digest or active_scope["scope_id"] != new_scope["scope_id"]:
_legacy._fail("P0_GENERIC_SCOPE_REPLAY_CONFLICT")
con.commit()
return {"scope_id": new_scope["scope_id"], "trust_manifest_sha256": new_digest, "replayed": True}
if con.execute("SELECT count(*) FROM " + GENERIC_SCOPE_TABLE).fetchone()[0] >= MAX_GENERIC_TRANSITIONS:
_legacy._fail("P0_GENERIC_SCOPE_TRANSITION_LIMIT_EXCEEDED")
active_digest, active_scope = _active_scope_chain(con)
if active_digest != old_digest or _legacy.sha256_canonical(_legacy._scope_material(active_scope)) != old_scope_digest:
_legacy._fail("P0_GENERIC_SCOPE_PRESTATE_MISMATCH")
task = con.execute("SELECT * FROM tasks WHERE task_id=?", (new_scope["task_id"],)).fetchone()
if task is None:
_legacy._fail("TASK_NOT_FOUND")
_task_is_currently_claimed(task, now)
execution = _legacy._execution(task)
if tuple(execution[key] for key in ("external_job_id", "repository", "target_branch", "generation", "execution_digest")) != tuple(new_scope[key] for key in ("external_job_id", "repository", "target_branch", "generation", "execution_digest")):
_legacy._fail("P0_GENERIC_SCOPE_TASK_BINDING_MISMATCH")
if not set(new_scope["capabilities"]).issubset(set(_legacy._capabilities(task))):
_legacy._fail("P0_GENERIC_SCOPE_CAPABILITIES_MISMATCH")
if con.execute("SELECT 1 FROM task_principal_admission_scopes WHERE scope_id=?", (new_scope["scope_id"],)).fetchone() is not None:
_legacy._fail("P0_GENERIC_SCOPE_ID_REUSE")
values = (
grant["supersession_id"], grant_sha, old_scope["task_id"], old_scope["external_job_id"],
int(old_scope["generation"]), old_trust["bridge_id"], old_digest, old_scope_digest,
old_scope["scope_id"], new_scope["task_id"], new_scope["external_job_id"],
new_scope["repository"], new_scope["target_branch"], int(new_scope["generation"]),
new_scope["execution_digest"], _legacy.canonical_json(new_scope["capabilities"]).decode(),
_legacy.canonical_json(new_scope["audiences"]).decode(), new_trust["bridge_id"], new_digest,
new_scope_digest, new_scope["scope_id"], grant["signer_namespace"], grant["signer_identity"],
grant["owner_key_id"], int(grant["issued_at"]), int(grant["not_before"]), int(grant["expires_at"]),
1, grant["supersession_reason"], grant["signature"], now,
)
con.execute(
"INSERT INTO " + GENERIC_SCOPE_TABLE + " VALUES(" + ",".join("?" for _ in values) + ")",
values,
)
generic_row = con.execute(
"SELECT * FROM " + GENERIC_SCOPE_TABLE + " WHERE supersession_id=?",
(grant["supersession_id"],),
).fetchone()
if generic_row is None or tuple(generic_row) != values:
_legacy._fail("P0_GENERIC_SCOPE_READBACK_MISMATCH")
scope_row = (
new_scope["scope_id"], new_trust["issuer_id"], new_trust["key_id"], new_scope["task_id"],
new_scope["external_job_id"], new_scope["repository"], new_scope["target_branch"],
int(new_scope["generation"]), new_scope["execution_digest"],
_legacy.canonical_json(new_scope["capabilities"]).decode(),
_legacy.canonical_json(new_scope["audiences"]).decode(), new_digest, now,
)
_legacy._insert_exact(
con,
"INSERT INTO task_principal_admission_scopes VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)",
scope_row,
"SELECT scope_id,issuer_id,key_id,task_id,external_job_id,repository,target_branch,generation,execution_digest,capabilities_json,audiences_json,trust_manifest_sha256,created_at FROM task_principal_admission_scopes WHERE scope_id=?",
(new_scope["scope_id"],),
scope_row,
)
event_row = (
"REGISTER", new_trust["issuer_id"], new_trust["key_id"], new_trust["public_key"],
new_digest, new_scope["scope_id"], now,
)
_legacy._insert_exact(
con,
"INSERT INTO task_principal_trust_events(event_type,issuer_id,key_id,public_key,trust_manifest_sha256,scope_id,created_at) VALUES(?,?,?,?,?,?,?)",
event_row,
"SELECT event_type,issuer_id,key_id,public_key,trust_manifest_sha256,scope_id,created_at FROM task_principal_trust_events WHERE scope_id=? AND trust_manifest_sha256=?",
(new_scope["scope_id"], new_digest),
event_row,
)
revocation_row = (
"SCOPE", old_scope["scope_id"],
"p0_task_scope_supersession:" + grant["supersession_id"], now,
)
_legacy._insert_exact(
con,
"INSERT INTO task_principal_revocations(kind,target,reason,created_at) VALUES(?,?,?,?)",
revocation_row,
"SELECT kind,target,reason,created_at FROM task_principal_revocations WHERE kind=? AND target=?",
("SCOPE", old_scope["scope_id"]),
revocation_row,
)
active_after, scope_after = _active_scope_chain(con)
if active_after != new_digest or scope_after["scope_id"] != new_scope["scope_id"]:
_legacy._fail("P0_GENERIC_SCOPE_READBACK_MISMATCH")
con.commit()
return {"scope_id": new_scope["scope_id"], "trust_manifest_sha256": new_digest, "replayed": False}
# The current R10 module has already patched the legacy classes to use its
# overlay globals. Point those same globals at this validated chain so the
# existing issuer/verifier classes retain their exact lease/assertion logic.
_current._active_scope_chain = _active_scope_chain
_current._legacy._active_scope_chain = _active_scope_chain
_current._legacy._sealed_readiness = _current._sealed_readiness
@@ -0,0 +1,102 @@
# ADR: P0-0 Task Admission Bootstrap without weakening TaskPrincipal
Date: 2026-09-03
Status: Candidate for semantic port
## Context
The live control plane can expose governed writer operations while still being
unable to create the Agent Task/TaskLease required by TaskPrincipal. This is a
bootstrap deadlock, not a reason to enable global repository writes.
The safety invariant is:
```text
Owner / durable delegation
-> Task admission grant
-> real Agent Task
-> claim + heartbeat + TaskLease
-> TaskPrincipal
-> WriterLease
-> governed commit
```
`GovernedTaskBootstrap.create()` may create a GitOps binding/Job. It is not, by
itself, evidence of an Agent Task claim or TaskLease.
## Decision
Introduce a narrow Task Admission Bootstrap substrate with three operations:
- `getTaskAdmissionEnvelope`
- `submitTaskAdmissionAuthorization`
- `getTaskAdmissionStatus`
The envelope normalizes and binds repository, source OID, target branch,
generation, execution digest, capabilities, file allowlist, risk, policy digest,
expiry and nonce.
Authorization is accepted only from:
1. an already-verified `OwnerDelegationEpoch`; or
2. an Owner signature over the exact canonical envelope.
There is deliberately no permissive default authorization backend.
The derived `TaskAdmissionGrant` can only narrow the parent authorization.
Production, tag, baseline, main, global-write, force-push, runtime-anchor and
arbitrary-shell capabilities are explicitly rejected.
The grant is passed to the existing Agent Task control plane via an injected
`TaskAdmissionPort`. This component never writes the Task database.
The production adapter accepts only a service-managed official Task transport;
it never returns its control credential. It first reconciles the exact task
identity, then POSTs the official `/v1/tasks` contract only if absent, and
reconciles an ambiguous response before reporting `UNKNOWN`. Task receipts use
actual `QUEUED`, `CLAIMED`, or `RUNNING` state, never a synthetic ready state.
Durable epochs are resolved through an injected provider that receives the
authenticated caller identity from trusted middleware. A body-supplied epoch
identifier is only a lookup key. The existing durable preparation authorization
does not become file-write or TaskPrincipal authority.
TaskPrincipal is not used to bootstrap the Task. It remains downstream of a
real claim/TaskLease.
## Crash / replay semantics
Envelope creation is idempotent by exact request digest.
Authorization/admission is durable and exact replay returns the same Task
receipt. A SQLite `BEGIN IMMEDIATE` claim persists the immutable grant and its
authorization-proof digest before the Task side effect. Cross-instance callers
observe the durable envelope winner; a different proof cannot replace the
stored grant. The live Task API retains the second exact-identity boundary.
A crash after authorization but before Task API admission must resume the same
envelope/grant identity. It must not create a replacement task or widen scope.
## Integration rule for current live authority
The source snapshot used for this candidate is not proven byte-identical to the
2026-09-03 live authority. Therefore the code is a semantic-port candidate.
The live integration must:
- preserve existing TaskPrincipal verification;
- adapt `TaskAdmissionPort` to the actual Agent Task API;
- adapt `OwnerAuthorizationBackend` to the actual trusted Owner/delegation
verifier;
- expose admission routes outside the TaskPrincipal gate, because the
cryptographic/delegation proof is the bootstrap authority;
- never turn the runtime anchor into a writer;
- never use Gitea/SSH/downstream credentials as Owner authority;
- publish OpenAPI/capability metadata only when handlers are actually wired.
## Consequences
The control plane can become unattended after one durable Owner delegation
epoch, while retaining fail-closed boundaries. If no valid delegation exists,
the only legitimate stop is an already-generated canonical signing envelope
awaiting the Owner's cryptographic signature.
@@ -0,0 +1,109 @@
# AIOS Task Admission Bootstrap OpenAPI fragment.
# Integrate into the canonical Bridge OpenAPI only when the corresponding live
# handlers and authorization backend are configured. This fragment does not
# enable legacy repository mutation or change TaskPrincipal/WriterLease gates.
components:
schemas:
TaskAdmissionEnvelopeRequest:
type: object
additionalProperties: false
required:
- idempotency_key
- task_id
- repository
- authority_base_oid
- target_branch
- generation
- execution_digest
- requested_capabilities
- file_allowlist
- risk
properties:
idempotency_key: {type: string, minLength: 8, maxLength: 256}
task_id: {type: string, minLength: 3, maxLength: 180}
repository: {type: string, enum: [aios/aios]}
authority_base_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
target_branch: {type: string, pattern: '^agent/.+'}
generation: {type: integer, minimum: 1}
execution_digest: {type: string, pattern: '^[0-9a-f]{64}$'}
requested_capabilities:
type: array
minItems: 1
uniqueItems: true
items: {type: string}
file_allowlist:
type: array
minItems: 1
uniqueItems: true
items: {type: string}
risk: {type: string, enum: [low, medium]}
expires_in_seconds:
type: integer
minimum: 60
maximum: 900
TaskAdmissionAuthorizationRequest:
type: object
additionalProperties: false
required: [envelope_id, authorization_kind]
properties:
envelope_id: {type: string}
authorization_kind:
type: string
enum: [delegation_epoch, owner_signature]
delegation_epoch_id: {type: string}
owner_key_id: {type: string}
signature_base64: {type: string}
paths:
/v2/task-admission/envelope:
post:
operationId: getTaskAdmissionEnvelope
summary: Build and persist a canonical narrow Task admission envelope
description: >
No Git/Task mutation occurs. The server normalizes repository, branch,
capabilities, file allowlist, risk, policy digest, expiry and nonce.
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/TaskAdmissionEnvelopeRequest'}
responses:
'200': {description: Canonical envelope and signing payload}
'403': {description: Requested scope exceeds bootstrap policy}
'409': {description: Idempotency key conflicts with a different request}
/v2/task-admission/authorize:
post:
operationId: submitTaskAdmissionAuthorization
summary: Verify durable delegation or Owner signature and admit one exact Task
description: >
This operation does not accept a general-purpose bearer as authority.
Authorization is proven by a verified OwnerDelegationEpoch or by the
Owner signature over the exact canonical envelope. The resulting grant
can only narrow scope and is handed to the existing Agent Task API.
A delegation identifier in caller JSON is insufficient: the backend
binds the verified parent epoch to the authenticated service caller.
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/TaskAdmissionAuthorizationRequest'}
responses:
'200': {description: Exact QUEUED, CLAIMED, or RUNNING Task receipt or idempotent replay}
'403': {description: Invalid, expired, revoked or widened authorization}
'409': {description: Conflicting idempotent admission}
/v2/task-admission/status:
get:
operationId: getTaskAdmissionStatus
summary: Read durable Task-admission state
parameters:
- in: query
name: envelope_id
required: true
schema: {type: string}
responses:
'200': {description: Pending or admitted receipt}
'404': {description: Envelope not found}
@@ -0,0 +1,102 @@
# Task Admission Bootstrap P0 Runbook
## Goal
Repair the Task-admission bootstrap chain without enabling legacy/global writes.
## Preconditions
Re-read live authority, runtime mode, OpenAPI, capabilities and current Task
API. Do not assume the package source paths are current.
Required invariants:
```text
AIOS_GITOPS_WRITES_ENABLED=false
legacy_global_writes_enabled=false
18893=IMMUTABLE_STAGING
main/tag/baseline/production writes=false
```
## Semantic-port sequence
1. Locate the live Agent Task create/admission implementation.
2. Locate the current Owner/delegation signature verifier.
3. Port `aios_task_admission_bootstrap.py` as the common substrate.
4. Implement `TaskAdmissionPort.admit()` with the official Agent Task API.
5. Implement `OwnerAuthorizationBackend` with the current trusted verifier.
6. Wire the three task-admission operations into the existing Bridge.
7. Keep TaskPrincipal issuance downstream of real Task claim/TaskLease.
8. Merge the OpenAPI fragment only after the live handlers are wired.
9. Run impacted Linux/POSIX, TaskPrincipal, Bridge and writer regression.
10. Create the bootstrap-repair governed commit through the existing sanctioned
recovery path.
11. Use the repaired admission path to create the Repository Discovery P0 Task.
12. Claim/heartbeat -> TaskLease -> TaskPrincipal -> WriterLease.
13. Commit only the existing Repository Discovery allowlist through
`commitFilesGoverned`.
14. Reconcile ambiguous outcomes before any retry.
Admission uses a per-envelope, crash-releasing SQLite lock under the
admission state path's `.locks` directory. It persists the immutable
grant before an effect, then the official Task API GET reconciles any
crashed or ambiguous effect before a POST is permitted.
15. Replays revalidate the supplied proof, policy, expiry and revocation before
reading a receipt. A different otherwise-valid proof conflicts with the
persisted authorization digest; it never becomes a harmless receipt replay.
16. The Task API adapter must receive a server-resolved trusted execution plan
for the signed execution digest. It supplies `metadata.execution` with
the exact repository, branch, generation, digest and nonempty external job
identifier. Do not derive a job identity from request JSON or generate one.
17. Require Authority / remote Git / API OID equality and idempotent replay.
18. Publish immutable staging artifact and run Repository Discovery live E2E.
## Never do
- direct Task DB inserts/updates;
- direct bare-repo/ref changes;
- raw Git push as a substitute for governed publication;
- borrow SSH/Gitea/service credentials as Owner authorization;
- disable TaskPrincipal verification;
- issue TaskPrincipal without a live TaskLease;
- set global writes true;
- turn 18893 into a writer;
- include production/tag/baseline/main permissions in a task grant.
## Acceptance
Bootstrap repair:
```text
ENVELOPE_EXACT_REPLAY=PASS
SCOPE_NARROWING=PASS
DELEGATION_REVOKE_EXPIRY=PASS
OWNER_SIGNATURE_VERIFY=PASS
CONCURRENT_ADMISSION_SINGLE_TASK=PASS
TASK_DB_DIRECT_WRITE=false
TASKPRINCIPAL_BYPASS=false
GLOBAL_WRITES=false
CRASH_AFTER_EFFECT_REPLAY=PASS
CRASH_BEFORE_EFFECT_REPLAY=PASS
CHANGED_VALID_PROOF_CONFLICT=PASS
TASK_API_EXACT_IDENTITY=PASS
```
These are implementation and test acceptance conditions only. They are not
evidence of a live claim, governed publication, remote OID equality, or staging
E2E execution; obtain those independently through the sanctioned live runbook.
Repository Discovery publication:
```text
TASK_CREATED=PASS
TASK_CLAIMED=PASS
TASK_HEARTBEAT=PASS
TASK_LEASE=PASS
TASK_PRINCIPAL=PASS
WRITER_LEASE=PASS
COMMIT_FILES_GOVERNED=PASS
AUTHORITY_REMOTE_API_OID_EQUAL=PASS
IDEMPOTENCY_REPLAY=PASS
STAGING_LIVE_E2E=PASS
TASK_VERIFIED=SUCCEEDED
```
+543
View File
@@ -0,0 +1,543 @@
"""Read-only Phase 2 runtime introspection with a bounded SSH transport.
The wrapper is deliberately sent as ``subprocess.run(input=payload)``. No
JSON, ABI data, or base64 representation is placed in the Windows SSH argv;
the default argv is the fixed, short command documented by
``DEFAULT_REMOTE_COMMAND``.
The remote wrapper reads the current issuer unit's MainPID, WorkingDirectory,
and PYTHONPATH, then resolves only the named importable modules. It does not
walk release trees, ``/opt``, historical worktrees, SOUL, or MEMORY, and it
does not call a database, signer, Bridge, Task API, or mutation endpoint.
This module is locally testable, but the module import and systemd/POSIX
behaviour still require external Linux live validation. Phase 2 status is
always explicit about all mutation classes being false.
"""
from __future__ import annotations
import argparse
import ast
import hashlib
import json
import subprocess
import sys
from pathlib import Path
from typing import Any, Callable, Mapping, Sequence
DEFAULT_SSH = "ssh.exe"
DEFAULT_HOST = "hw"
DEFAULT_REMOTE_COMMAND = 'test "$(hostname)" = cn-network-mkt && sudo -n python3 -'
DEFAULT_TIMEOUT_SECONDS = 60
DEFAULT_ISSUER_UNIT = "aios-task-principal-issuer.service"
DEFAULT_ADMISSION_MODULE = "aios_task_principal_admission"
DEFAULT_MIGRATION_MODULE = "aios_task_principal_storage_migration"
DEFAULT_REQUIRED_ADMISSION_FUNCTIONS = (
"offline_migrate_task_principal_schema",
"supersede_p0_scope",
)
DEFAULT_MIGRATION_VERIFIER = "SSHOwnerBootstrapVerifier"
MAX_ARGV_BYTES = 512
MAX_REQUEST_BYTES = 32 * 1024
MUTATION_FLAGS = (
"task_db_mutation",
"scope_mutation",
"git_mutation",
"service_mutation",
)
class RemoteTransportError(RuntimeError):
"""A safe, non-secret description of an SSH transport failure."""
class Phase2StatusError(RuntimeError):
"""The remote result was not a valid fail-closed Phase2 status."""
def build_ssh_argv(
ssh: str = DEFAULT_SSH,
host: str = DEFAULT_HOST,
) -> list[str]:
"""Return the only SSH argv shape permitted by the Phase2 transport."""
argv = [
ssh,
"-T",
"-o",
"BatchMode=yes",
"-o",
"ConnectTimeout=12",
host,
DEFAULT_REMOTE_COMMAND,
]
argv_bytes = sum(len(part.encode("utf-8")) + 1 for part in argv)
if argv_bytes > MAX_ARGV_BYTES:
raise ValueError("SSH argv exceeds the Phase2 hard limit")
return argv
def run_remote_python(
payload: bytes,
*,
ssh: str = DEFAULT_SSH,
host: str = DEFAULT_HOST,
timeout: float = DEFAULT_TIMEOUT_SECONDS,
runner: Callable[..., Any] = subprocess.run,
) -> bytes:
"""Send *payload* as stdin to the fixed remote Python command.
Error messages intentionally contain no stderr text. SSH stderr can
include command output, environment values, or credentials emitted by a
misconfigured remote service.
"""
if not isinstance(payload, bytes):
raise TypeError("payload must be bytes")
if timeout <= 0:
raise ValueError("timeout must be positive")
argv = build_ssh_argv(ssh=ssh, host=host)
try:
completed = runner(
argv,
input=payload,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=timeout,
check=False,
)
except subprocess.TimeoutExpired as exc:
raise RemoteTransportError(
f"remote SSH timed out after {timeout:g} seconds"
) from exc
except OSError as exc:
raise RemoteTransportError("remote SSH could not be started") from exc
return_code = getattr(completed, "returncode", None)
if return_code != 0:
raise RemoteTransportError(f"remote SSH exited with status {return_code}")
output = getattr(completed, "stdout", b"")
if isinstance(output, str):
output = output.encode("utf-8")
if not isinstance(output, bytes):
raise RemoteTransportError("remote SSH returned a non-byte stdout value")
return output
def module_sha256(path: str | Path) -> str:
"""Hash one resolved module file without searching neighbouring files."""
digest = hashlib.sha256()
with Path(path).open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def validate_public_function_ast(
source: str,
required_functions: Sequence[str] = DEFAULT_REQUIRED_ADMISSION_FUNCTIONS,
) -> dict[str, Any]:
"""Validate public symbols and the source-level export mechanism.
The live admission overlay intentionally re-exports the legacy public API
through a small ``globals()[name] = value`` loop. Requiring every public
function to be a direct ``def`` would therefore reject the exact deployed
module even though the runtime symbol and signature are present. The AST
check still proves that the module has either a direct/imported binding or
an explicit dynamic re-export mechanism; the caller separately verifies
the runtime callable and its signature.
"""
try:
tree = ast.parse(source)
except SyntaxError as exc:
return {
"ok": False,
"missing": list(required_functions),
"error": f"AST_PARSE_ERROR:{exc.msg}",
}
top_level = {
node.name: node
for node in tree.body
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and not node.name.startswith("_")
}
imported = {
alias.asname or alias.name.split(".", 1)[0]
for node in tree.body
if isinstance(node, (ast.Import, ast.ImportFrom))
for alias in node.names
if alias.name != "*"
}
dynamic_reexport = any(
isinstance(node, ast.Call)
and isinstance(node.func, ast.Name)
and node.func.id == "globals"
for node in ast.walk(tree)
)
bound = set(top_level) | imported
missing = [
name
for name in required_functions
if name not in bound and not dynamic_reexport
]
return {
"ok": not missing,
"missing": missing,
"functions": sorted(top_level),
"imported": sorted(imported),
"dynamic_reexport": dynamic_reexport,
}
def make_phase2_status(
status: str,
errors: Sequence[str] = (),
**details: Any,
) -> dict[str, Any]:
"""Create a status object whose mutation flags are always false."""
result: dict[str, Any] = {
"kind": "phase2-status",
"version": 1,
"status": status,
"read_only": True,
"fail_closed": status != "PASS",
}
result.update({flag: False for flag in MUTATION_FLAGS})
if errors:
result["errors"] = list(errors)
result.update(details)
return result
def validate_phase2_status(status: Mapping[str, Any]) -> dict[str, Any]:
"""Validate the result contract and reject any mutation claim."""
if status.get("kind") != "phase2-status":
raise Phase2StatusError("remote result is not phase2-status")
for flag in MUTATION_FLAGS:
if status.get(flag) is not False:
raise Phase2StatusError(f"remote result has invalid {flag}")
if status.get("status") not in {"PASS", "FAIL"}:
raise Phase2StatusError("remote result has invalid status")
return dict(status)
def _json_request(request: Mapping[str, Any]) -> str:
"""Serialize only a small request into the stdin-delivered wrapper."""
encoded = json.dumps(dict(request), ensure_ascii=True, separators=(",", ":"))
if len(encoded.encode("utf-8")) > MAX_REQUEST_BYTES:
raise ValueError("Phase2 request is unexpectedly large")
return encoded
REMOTE_SCRIPT_PREFIX = r'''# Phase2 read-only runtime wrapper. Do not add mutations here.
import ast
import hashlib
import importlib
import importlib.util
import inspect
import json
import os
import shlex
import subprocess
import sys
from pathlib import Path
sys.dont_write_bytecode = True
REQUEST = json.loads('''
REMOTE_SCRIPT_SUFFIX = r''')
MUTATION_FLAGS = ("task_db_mutation", "scope_mutation", "git_mutation", "service_mutation")
def status(status_name, errors=(), **details):
result = {
"kind": "phase2-status",
"version": 1,
"status": status_name,
"read_only": True,
"fail_closed": status_name != "PASS",
}
result.update({flag: False for flag in MUTATION_FLAGS})
if errors:
result["errors"] = list(errors)
result.update(details)
return result
def systemd_value(unit, property_name):
try:
completed = subprocess.run(
["systemctl", "show", unit, "--property=" + property_name, "--value", "--no-pager"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
timeout=12,
)
except (OSError, subprocess.TimeoutExpired):
raise RuntimeError("SYSTEMD_QUERY_FAILED")
if completed.returncode != 0:
raise RuntimeError("SYSTEMD_QUERY_FAILED")
return completed.stdout.decode("utf-8", "strict").strip()
def parse_pythonpath(environment_value):
for token in shlex.split(environment_value):
if token.startswith("PYTHONPATH="):
return token[len("PYTHONPATH="):].split(os.pathsep)
return []
def runtime_identity():
unit = REQUEST.get("issuer_unit", "aios-task-principal-issuer.service")
pid = REQUEST.get("issuer_pid")
working_directory = REQUEST.get("working_directory")
pythonpath = REQUEST.get("pythonpath")
# Regression callers may pin one identity component while obtaining the
# remaining components from the live unit. In particular, never replace
# an explicitly supplied PID with a newly queried MainPID.
if pid is None:
pid = int(systemd_value(unit, "MainPID"))
if working_directory is None:
working_directory = systemd_value(unit, "WorkingDirectory")
if pythonpath is None:
pythonpath = parse_pythonpath(systemd_value(unit, "Environment"))
if not pid or not working_directory or working_directory == "-":
raise RuntimeError("ISSUER_RUNTIME_IDENTITY_INCOMPLETE")
if isinstance(pythonpath, str):
pythonpath = pythonpath.split(os.pathsep)
return {
"unit": unit,
"main_pid": int(pid),
"working_directory": working_directory,
"pythonpath": [item for item in pythonpath if item],
}
def setup_import_path(identity):
path = [identity["working_directory"]]
path.extend(identity["pythonpath"])
path.extend(item for item in sys.path if item and item not in path)
sys.path[:] = path
def importable_module(module_name):
try:
spec = importlib.util.find_spec(module_name)
if spec is None or not spec.origin or spec.origin in {"built-in", "frozen"}:
raise RuntimeError("MODULE_NOT_IMPORTABLE:" + module_name)
module = importlib.import_module(module_name)
except Exception as exc:
if isinstance(exc, RuntimeError):
raise
raise RuntimeError("MODULE_IMPORT_FAILED:" + module_name) from exc
module_path = getattr(module, "__file__", None)
if not module_path:
raise RuntimeError("MODULE_HAS_NO_FILE:" + module_name)
return module, Path(module_path).resolve()
def hash_file(path):
digest = hashlib.sha256()
with path.open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def inspect_admission(module, path, required, expected_hash):
source = path.read_text(encoding="utf-8")
tree = ast.parse(source)
nodes = {
node.name: node for node in tree.body
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and not node.name.startswith("_")
}
imported = {
alias.asname or alias.name.split(".", 1)[0]
for node in tree.body
if isinstance(node, (ast.Import, ast.ImportFrom))
for alias in node.names
if alias.name != "*"
}
dynamic_reexport = any(
isinstance(node, ast.Call)
and isinstance(node.func, ast.Name)
and node.func.id == "globals"
for node in ast.walk(tree)
)
bound = set(nodes) | imported
missing = [
name
for name in required
if name not in bound and not dynamic_reexport
]
if missing:
raise RuntimeError("PUBLIC_SYMBOL_MISSING:" + ",".join(missing))
signatures = {}
for name in required:
value = getattr(module, name, None)
if value is None:
raise RuntimeError("PUBLIC_SYMBOL_NOT_IMPORTABLE:" + name)
try:
signatures[name] = str(inspect.signature(value))
except (TypeError, ValueError):
raise RuntimeError("SIGNATURE_UNAVAILABLE:" + name)
actual_hash = hash_file(path)
if expected_hash and actual_hash != expected_hash:
raise RuntimeError("MODULE_HASH_MISMATCH:admission")
return {
"name": module.__name__,
"path": str(path),
"sha256": actual_hash,
"signatures": signatures,
"ast_functions": sorted(nodes),
"ast_imported": sorted(imported),
"ast_dynamic_reexport": dynamic_reexport,
}
def inspect_migration(module, path, verifier_name, expected_hash):
verifier = getattr(module, verifier_name, None)
if verifier is None:
raise RuntimeError("MIGRATION_VERIFIER_MISSING:" + verifier_name)
try:
signature = str(inspect.signature(verifier))
except (TypeError, ValueError):
raise RuntimeError("MIGRATION_VERIFIER_SIGNATURE_UNAVAILABLE")
actual_hash = hash_file(path)
if expected_hash and actual_hash != expected_hash:
raise RuntimeError("MODULE_HASH_MISMATCH:migration")
return {
"name": module.__name__,
"path": str(path),
"sha256": actual_hash,
"verifier": verifier_name,
"signature": signature,
}
def main():
try:
identity = runtime_identity()
setup_import_path(identity)
admission_name = REQUEST.get("admission_module", "aios_task_principal_admission")
migration_name = REQUEST.get("migration_module", "aios_task_principal_storage_migration")
if migration_name == admission_name:
raise RuntimeError("MIGRATION_MODULE_NOT_DISTINCT")
required = tuple(REQUEST.get("required_admission_functions", (
"offline_migrate_task_principal_schema", "supersede_p0_scope")))
admission, admission_path = importable_module(admission_name)
migration, migration_path = importable_module(migration_name)
admission_info = inspect_admission(
admission,
admission_path,
required,
REQUEST.get("expected_admission_sha256"),
)
migration_info = inspect_migration(
migration,
migration_path,
REQUEST.get("migration_verifier", "SSHOwnerBootstrapVerifier"),
REQUEST.get("expected_migration_sha256"),
)
print(json.dumps(status(
"PASS",
issuer=identity,
modules={"admission": admission_info, "migration": migration_info},
ast_public_functions=list(required),
), sort_keys=True, separators=(",", ":")))
except Exception as exc:
# Emit only stable error classes and never exception text from imports.
error = str(exc).split(":", 1)[0] or "PHASE2_INTROSPECTION_FAILED"
print(json.dumps(status("FAIL", errors=[error]), sort_keys=True, separators=(",", ":")))
if __name__ == "__main__":
main()
'''
def build_remote_payload(request: Mapping[str, Any] | None = None) -> bytes:
"""Build the stdin wrapper; request data never enters SSH argv."""
request = {} if request is None else request
# repr() makes the embedded JSON a valid Python string for every request
# value, including apostrophes and backslashes; it still remains stdin.
request_literal = repr(_json_request(request))
return (REMOTE_SCRIPT_PREFIX + request_literal + REMOTE_SCRIPT_SUFFIX).encode("utf-8")
def _load_request(args: argparse.Namespace) -> dict[str, Any]:
request: dict[str, Any] = {}
if args.request_json:
loaded = json.loads(args.request_json)
if not isinstance(loaded, dict):
raise ValueError("request JSON must be an object")
request.update(loaded)
if args.request_file:
loaded = json.loads(Path(args.request_file).read_text(encoding="utf-8"))
if not isinstance(loaded, dict):
raise ValueError("request JSON must be an object")
request.update(loaded)
overrides = {
"issuer_unit": args.issuer_unit,
"issuer_pid": args.issuer_pid,
"working_directory": args.working_directory,
"pythonpath": args.pythonpath,
"admission_module": args.admission_module,
"migration_module": args.migration_module,
"expected_admission_sha256": args.expected_admission_sha256,
"expected_migration_sha256": args.expected_migration_sha256,
}
request.update({key: value for key, value in overrides.items() if value is not None})
return request
def _write_status(path: str | Path, status: Mapping[str, Any]) -> None:
destination = Path(path)
destination.write_text(
json.dumps(dict(status), ensure_ascii=True, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--request-json")
parser.add_argument("--request-file")
parser.add_argument("--output", default="phase2-status.json")
parser.add_argument("--ssh", default=DEFAULT_SSH)
parser.add_argument("--host", default=DEFAULT_HOST)
parser.add_argument("--timeout", type=float, default=DEFAULT_TIMEOUT_SECONDS)
parser.add_argument("--issuer-unit")
parser.add_argument("--issuer-pid", type=int)
parser.add_argument("--working-directory")
parser.add_argument("--pythonpath", nargs="*")
parser.add_argument("--admission-module")
parser.add_argument("--migration-module")
parser.add_argument("--expected-admission-sha256")
parser.add_argument("--expected-migration-sha256")
args = parser.parse_args(argv)
try:
request = _load_request(args)
payload = build_remote_payload(request)
stdout = run_remote_python(
payload,
ssh=args.ssh,
host=args.host,
timeout=args.timeout,
)
remote_status = json.loads(stdout.decode("utf-8"))
status = validate_phase2_status(remote_status)
except (OSError, ValueError, json.JSONDecodeError, RemoteTransportError, Phase2StatusError) as exc:
status = make_phase2_status("FAIL", errors=[str(exc).split(":", 1)[0]])
_write_status(args.output, status)
return 1
_write_status(args.output, status)
return 0 if status["status"] == "PASS" else 1
if __name__ == "__main__":
raise SystemExit(main())
+109 -106
View File
@@ -1,108 +1,111 @@
import base64
import subprocess
from pathlib import Path
from aios_authority_git_executor import AuthorityGitExecutor
from aios_chatgpt_bridge_v2_server import BridgeHandler, RuntimeConfig
from aios_continuation_executor import GiteaContinuationRemote, ServerSideCredentialProjection
from aios_continuation_lease import WriterLeaseRequestV2
class Probe:
def __init__(self, present=False):
self.present = present
def branch_exists(self, _branch):
return self.present
def branch_head(self, _branch):
return "a" * 40
def is_ancestor(self, _older, _newer):
return True
def commit_exists(self, _oid):
return True
def test_authority_create_task_branch_is_agent_scoped_and_idempotent(tmp_path):
bare = tmp_path / "repo.git"
subprocess.run(["git", "init", "--bare", str(bare)], check=True, capture_output=True)
source = tmp_path / "source"
subprocess.run(["git", "init", str(source)], check=True, capture_output=True)
(source / "README.md").write_text("fixture\n", encoding="utf-8")
subprocess.run(["git", "-C", str(source), "add", "README.md"], check=True)
subprocess.run(["git", "-C", str(source), "-c", "user.name=test", "-c", "user.email=test@example.invalid", "commit", "-m", "fixture"], check=True, capture_output=True)
base = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
executor = AuthorityGitExecutor(bare, tmp_path / "worktrees")
subprocess.run(["git", "-C", str(source), "push", str(bare), "HEAD:refs/heads/main"], check=True, capture_output=True)
assert executor.create_task_branch("refs/heads/agent/contract-test", base) == base
assert executor.create_task_branch("refs/heads/agent/contract-test", base) == base
assert executor.read_ref("refs/heads/agent/contract-test") == base
def test_create_new_lease_is_exact_base_and_absent():
request = WriterLeaseRequestV2(
task_id="TASK-P0-BRIDGE-V21-DURABLE-RECONCILIATION-20260809",
repository="aios/aios",
branch_mode="CREATE_NEW",
base_oid="a" * 40,
expected_branch_oid="a" * 40,
target_branch="agent/p0-bridge-v21-durable-reconciliation-20260809",
generation=1,
)
request.validate(Probe())
def test_create_new_lease_rejects_existing_target():
request = WriterLeaseRequestV2(
task_id="task",
repository="aios/aios",
branch_mode="CREATE_NEW",
base_oid="a" * 40,
expected_branch_oid="a" * 40,
target_branch="agent/existing-target",
generation=1,
)
try:
request.validate(Probe(True))
except Exception as exc:
assert str(exc) == "TARGET_BRANCH_ALREADY_EXISTS"
else:
raise AssertionError("existing CREATE_NEW target must fail")
def test_slash_branch_uses_paginated_collection():
credential = ServerSideCredentialProjection(Path("/tmp/key"), Path("/tmp/token"), active=True)
calls = []
class FakeRemote(GiteaContinuationRemote):
def _api(self, path):
calls.append(path)
if "page=1" in path:
return [{"name": f"agent/other-{i}", "commit": {"id": "b" * 40}} for i in range(50)]
return [{"name": "agent/slash/branch", "commit": {"id": "c" * 40}}]
remote = FakeRemote(Path("/tmp"), credential)
assert remote.read_api_branch("agent/slash/branch") == "c" * 40
assert calls[0].endswith("branches?page=1&limit=50")
assert calls[1].endswith("branches?page=2&limit=50")
def test_http_route_contract_is_complete():
expected = {
"/v2/runtime", "/v2/capabilities", "/v2/governed-task",
"/v2/execution-envelope",
"/v2/writer-lease", "/v2/commit-file", "/v2/commit-files",
"/v2/job", "/v2/job/resume", "/v2/job/reconcile",
"/v2/tag/approval-request",
"/v2/tag/publish",
"/v2/tag/status",
"/v2/release/artifact",
"/v2/release/execute",
import base64
import subprocess
from pathlib import Path
from aios_authority_git_executor import AuthorityGitExecutor
from aios_chatgpt_bridge_v2_server import BridgeHandler, RuntimeConfig
from aios_continuation_executor import GiteaContinuationRemote, ServerSideCredentialProjection
from aios_continuation_lease import WriterLeaseRequestV2
class Probe:
def __init__(self, present=False):
self.present = present
def branch_exists(self, _branch):
return self.present
def branch_head(self, _branch):
return "a" * 40
def is_ancestor(self, _older, _newer):
return True
def commit_exists(self, _oid):
return True
def test_authority_create_task_branch_is_agent_scoped_and_idempotent(tmp_path):
bare = tmp_path / "repo.git"
subprocess.run(["git", "init", "--bare", str(bare)], check=True, capture_output=True)
source = tmp_path / "source"
subprocess.run(["git", "init", str(source)], check=True, capture_output=True)
(source / "README.md").write_text("fixture\n", encoding="utf-8")
subprocess.run(["git", "-C", str(source), "add", "README.md"], check=True)
subprocess.run(["git", "-C", str(source), "-c", "user.name=test", "-c", "user.email=test@example.invalid", "commit", "-m", "fixture"], check=True, capture_output=True)
base = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
executor = AuthorityGitExecutor(bare, tmp_path / "worktrees")
subprocess.run(["git", "-C", str(source), "push", str(bare), "HEAD:refs/heads/main"], check=True, capture_output=True)
assert executor.create_task_branch("refs/heads/agent/contract-test", base) == base
assert executor.create_task_branch("refs/heads/agent/contract-test", base) == base
assert executor.read_ref("refs/heads/agent/contract-test") == base
def test_create_new_lease_is_exact_base_and_absent():
request = WriterLeaseRequestV2(
task_id="TASK-P0-BRIDGE-V21-DURABLE-RECONCILIATION-20260809",
repository="aios/aios",
branch_mode="CREATE_NEW",
base_oid="a" * 40,
expected_branch_oid="a" * 40,
target_branch="agent/p0-bridge-v21-durable-reconciliation-20260809",
generation=1,
)
request.validate(Probe())
def test_create_new_lease_rejects_existing_target():
request = WriterLeaseRequestV2(
task_id="task",
repository="aios/aios",
branch_mode="CREATE_NEW",
base_oid="a" * 40,
expected_branch_oid="a" * 40,
target_branch="agent/existing-target",
generation=1,
)
try:
request.validate(Probe(True))
except Exception as exc:
assert str(exc) == "TARGET_BRANCH_ALREADY_EXISTS"
else:
raise AssertionError("existing CREATE_NEW target must fail")
def test_slash_branch_uses_paginated_collection():
credential = ServerSideCredentialProjection(Path("/tmp/key"), Path("/tmp/token"), active=True)
calls = []
class FakeRemote(GiteaContinuationRemote):
def _api(self, path):
calls.append(path)
if "page=1" in path:
return [{"name": f"agent/other-{i}", "commit": {"id": "b" * 40}} for i in range(50)]
return [{"name": "agent/slash/branch", "commit": {"id": "c" * 40}}]
remote = FakeRemote(Path("/tmp"), credential)
assert remote.read_api_branch("agent/slash/branch") == "c" * 40
assert calls[0].endswith("branches?page=1&limit=50")
assert calls[1].endswith("branches?page=2&limit=50")
def test_http_route_contract_is_complete():
expected = {
"/v2/runtime", "/v2/capabilities", "/v2/governed-task",
"/v2/execution-envelope",
"/v2/writer-lease", "/v2/commit-file", "/v2/commit-files",
"/v2/job", "/v2/job/resume", "/v2/job/reconcile",
"/v2/tag/approval-request",
"/v2/tag/publish",
"/v2/tag/status",
"/v2/release/artifact",
"/v2/release/execute",
"/v2/release/consistency",
"/v2/release/acceptance",
}
from aios_chatgpt_bridge_v2_server import ROUTES
assert set(ROUTES) == expected
"/v2/task-admission/envelope",
"/v2/task-admission/authorize",
"/v2/task-admission/status",
}
from aios_chatgpt_bridge_v2_server import ROUTES
assert set(ROUTES) == expected
+131
View File
@@ -0,0 +1,131 @@
"""Local, non-SSH tests for the bounded Phase2 transport and status contract."""
from __future__ import annotations
import importlib.util
import subprocess
from pathlib import Path
from types import SimpleNamespace
import pytest
SCRIPT = Path(__file__).parents[1] / "scripts" / "phase2_introspect_and_freeze.py"
SPEC = importlib.util.spec_from_file_location("phase2_introspect_and_freeze", SCRIPT)
assert SPEC and SPEC.loader
phase2 = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(phase2)
def test_transport_sends_large_payload_without_argv_growth():
payload = b"x" * (256 * 1024)
observed = {}
def runner(argv, **kwargs):
observed["argv"] = argv
observed["kwargs"] = kwargs
return SimpleNamespace(returncode=0, stdout=b'{"ok":true}')
assert phase2.run_remote_python(payload, runner=runner) == b'{"ok":true}'
assert observed["kwargs"]["input"] == payload
argv = observed["argv"]
assert argv == [
"ssh.exe",
"-T",
"-o",
"BatchMode=yes",
"-o",
"ConnectTimeout=12",
"hw",
'test "$(hostname)" = cn-network-mkt && sudo -n python3 -',
]
assert sum(len(part.encode("utf-8")) + 1 for part in argv) <= 512
assert all(payload not in part.encode("utf-8") for part in argv)
assert all("base64" not in part.lower() for part in argv)
def test_transport_reports_timeout_without_stderr():
def runner(argv, **kwargs):
raise subprocess.TimeoutExpired(argv, kwargs["timeout"], stderr=b"secret=do-not-leak")
with pytest.raises(phase2.RemoteTransportError, match="timed out") as error:
phase2.run_remote_python(b"payload", runner=runner, timeout=3)
assert "secret" not in str(error.value)
def test_transport_reports_nonzero_without_stderr():
def runner(argv, **kwargs):
return SimpleNamespace(returncode=23, stdout=b"", stderr=b"token=do-not-leak")
with pytest.raises(phase2.RemoteTransportError, match="status 23") as error:
phase2.run_remote_python(b"payload", runner=runner)
assert "token" not in str(error.value)
def test_status_flags_are_explicit_and_missing_public_symbol_fails_closed():
valid = phase2.validate_public_function_ast(
"def offline_migrate_task_principal_schema():\n pass\n"
"def supersede_p0_scope():\n pass\n"
)
assert valid["ok"] is True
missing = phase2.validate_public_function_ast(
"def offline_migrate_task_principal_schema():\n pass\n"
)
assert missing["ok"] is False
assert missing["missing"] == ["supersede_p0_scope"]
status = phase2.make_phase2_status(
"FAIL", errors=["PUBLIC_SYMBOL_MISSING:supersede_p0_scope"]
)
assert status["status"] == "FAIL"
assert status["fail_closed"] is True
assert all(status[flag] is False for flag in phase2.MUTATION_FLAGS)
def test_dynamic_reexport_is_accepted_only_with_explicit_globals_export_mechanism():
source = (
"for _name, _value in _legacy.__dict__.items():\n"
" globals()[_name] = _value\n"
)
result = phase2.validate_public_function_ast(source)
assert result["ok"] is True
assert result["dynamic_reexport"] is True
no_export = "x = 1\n"
result = phase2.validate_public_function_ast(no_export)
assert result["ok"] is False
assert result["missing"] == list(phase2.DEFAULT_REQUIRED_ADMISSION_FUNCTIONS)
def test_status_validator_rejects_mutation_claims():
status = phase2.make_phase2_status("PASS")
assert phase2.validate_phase2_status(status)["read_only"] is True
status["git_mutation"] = True
with pytest.raises(phase2.Phase2StatusError, match="git_mutation"):
phase2.validate_phase2_status(status)
def test_request_payload_contains_request_but_argv_does_not():
payload = phase2.build_remote_payload(
{
"issuer_pid": 123,
"expected_admission_sha256": "a" * 64,
}
)
assert b'"issuer_pid":123' in payload
assert b'"expected_admission_sha256":"' in payload
argv = phase2.build_ssh_argv()
assert all("123" not in part and "aaaa" not in part for part in argv)
def test_live_defaults_use_current_issuer_and_storage_migration_module():
assert phase2.DEFAULT_ISSUER_UNIT == "aios-task-principal-issuer.service"
assert phase2.DEFAULT_MIGRATION_MODULE == "aios_task_principal_storage_migration"
payload = phase2.build_remote_payload()
assert b"aios-task-principal-issuer.service" in payload
assert b"aios_task_principal_storage_migration" in payload
stale_issuer = "aios-task-principal-" + "admission.service"
stale_migration = "aios_task_principal_" + "migration"
assert stale_issuer.encode() not in payload
assert stale_migration.encode() not in payload
+582
View File
@@ -0,0 +1,582 @@
from __future__ import annotations
import base64
from concurrent.futures import ThreadPoolExecutor
import hashlib
from pathlib import Path
import sys
import threading
import pytest
ROOT = Path(__file__).resolve().parents[1]
APP = ROOT / "active-runtime" / "app"
if str(APP) not in sys.path:
sys.path.insert(0, str(APP))
from aios_task_admission_bootstrap import ( # noqa: E402
OwnerDelegationEpoch,
TaskAdmissionBootstrapService,
TaskAdmissionError,
TaskAdmissionGrant,
TaskAdmissionPolicy,
TaskAdmissionStore,
canonical_json_bytes,
)
from aios_task_admission_live_adapters import ( # noqa: E402
AgentTaskAdmissionPort,
BoundOwnerAuthorizationBackend,
)
BASE_OID = "9" * 40
EXECUTION_DIGEST = "a" * 64
TASK_ID = "repository-discovery-p0-20260903"
BRANCH = "agent/repository-discovery-p0-20260902"
IDEMPOTENCY = "repository-discovery-p0-admission-20260903"
FILES = [
"active-runtime/app/aios_repository_discovery.py",
"active-runtime/app/aios_chatgpt_bridge_v2.py",
"docs/gitops/openapi-chatgpt-actions.yaml",
"tests/test_repository_discovery.py",
]
CAPS = [
"governed.task.create",
"agent-task:control",
"task.principal.issue",
"writer.lease",
"governed.write.files",
"git.read",
"testing",
"verification",
]
class FakeAuthorization:
def __init__(self, *, policy_digest: str, now: int = 1_800_000_000):
self.now = now
self.signature_calls = 0
self.epoch_calls = 0
self.signature_ok = True
self.epoch = OwnerDelegationEpoch(
epoch_id="epoch-repository-discovery-20260903",
owner_identity="owner",
trust_domain="aios.owner",
repository="aios/aios",
allowed_branch_prefixes=("agent/repository-discovery-",),
allowed_capabilities=frozenset(CAPS),
allowed_file_prefixes=("active-runtime", "docs", "tests"),
risk_ceiling="medium",
issued_at=now - 60,
not_before=now - 60,
expires_at=now + 3600,
revocation_epoch=0,
policy_digest=policy_digest,
)
def verify_delegation_epoch(self, epoch_id: str) -> OwnerDelegationEpoch:
self.epoch_calls += 1
if epoch_id != self.epoch.epoch_id:
raise TaskAdmissionError("UNKNOWN_EPOCH", 403)
return self.epoch
def verify_owner_signature(
self,
*,
key_id: str,
canonical_payload: bytes,
signature_b64: str,
) -> str:
self.signature_calls += 1
if key_id != "owner-key-1" or not self.signature_ok:
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403)
expected = base64.b64encode(
hashlib.sha256(canonical_payload + b"\0owner-key-1").digest()
).decode("ascii")
if signature_b64 != expected:
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403)
return "owner"
class FakeTaskPort:
def __init__(self):
self.calls = 0
self.grants = []
self._lock = threading.Lock()
def admit(self, grant):
with self._lock:
self.calls += 1
self.grants.append(grant)
return {"task_id": grant.task_id, "state": "QUEUED"}
@pytest.fixture
def policy():
return TaskAdmissionPolicy(
allowed_capabilities=frozenset(CAPS),
max_file_count=16,
max_task_ttl_seconds=3600,
max_envelope_ttl_seconds=900,
max_risk="medium",
)
@pytest.fixture
def service(tmp_path: Path, policy: TaskAdmissionPolicy):
now = 1_800_000_000
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest(), now=now)
port = FakeTaskPort()
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=auth,
task_port=port,
policy=policy,
clock=lambda: now,
)
return svc, auth, port
def request(**overrides):
value = {
"idempotency_key": IDEMPOTENCY,
"task_id": TASK_ID,
"repository": "aios/aios",
"authority_base_oid": BASE_OID,
"target_branch": BRANCH,
"generation": 1,
"execution_digest": EXECUTION_DIGEST,
"requested_capabilities": list(CAPS),
"file_allowlist": list(FILES),
"risk": "medium",
"expires_in_seconds": 600,
}
value.update(overrides)
return value
def test_envelope_exact_replay_returns_same_nonce_and_payload(service):
svc, _, _ = service
first = svc.create_envelope(request())
second = svc.create_envelope(request())
assert first["idempotent"] is False
assert second["idempotent"] is True
assert first["envelope"] == second["envelope"]
assert first["canonical_payload_base64"] == second["canonical_payload_base64"]
def test_envelope_idempotency_conflict_fails_closed(service):
svc, _, _ = service
svc.create_envelope(request())
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_IDEMPOTENCY_CONFLICT"):
svc.create_envelope(request(risk="low"))
@pytest.mark.parametrize(
"field,value,code",
[
("repository", "other/repo", "TASK_ADMISSION_REPOSITORY_REJECTED"),
("target_branch", "main", "TASK_ADMISSION_BRANCH_REJECTED"),
("authority_base_oid", "HEAD", "TASK_ADMISSION_BASE_OID_INVALID"),
("requested_capabilities", ["production.write"], "TASK_ADMISSION_PROHIBITED_CAPABILITY"),
("requested_capabilities", ["git:*"], "TASK_ADMISSION_CAPABILITY_INVALID"),
("file_allowlist", ["../secrets"], "TASK_ADMISSION_FILE_PATH_INVALID"),
("file_allowlist", ["/etc/passwd"], "TASK_ADMISSION_FILE_PATH_INVALID"),
("risk", "high", "TASK_ADMISSION_RISK_OUT_OF_POLICY"),
],
)
def test_envelope_policy_rejects_scope_widening(service, field, value, code):
svc, _, _ = service
with pytest.raises(TaskAdmissionError, match=code):
svc.create_envelope(request(**{field: value}))
def test_valid_delegation_epoch_admits_exact_task_once(service):
svc, auth, port = service
envelope = svc.create_envelope(request())["envelope"]
result = svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert result["schema"] == "aios.task-admission-receipt.v1"
assert result["task_id"] == TASK_ID
assert result["task_state"] == "QUEUED"
assert result["parent_epoch_id"] == auth.epoch.epoch_id
assert port.calls == 1
replay = svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert replay["task_id"] == TASK_ID
assert replay["idempotent"] is True
assert port.calls == 1
def test_post_admission_projection_runs_after_exact_task_readback_once(
tmp_path: Path, policy: TaskAdmissionPolicy
):
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest())
port = FakeTaskPort()
observations = []
def project(grant, task):
observations.append((grant.task_id, task["task_id"], task["state"]))
return {"job_id": "projected-job"}
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=auth,
task_port=port,
policy=policy,
clock=lambda: 1_800_000_000,
post_admission=project,
)
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
proof = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
first = svc.authorize_and_admit(proof)
replay = svc.authorize_and_admit(proof)
assert first["task_id"] == TASK_ID
assert replay["idempotent"] is True
assert observations == [(TASK_ID, TASK_ID, "QUEUED")]
def test_epoch_capability_widening_rejected_before_task_port(service):
svc, auth, port = service
# Envelope policy allows the capability, parent epoch deliberately does not.
auth.epoch = OwnerDelegationEpoch(
**{
**auth.epoch.as_public_dict(),
"allowed_capabilities": frozenset(
item for item in CAPS if item != "writer.lease"
),
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": auth.epoch.allowed_file_prefixes,
}
)
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(
TaskAdmissionError, match="TASK_ADMISSION_EPOCH_CAPABILITY_WIDENING"
):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_epoch_file_scope_widening_rejected(service):
svc, auth, port = service
auth.epoch = OwnerDelegationEpoch(
**{
**auth.epoch.as_public_dict(),
"allowed_capabilities": auth.epoch.allowed_capabilities,
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": ("docs",),
}
)
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_FILE_WIDENING"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_revoked_or_expired_epoch_rejected(service):
svc, auth, port = service
auth.epoch = OwnerDelegationEpoch(
**{
**auth.epoch.as_public_dict(),
"allowed_capabilities": auth.epoch.allowed_capabilities,
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": auth.epoch.allowed_file_prefixes,
"revocation_epoch": 1,
}
)
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_REVOKED"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_owner_signature_path_verifies_canonical_payload(service):
svc, auth, port = service
created = svc.create_envelope(request())
payload = canonical_json_bytes(created["envelope"])
signature = base64.b64encode(
hashlib.sha256(payload + b"\0owner-key-1").digest()
).decode("ascii")
result = svc.authorize_and_admit(
{
"envelope_id": created["envelope"]["envelope_id"],
"authorization_kind": "owner_signature",
"owner_key_id": "owner-key-1",
"signature_base64": signature,
}
)
assert result["authorization_kind"] == "owner_signature"
assert result["owner_identity"] == "owner"
assert auth.signature_calls == 1
assert port.calls == 1
def test_bad_owner_signature_is_rejected(service):
svc, _, port = service
envelope = svc.create_envelope(request())["envelope"]
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_OWNER_SIGNATURE_INVALID"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "owner_signature",
"owner_key_id": "owner-key-1",
"signature_base64": base64.b64encode(b"bad").decode("ascii"),
}
)
assert port.calls == 0
def test_expired_envelope_cannot_admit(tmp_path: Path, policy: TaskAdmissionPolicy):
now = [1_800_000_000]
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest(), now=now[0])
port = FakeTaskPort()
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=auth,
task_port=port,
policy=policy,
clock=lambda: now[0],
)
envelope = svc.create_envelope(request(expires_in_seconds=60))["envelope"]
now[0] += 61
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_ENVELOPE_EXPIRED"):
svc.authorize_and_admit(
{
"envelope_id": envelope["envelope_id"],
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
assert port.calls == 0
def test_concurrent_exact_authorization_calls_admit_once(service):
svc, auth, port = service
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
body = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
with ThreadPoolExecutor(max_workers=16) as pool:
results = list(pool.map(lambda _: svc.authorize_and_admit(body), range(16)))
assert {item["task_id"] for item in results} == {TASK_ID}
assert port.calls == 1
def test_cross_instance_envelope_winner_is_the_durable_value(tmp_path: Path, policy: TaskAdmissionPolicy):
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest())
port = FakeTaskPort()
stores = [TaskAdmissionStore(tmp_path / "shared.sqlite3") for _ in range(16)]
services = [
TaskAdmissionBootstrapService(
store=store, authorization=auth, task_port=port, policy=policy,
clock=lambda: 1_800_000_000,
)
for store in stores
]
with ThreadPoolExecutor(max_workers=16) as pool:
values = list(pool.map(lambda svc: svc.create_envelope(request()), services))
assert len({item["envelope"]["envelope_id"] for item in values}) == 1
assert sum(not item["idempotent"] for item in values) == 1
def test_cross_instance_authorization_calls_create_one_task(tmp_path: Path, policy: TaskAdmissionPolicy):
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest())
port = FakeTaskPort()
database = tmp_path / "shared.sqlite3"
first = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(database), authorization=auth, task_port=port,
policy=policy, clock=lambda: 1_800_000_000,
)
envelope_id = first.create_envelope(request())["envelope"]["envelope_id"]
services = [
TaskAdmissionBootstrapService(
store=TaskAdmissionStore(database), authorization=auth, task_port=port,
policy=policy, clock=lambda: 1_800_000_000,
)
for _ in range(16)
]
body = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
with ThreadPoolExecutor(max_workers=16) as pool:
values = list(pool.map(lambda svc: svc.authorize_and_admit(body), services))
assert {item["task_id"] for item in values} == {TASK_ID}
assert port.calls == 1
@pytest.mark.parametrize("file_name", ["docs//gitops/a.yaml", "docs/./gitops/a.yaml", "docs/a\x00.yaml"])
def test_file_paths_must_already_be_canonical(service, file_name):
svc, _, _ = service
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_FILE_PATH_INVALID"):
svc.create_envelope(request(file_allowlist=[file_name]))
@pytest.mark.parametrize("generation", [True, "1", 1.0, 0])
def test_generation_is_a_strict_positive_integer(service, generation):
svc, _, _ = service
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_GENERATION_INVALID"):
svc.create_envelope(request(generation=generation))
def test_policy_digest_drift_and_changed_proof_cannot_replay(service):
svc, auth, _ = service
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
body = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
svc.authorize_and_admit(body)
svc.policy = TaskAdmissionPolicy(
allowed_capabilities=frozenset(CAPS), policy_digest="policy-has-changed"
)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_POLICY_DIGEST_DRIFT"):
svc.authorize_and_admit(body)
def test_grant_expiry_is_clipped_to_parent_epoch(tmp_path: Path, policy: TaskAdmissionPolicy):
now = 1_800_000_000
auth = FakeAuthorization(policy_digest=policy.normalized_policy_digest(), now=now)
auth.epoch = OwnerDelegationEpoch(
**{**auth.epoch.as_public_dict(), "allowed_capabilities": auth.epoch.allowed_capabilities,
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": auth.epoch.allowed_file_prefixes, "expires_at": now + 120}
)
port = FakeTaskPort()
svc = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"), authorization=auth,
task_port=port, policy=policy, clock=lambda: now,
)
result = svc.authorize_and_admit({
"envelope_id": svc.create_envelope(request())["envelope"]["envelope_id"],
"authorization_kind": "delegation_epoch", "delegation_epoch_id": auth.epoch.epoch_id,
})
assert port.grants[0].expires_at == now + 120
assert result["task_id"] == TASK_ID
def test_owner_backend_uses_bound_caller_and_decoded_armored_signature(policy):
captured = {}
class Verifier:
def verify(self, **kwargs):
captured.update(kwargs)
return True
epoch = FakeAuthorization(policy_digest=policy.normalized_policy_digest()).epoch
backend = BoundOwnerAuthorizationBackend(
authenticated_caller={"uid": "bridge-service"},
verified_epoch_provider=lambda **kwargs: captured.update(epoch_lookup=kwargs) or epoch,
signature_verifier=Verifier(),
)
assert backend.verify_delegation_epoch(epoch.epoch_id) == epoch
assert captured["epoch_lookup"]["caller"] == {"uid": "bridge-service"}
assert backend.verify_owner_signature(
key_id="owner-key-1", canonical_payload=b"exact", signature_b64=base64.b64encode(b"-----BEGIN SSH SIGNATURE-----").decode(),
) == "aios-task-grant-signer"
assert captured["namespace"] == "aios-owner-approval-v1"
assert captured["payload"] == b"exact"
def test_official_task_port_reconciles_ambiguous_create(tmp_path: Path, policy):
grant = TaskAdmissionBootstrapService(
store=TaskAdmissionStore(tmp_path / "admission.sqlite3"),
authorization=FakeAuthorization(policy_digest=policy.normalized_policy_digest()),
task_port=FakeTaskPort(), policy=policy, clock=lambda: 1_800_000_000,
)
# Construct via the public flow so the port sees a production-shaped grant.
created = grant.create_envelope(request())["envelope"]
envelope = grant._load_envelope(created["envelope_id"])
task_grant = TaskAdmissionGrant(
grant_id="task-grant-adapter-test", envelope_id=envelope.envelope_id,
task_id=envelope.task_id, repository=envelope.repository,
authority_base_oid=envelope.authority_base_oid, target_branch=envelope.target_branch,
generation=envelope.generation, execution_digest=envelope.execution_digest,
capabilities=envelope.requested_capabilities, file_allowlist=envelope.file_allowlist,
risk=envelope.risk, trust_domain=envelope.trust_domain,
policy_digest=envelope.policy_digest, authorization_kind="owner_signature",
owner_identity="aios-task-grant-signer", parent_epoch_id=None,
issued_at=1_800_000_000, expires_at=1_800_000_600,
)
class NotFound(Exception):
status = 404
class Transport:
def __init__(self): self.calls = []; self.task = None
def request(self, method, path, body=None):
self.calls.append((method, path, body))
if method == "GET" and self.task is None: raise NotFound()
if method == "POST":
self.task = {key: value for key, value in body.items() if key != "idempotency_key"}
self.task["status"] = "QUEUED"
raise TimeoutError("response lost after side effect")
return self.task
transport = Transport()
plan = {
"external_job_id": "fixture-job-adapter-01", "repository": task_grant.repository,
"target_branch": task_grant.target_branch, "generation": task_grant.generation,
"execution_digest": task_grant.execution_digest,
}
assert AgentTaskAdmissionPort(transport, execution_plan_provider=lambda _: plan).admit(task_grant) == {"task_id": TASK_ID, "state": "QUEUED"}
assert [call[:2] for call in transport.calls] == [
("GET", "/v1/tasks/" + TASK_ID), ("POST", "/v1/tasks"), ("GET", "/v1/tasks/" + TASK_ID),
]
def test_status_is_read_only_and_reports_admitted_task(service):
svc, auth, _ = service
envelope_id = svc.create_envelope(request())["envelope"]["envelope_id"]
before = svc.status(envelope_id)
assert before["status"] == "PENDING_AUTHORIZATION"
assert before["task"] is None
svc.authorize_and_admit(
{
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": auth.epoch.epoch_id,
}
)
after = svc.status(envelope_id)
assert after["status"] == "ADMITTED"
assert after["task"]["task_id"] == TASK_ID
@@ -0,0 +1,46 @@
from __future__ import annotations
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[1]
APP = ROOT / "active-runtime" / "app"
if str(APP) not in sys.path:
sys.path.insert(0, str(APP))
from aios_task_admission_bridge_contract import ( # noqa: E402
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS,
TASK_ADMISSION_ROUTES,
)
def test_bootstrap_route_operation_mapping_is_exact():
assert TASK_ADMISSION_ROUTES == {
"/v2/task-admission/envelope": "getTaskAdmissionEnvelope",
"/v2/task-admission/authorize": "submitTaskAdmissionAuthorization",
"/v2/task-admission/status": "getTaskAdmissionStatus",
}
def test_bootstrap_operations_do_not_replace_writer_or_principal_gates():
operations = set(TASK_ADMISSION_OPERATION_REQUIRED_FIELDS)
assert "acquireWriterLease" not in operations
assert "commitFilesGoverned" not in operations
assert "issueTaskPrincipal" not in operations
assert operations == {
"getTaskAdmissionEnvelope",
"submitTaskAdmissionAuthorization",
"getTaskAdmissionStatus",
}
def test_openapi_fragment_matches_runtime_contract():
text = (
ROOT / "docs" / "gitops" / "openapi-task-admission-bootstrap-fragment.yaml"
).read_text(encoding="utf-8")
for route, operation in TASK_ADMISSION_ROUTES.items():
assert f" {route}:" in text
assert f"operationId: {operation}" in text
assert "global write" not in text.lower()
assert "runShell" not in text
assert "force push" not in text.lower()
+346
View File
@@ -0,0 +1,346 @@
from __future__ import annotations
import ast
from contextlib import contextmanager
import json
import os
from pathlib import Path
import sys
import threading
from http.server import ThreadingHTTPServer
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen
ROOT = Path(__file__).resolve().parents[1]
APP = ROOT / "active-runtime" / "app"
if str(APP) not in sys.path:
sys.path.insert(0, str(APP))
from aios_chatgpt_bridge_v2 import ( # noqa: E402
BridgeV2Adapter,
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS,
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS,
TASK_ADMISSION_OPERATIONS,
)
from aios_chatgpt_bridge_v2_server import ( # noqa: E402
BridgeHandler,
build_bridge_adapter,
)
from aios_task_admission_bootstrap import TaskAdmissionError # noqa: E402
class StubRuntime:
def __init__(self, status: dict[str, object], handlers=None) -> None:
self.status = status
self._handlers = dict(handlers or {})
def runtime_status(self) -> dict[str, object]:
return dict(self.status)
def handlers(self) -> dict[str, object]:
return dict(self._handlers)
class FakeOwnerPort:
def __init__(self) -> None:
self.envelopes: list[dict[str, object]] = []
self.authorizations: list[dict[str, object]] = []
def create_envelope(self, request):
self.envelopes.append(dict(request))
return {"envelope_id": "env-1", "status": "PENDING", "http_status": 201}
def authorize(self, request, task_port):
self.authorizations.append(dict(request))
kind = request.get("authorization_kind")
if kind == "unsigned":
raise TaskAdmissionError("TASK_ADMISSION_AUTHORIZATION_REQUIRED", 400)
if kind == "bad_proof":
raise TaskAdmissionError("TASK_ADMISSION_OWNER_SIGNATURE_INVALID", 403)
return task_port.admit(request)
class FakeTaskPort:
def __init__(self) -> None:
self.calls: list[dict[str, object]] = []
def admit(self, request):
self.calls.append(dict(request))
return {"task_id": request.get("task_id", "task-1"), "state": "QUEUED"}
def _status(**overrides: object) -> dict[str, object]:
value: dict[str, object] = {
"status": "active",
"health": 200,
"governed_writes_enabled": False,
"legacy_global_writes_enabled": False,
"task_principal_writes_enabled": False,
}
value.update(overrides)
return value
def _admission_handlers(owner: FakeOwnerPort, task: FakeTaskPort):
return {
"getTaskAdmissionEnvelope": owner.create_envelope,
"submitTaskAdmissionAuthorization": lambda request: owner.authorize(
request, task
),
"getTaskAdmissionStatus": lambda request: {
"envelope_id": request["envelope_id"],
"status": "PENDING",
},
}
def _post(base_url: str, path: str, body: dict[str, object]):
request = Request(
base_url + path,
data=json.dumps(body).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urlopen(request, timeout=3) as response:
return response.status, json.loads(response.read())
except HTTPError as exc:
return exc.code, json.loads(exc.read())
def _get(base_url: str, path: str):
try:
with urlopen(base_url + path, timeout=3) as response:
return response.status, json.loads(response.read())
except HTTPError as exc:
return exc.code, json.loads(exc.read())
@contextmanager
def _serve(adapter: BridgeV2Adapter):
class TestBridgeHandler(BridgeHandler):
pass
TestBridgeHandler.adapter = adapter
TestBridgeHandler.principal_verifier = None
server = ThreadingHTTPServer(("127.0.0.1", 0), TestBridgeHandler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
yield f"http://127.0.0.1:{server.server_port}"
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
def test_capabilities_expose_admission_only_for_complete_callable_handlers():
runtime = StubRuntime(_status(), {"getRuntimeStatus": lambda request: request})
without_backend = build_bridge_adapter(runtime)
assert not set(TASK_ADMISSION_OPERATIONS) & set(
without_backend.getGovernedWriteCapabilities()["operations"]
)
assert not set(TASK_ADMISSION_OPERATIONS) & set(
without_backend.getGovernedWriteCapabilities()["operation_contracts"]
)
handlers = _admission_handlers(FakeOwnerPort(), FakeTaskPort())
complete = build_bridge_adapter(runtime, task_admission_handlers=handlers)
capabilities = complete.getGovernedWriteCapabilities()
assert set(TASK_ADMISSION_OPERATIONS) <= set(capabilities["operations"])
expected_routes = {
"getTaskAdmissionEnvelope": ("POST", "/v2/task-admission/envelope"),
"submitTaskAdmissionAuthorization": ("POST", "/v2/task-admission/authorize"),
"getTaskAdmissionStatus": ("GET", "/v2/task-admission/status"),
}
for operation in TASK_ADMISSION_OPERATIONS:
contract = capabilities["operation_contracts"][operation]
assert (contract["method"], contract["path"]) == expected_routes[operation]
assert contract["required_fields"] == list(
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS[operation]
)
assert contract["optional_fields"] == list(
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS[operation]
)
def test_partial_task_admission_handlers_are_rejected():
runtime = StubRuntime(_status())
try:
build_bridge_adapter(
runtime,
task_admission_handlers={
"getTaskAdmissionEnvelope": lambda request: request,
},
)
except ValueError as exc:
assert str(exc) == "TASK_ADMISSION_HANDLERS_INCOMPLETE"
else:
raise AssertionError("partial Task Admission handlers must be rejected")
def test_http_admission_backend_missing_returns_404():
adapter = build_bridge_adapter(StubRuntime(_status()))
with _serve(adapter) as base_url:
status, body = _post(
base_url,
"/v2/task-admission/envelope",
{"unexpected": True},
)
assert status == 404
assert body == {"error": "NOT_FOUND"}
def test_http_admission_envelope_and_status_work_without_task_principal():
owner = FakeOwnerPort()
task = FakeTaskPort()
adapter = build_bridge_adapter(
StubRuntime(_status()),
task_admission_handlers=_admission_handlers(owner, task),
)
envelope = {
"idempotency_key": "idempotency-1",
"task_id": "task-1",
"repository": "aios/aios",
"authority_base_oid": "a" * 40,
"target_branch": "agent/task-1",
"generation": 1,
"execution_digest": "b" * 64,
"requested_capabilities": ["testing"],
"file_allowlist": ["tests/test_task.py"],
"risk": "low",
"expires_in_seconds": 60,
}
with _serve(adapter) as base_url:
status, body = _post(base_url, "/v2/task-admission/envelope", envelope)
assert status == 201
assert body["envelope_id"] == "env-1"
status, body = _get(
base_url, "/v2/task-admission/status?envelope_id=" + quote("env-1")
)
assert status == 200
assert body == {"envelope_id": "env-1", "status": "PENDING"}
assert len(owner.envelopes) == 1
assert task.calls == []
def test_http_admission_rejects_bad_queries_and_caller_principal():
owner = FakeOwnerPort()
task = FakeTaskPort()
adapter = build_bridge_adapter(
StubRuntime(_status()),
task_admission_handlers=_admission_handlers(owner, task),
)
envelope = {
"idempotency_key": "idempotency-1",
"task_id": "task-1",
"repository": "aios/aios",
"authority_base_oid": "a" * 40,
"target_branch": "agent/task-1",
"generation": 1,
"execution_digest": "b" * 64,
"requested_capabilities": ["testing"],
"file_allowlist": ["tests/test_task.py"],
"risk": "low",
}
with _serve(adapter) as base_url:
for query in (
"unknown=x",
"envelope_id=env-1&envelope_id=env-2",
"envelope_id=",
):
status, body = _get(base_url, "/v2/task-admission/status?" + query)
assert status == 400
assert body["error"] == "TASK_ADMISSION_QUERY_INVALID"
status, body = _post(
base_url,
"/v2/task-admission/envelope",
{**envelope, "principal": {"task_id": "caller-controlled"}},
)
assert status == 400
assert body == {"error": "CALLER_PRINCIPAL_FORBIDDEN"}
assert owner.envelopes == []
assert task.calls == []
def test_http_admission_proof_failures_use_task_admission_status_and_do_not_mutate():
owner = FakeOwnerPort()
task = FakeTaskPort()
adapter = build_bridge_adapter(
StubRuntime(_status()),
task_admission_handlers=_admission_handlers(owner, task),
)
with _serve(adapter) as base_url:
for kind, expected_status, expected_error in (
("unsigned", 400, "TASK_ADMISSION_AUTHORIZATION_REQUIRED"),
("bad_proof", 403, "TASK_ADMISSION_OWNER_SIGNATURE_INVALID"),
):
status, body = _post(
base_url,
"/v2/task-admission/authorize",
{"envelope_id": "env-1", "authorization_kind": kind},
)
assert status == expected_status
assert body == {"error": expected_error}
assert task.calls == []
def test_existing_governed_mutation_gates_are_unchanged():
request = {
"schema": "aios.writer-lease-request.v2",
"task_id": "task-1",
"job_id": "job-1",
"repository": "aios/aios",
"branch_mode": "CREATE_NEW",
"base_oid": "a" * 40,
"expected_branch_oid": "a" * 40,
"target_branch": "agent/task-1",
"generation": 1,
"idempotency_key": "idempotency-1",
"lease_seconds": 60,
}
runtime = StubRuntime(_status(), {"acquireWriterLease": lambda body: body})
with _serve(build_bridge_adapter(runtime)) as base_url:
status, body = _post(base_url, "/v2/writer-lease", request)
assert status == 423
assert body == {"error": "TASK_PRINCIPAL_WRITES_DISABLED"}
enabled_runtime = StubRuntime(
_status(
governed_writes_enabled=True,
task_principal_writes_enabled=True,
),
{"acquireWriterLease": lambda body: body},
)
with _serve(build_bridge_adapter(enabled_runtime)) as base_url:
status, body = _post(base_url, "/v2/writer-lease", request)
assert status == 401
assert body == {"error": "TASK_PRINCIPAL_VERIFIER_UNAVAILABLE"}
def test_live_bridge_preservation_when_source_root_is_supplied():
configured_source = os.environ.get("AIOS_LIVE_BRIDGE_SOURCE")
if not configured_source:
return
source = Path(configured_source)
live_server = source / "aios_chatgpt_bridge_v2_server.py" if source.is_dir() else source
candidate_server = APP / "aios_chatgpt_bridge_v2_server.py"
live_tree = ast.parse(live_server.read_text(encoding="utf-8"))
candidate_tree = ast.parse(candidate_server.read_text(encoding="utf-8"))
def selected(tree):
return [
node
for node in ast.walk(tree)
if (isinstance(node, ast.ClassDef) and node.name == "GovernedRuntime")
or (isinstance(node, ast.FunctionDef) and node.name == "_verified_request")
]
def without_attributes(nodes):
return [ast.dump(node, include_attributes=False) for node in nodes]
assert without_attributes(selected(candidate_tree)) == without_attributes(
selected(live_tree)
)
@@ -0,0 +1,474 @@
from __future__ import annotations
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import sys
import threading
import subprocess
from dataclasses import replace
import pytest
ROOT = Path(__file__).resolve().parents[1]
APP = ROOT / "active-runtime" / "app"
if str(APP) not in sys.path:
sys.path.insert(0, str(APP))
from aios_task_admission_bootstrap import ( # noqa: E402
OwnerDelegationEpoch,
TaskAdmissionBootstrapService,
TaskAdmissionError,
TaskAdmissionPolicy,
TaskAdmissionStore,
)
NOW = 1_800_000_000
CAPS = ("governed.task.create", "agent-task:control")
class Authorization:
def __init__(self, digest: str):
self.epoch = OwnerDelegationEpoch(
epoch_id="epoch-recovery", owner_identity="owner", trust_domain="aios.owner",
repository="aios/aios", allowed_branch_prefixes=("agent/recovery-",),
allowed_capabilities=frozenset(CAPS), allowed_file_prefixes=("docs",),
risk_ceiling="medium", issued_at=NOW - 1, not_before=NOW - 1,
expires_at=NOW + 600, revocation_epoch=0, policy_digest=digest,
)
def verify_delegation_epoch(self, epoch_id):
if epoch_id != self.epoch.epoch_id:
raise TaskAdmissionError("UNKNOWN_EPOCH", 403)
return self.epoch
def verify_owner_signature(self, **_kwargs):
return "owner"
class RecoveringPort:
def __init__(self, *, fail_after_effect=False, fail_before_effect=False):
self.fail_after_effect = fail_after_effect
self.fail_before_effect = fail_before_effect
self.posts = 0
self.task_ids = set()
self._lock = threading.Lock()
def admit(self, grant):
with self._lock:
if grant.task_id in self.task_ids:
return {"task_id": grant.task_id, "state": "QUEUED"}
if self.fail_before_effect:
self.fail_before_effect = False
raise RuntimeError("before effect")
self.posts += 1
self.task_ids.add(grant.task_id)
if self.fail_after_effect:
self.fail_after_effect = False
raise RuntimeError("after effect")
return {"task_id": grant.task_id, "state": "QUEUED"}
def request():
return {
"idempotency_key": "recovery-admission-key", "task_id": "recovery-task-20260903",
"repository": "aios/aios", "authority_base_oid": "a" * 40,
"target_branch": "agent/recovery-20260903", "generation": 1,
"execution_digest": "b" * 64, "requested_capabilities": list(CAPS),
"file_allowlist": ["docs/recovery.md"], "risk": "medium", "expires_in_seconds": 300,
}
def make_service(path, port):
policy = TaskAdmissionPolicy(allowed_capabilities=frozenset(CAPS))
auth = Authorization(policy.normalized_policy_digest())
return TaskAdmissionBootstrapService(
store=TaskAdmissionStore(path), authorization=auth, task_port=port,
policy=policy, clock=lambda: NOW,
), auth
@pytest.mark.parametrize("after_effect", [True, False])
def test_replay_recovers_crash_window_with_one_effect(tmp_path, after_effect):
database = tmp_path / "admission.sqlite3"
port = RecoveringPort(
fail_after_effect=after_effect, fail_before_effect=not after_effect,
)
first, auth = make_service(database, port)
envelope_id = first.create_envelope(request())["envelope"]["envelope_id"]
proof = {"envelope_id": envelope_id, "authorization_kind": "delegation_epoch", "delegation_epoch_id": auth.epoch.epoch_id}
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_TASK_STATE_UNKNOWN"):
first.authorize_and_admit(proof)
second, _ = make_service(database, port)
replay = second.authorize_and_admit(proof)
assert replay["task_id"] == "recovery-task-20260903"
assert port.posts == 1
def test_cross_instance_recovery_lock_allows_one_effect(tmp_path):
database = tmp_path / "admission.sqlite3"
port = RecoveringPort()
first, auth = make_service(database, port)
envelope_id = first.create_envelope(request())["envelope"]["envelope_id"]
proof = {"envelope_id": envelope_id, "authorization_kind": "delegation_epoch", "delegation_epoch_id": auth.epoch.epoch_id}
services = [make_service(database, port)[0] for _ in range(16)]
with ThreadPoolExecutor(max_workers=16) as pool:
receipts = list(pool.map(lambda svc: svc.authorize_and_admit(proof), services))
assert {value["task_id"] for value in receipts} == {"recovery-task-20260903"}
assert port.posts == 1
def test_changed_valid_epoch_proof_conflicts_after_receipt(tmp_path):
service, auth = make_service(tmp_path / "admission.sqlite3", RecoveringPort())
envelope_id = service.create_envelope(request())["envelope"]["envelope_id"]
first = {"envelope_id": envelope_id, "authorization_kind": "delegation_epoch", "delegation_epoch_id": auth.epoch.epoch_id}
service.authorize_and_admit(first)
auth.epoch = OwnerDelegationEpoch(
**{**auth.epoch.as_public_dict(), "epoch_id": "epoch-recovery-reissued",
"allowed_capabilities": auth.epoch.allowed_capabilities,
"allowed_branch_prefixes": auth.epoch.allowed_branch_prefixes,
"allowed_file_prefixes": auth.epoch.allowed_file_prefixes}
)
changed = {**first, "delegation_epoch_id": auth.epoch.epoch_id}
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_AUTHORIZATION_CONFLICT"):
service.authorize_and_admit(changed)
@pytest.mark.parametrize("value", ["docs/a.md", b"docs/a.md", {"docs/a.md": True}, [], ["docs/a\x01.md"], ["docs\\a.md"]])
def test_file_allowlist_requires_canonical_sequence(tmp_path, value):
service, _ = make_service(tmp_path / "admission.sqlite3", RecoveringPort())
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_(FILE_ALLOWLIST_REJECTED|FILE_PATH_INVALID)"):
service.create_envelope({**request(), "file_allowlist": value})
@pytest.mark.parametrize("branch", ["agent/.hidden-task", "agent/recovery.lock", "agent/recovery@{x", "agent/recovery..task"])
def test_branch_components_reject_git_ambiguous_spelling(tmp_path, branch):
service, _ = make_service(tmp_path / "admission.sqlite3", RecoveringPort())
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_BRANCH_REJECTED"):
service.create_envelope({**request(), "target_branch": branch})
def test_process_death_releases_admission_lock(tmp_path):
database = tmp_path / "death.sqlite3"
script = (
"import sys,os;from pathlib import Path;"
"from aios_task_admission_bootstrap import TaskAdmissionStore;"
"s=TaskAdmissionStore(Path(sys.argv[1]));"
"lock=s.admission_lock('death-envelope');lock.__enter__();os._exit(17)"
)
result = subprocess.run([sys.executable, "-c", script, str(database)], check=False)
assert result.returncode == 17
with TaskAdmissionStore(database).admission_lock("death-envelope", timeout_seconds=1):
pass
def test_persisted_grant_cannot_outlive_its_own_shorter_ttl(tmp_path):
port = RecoveringPort(fail_before_effect=True)
service, auth = make_service(tmp_path / "expiry.sqlite3", port)
service.policy = replace(service.policy, max_task_ttl_seconds=60)
auth.epoch = replace(auth.epoch, policy_digest=service.policy.normalized_policy_digest())
envelope = service.create_envelope(request())["envelope"]
proof = {"envelope_id": envelope["envelope_id"], "authorization_kind": "delegation_epoch", "delegation_epoch_id": auth.epoch.epoch_id}
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_TASK_STATE_UNKNOWN"):
service.authorize_and_admit(proof)
service.clock = lambda: NOW + 61
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_GRANT_EXPIRED"):
service.authorize_and_admit(proof)
assert port.posts == 0
def _adapter_fixture(tmp_path):
from aios_task_admission_live_adapters import AgentTaskAdmissionPort
service, auth = make_service(tmp_path / "adapter.sqlite3", RecoveringPort())
envelope = service.create_envelope(request())["envelope"]
service.authorize_and_admit({"envelope_id": envelope["envelope_id"], "authorization_kind": "delegation_epoch", "delegation_epoch_id": auth.epoch.epoch_id})
with service.store._connection() as con:
import json
from aios_task_admission_bootstrap import TaskAdmissionGrant
raw = json.loads(con.execute("SELECT grant_json FROM task_admission_grants").fetchone()[0])
grant = TaskAdmissionGrant(**{**raw, "capabilities": tuple(raw["capabilities"]), "file_allowlist": tuple(raw["file_allowlist"])})
plan = {"external_job_id": "fixture-exact-job", "repository": grant.repository,
"target_branch": grant.target_branch, "generation": grant.generation,
"execution_digest": grant.execution_digest, "steps": [{"operation": "fixture-only"}]}
class Transport:
posts = 0
task = None
error = None
def request(self, method, path, body=None):
if method == "POST": self.posts += 1; raise AssertionError("unexpected create")
if self.error: raise self.error
return self.task
transport = Transport()
port = AgentTaskAdmissionPort(transport, execution_plan_provider=lambda _: plan)
transport.task = {**port._body(grant, plan), "status": "QUEUED"}
return port, transport, grant, plan
@pytest.mark.parametrize("field", ["metadata", "instructions", "required_capabilities", "constraints", "deadline", "priority"])
def test_existing_task_conflict_never_becomes_absence(tmp_path, field):
port, transport, grant, _ = _adapter_fixture(tmp_path)
transport.task[field] = None
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_TASK_IDENTITY_CONFLICT"):
port.admit(grant)
assert transport.posts == 0
def test_unknown_get_does_not_create_task(tmp_path):
port, transport, grant, _ = _adapter_fixture(tmp_path)
transport.error = TimeoutError("fixture readback unavailable")
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_TASK_STATE_UNKNOWN"):
port.admit(grant)
assert transport.posts == 0
def test_execution_provider_keeps_full_payload_and_rejects_type_drift(tmp_path):
port, transport, grant, plan = _adapter_fixture(tmp_path)
assert port._execution_plan(grant) == plan
plan["generation"] = True
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EXECUTION_PLAN_CONFLICT"):
port.admit(grant)
assert transport.posts == 0
def test_signed_epoch_host_provider_reverifies_signature_and_policy(tmp_path):
import hashlib
import json
import time
from aios_task_admission_bootstrap import canonical_json_bytes
from aios_task_admission_live_adapters import (
SignedEpochDirectoryProvider,
task_admission_policy_from_epoch,
)
class Verifier:
def __init__(self):
self.calls = []
def verify(self, **kwargs):
self.calls.append(kwargs)
return True
now = int(time.time())
epoch_value = {
"schema": "aios.owner-delegation-epoch.v1",
"epoch_id": "owner-epoch-host-test-20260903",
"owner_identity": "aios-task-grant-signer",
"trust_domain": "aios.owner",
"repository": "aios/aios",
"allowed_branch_prefixes": ["agent/p0-test"],
"allowed_capabilities": ["git.read", "governed.write.file"],
"allowed_file_prefixes": ["active-runtime/app/example.py"],
"risk_ceiling": "medium",
"issued_at": now - 1,
"not_before": now - 1,
"expires_at": now + 600,
"revocation_epoch": 0,
"policy_digest": "a" * 64,
}
root = tmp_path / "epoch"
root.mkdir(mode=0o700)
(root / "owner-delegation-epoch.canonical.json").write_bytes(
canonical_json_bytes(epoch_value)
)
(root / "owner-delegation-epoch.sig").write_text(
"-----BEGIN SSH SIGNATURE-----\nabc\n-----END SSH SIGNATURE-----\n",
encoding="utf-8",
)
(root / "owner-delegation-epoch.key-id").write_text(
"SHA256:test-key\n",
encoding="utf-8",
)
revocations = tmp_path / "revocations.json"
revocations.write_bytes(
canonical_json_bytes({
"schema": "aios.owner-delegation-revocations.v1",
"revoked_epoch_ids": [],
})
)
for path in (
root / "owner-delegation-epoch.canonical.json",
root / "owner-delegation-epoch.sig",
root / "owner-delegation-epoch.key-id",
revocations,
):
path.chmod(0o600)
verifier = Verifier()
caller = {"service": "test", "trust_domain": "aios.owner"}
provider = SignedEpochDirectoryProvider(
root=root,
verifier=verifier,
revocation_file=revocations,
expected_caller=caller,
require_root_owner=False,
clock=lambda: now,
)
epoch = provider(epoch_id=epoch_value["epoch_id"], caller=caller)
policy = task_admission_policy_from_epoch(epoch)
assert verifier.calls[0]["namespace"] == "aios-owner-approval-v1"
assert verifier.calls[0]["payload"] == canonical_json_bytes(epoch_value)
assert policy.allowed_capabilities == frozenset(
epoch_value["allowed_capabilities"]
)
assert policy.policy_digest == epoch_value["policy_digest"]
def test_signed_epoch_host_provider_rejects_revocation_and_wrong_caller(tmp_path):
import time
from aios_task_admission_bootstrap import canonical_json_bytes
from aios_task_admission_live_adapters import SignedEpochDirectoryProvider
class Verifier:
def verify(self, **_kwargs):
return True
now = int(time.time())
epoch_id = "owner-epoch-host-revoked-20260903"
root = tmp_path / "epoch"
root.mkdir(mode=0o700)
value = {
"schema": "aios.owner-delegation-epoch.v1",
"epoch_id": epoch_id,
"owner_identity": "aios-task-grant-signer",
"trust_domain": "aios.owner",
"repository": "aios/aios",
"allowed_branch_prefixes": ["agent/p0-test"],
"allowed_capabilities": ["git.read"],
"allowed_file_prefixes": ["docs/test.md"],
"risk_ceiling": "medium",
"issued_at": now - 1,
"not_before": now - 1,
"expires_at": now + 600,
"revocation_epoch": 0,
"policy_digest": "b" * 64,
}
(root / "owner-delegation-epoch.canonical.json").write_bytes(
canonical_json_bytes(value)
)
(root / "owner-delegation-epoch.sig").write_text(
"sig", encoding="utf-8"
)
(root / "owner-delegation-epoch.key-id").write_text(
"SHA256:test-key", encoding="utf-8"
)
revocations = tmp_path / "revocations.json"
revocations.write_bytes(
canonical_json_bytes({
"schema": "aios.owner-delegation-revocations.v1",
"revoked_epoch_ids": [epoch_id],
})
)
for path in list(root.iterdir()) + [revocations]:
path.chmod(0o600)
caller = {"service": "test", "trust_domain": "aios.owner"}
provider = SignedEpochDirectoryProvider(
root=root,
verifier=Verifier(),
revocation_file=revocations,
expected_caller=caller,
require_root_owner=False,
clock=lambda: now,
)
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_CALLER_BINDING_MISMATCH"):
provider(epoch_id=epoch_id, caller={"service": "wrong"})
with pytest.raises(TaskAdmissionError, match="TASK_ADMISSION_EPOCH_REVOKED"):
provider(epoch_id=epoch_id, caller=caller)
def test_execution_plan_host_provider_is_content_addressed(tmp_path):
import hashlib
from types import SimpleNamespace
from aios_task_admission_bootstrap import canonical_json_bytes
from aios_task_admission_live_adapters import ExecutionPlanDirectoryProvider
plan = {
"task_id": "p0-task-admission-bootstrap-20260903",
"repository": "aios/aios",
"target_branch": "agent/p0-task-admission-bootstrap-repair-20260903",
"generation": 1,
"external_job_id": "job-host-test",
"files": [],
}
digest = hashlib.sha256(canonical_json_bytes(plan)).hexdigest()
root = tmp_path / "plans"
root.mkdir(mode=0o700)
path = root / (digest + ".json")
path.write_bytes(canonical_json_bytes(plan))
path.chmod(0o600)
grant = SimpleNamespace(
execution_digest=digest,
task_id=plan["task_id"],
repository=plan["repository"],
target_branch=plan["target_branch"],
generation=1,
)
provider = ExecutionPlanDirectoryProvider(
root=root,
require_root_owner=False,
)
value = provider(grant)
assert value["execution_digest"] == digest
assert value["external_job_id"] == "job-host-test"
def test_bridge_persistent_consumer_is_opt_in_and_default_fail_closed():
source = (
ROOT / "active-runtime" / "app" / "aios_chatgpt_bridge_v2_server.py"
).read_text(encoding="utf-8")
assert 'os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip()' in source
assert "build_task_admission_handlers_from_environment" in source
assert "task_admission_handlers is None" in source
def test_bridge_persistent_host_is_opt_in_and_activation_file_is_fail_closed():
text = (APP / "aios_chatgpt_bridge_v2_server.py").read_text(encoding="utf-8")
assert 'AIOS_TASK_ADMISSION_EPOCH_ROOT' in text
assert 'AIOS_TASK_ADMISSION_ACTIVATION_FILE' in text
assert '/var/lib/aios-gitops/bridge-v2-staging/task-admission-activation.json' in text
assert 'Path(activation_raw).is_file()' in text
assert 'build_task_admission_handlers_from_activation_file' in text
def test_host_default_state_database_stays_inside_existing_bridge_state_root():
text = (APP / "aios_task_admission_live_adapters.py").read_text(encoding="utf-8")
assert '/var/lib/aios-gitops/bridge-v2-staging/task-admission.sqlite3' in text
assert '/var/lib/aios-task-admission/state.sqlite3' not in text
def test_host_posix_permission_gate_is_skipped_when_posix_metadata_is_not_authoritative(
tmp_path,
monkeypatch,
):
import aios_task_admission_live_adapters as live
root = tmp_path / "non-posix-mode"
root.mkdir()
target = root / "value.json"
target.write_text("{}", encoding="utf-8")
# Deliberately make the synthesized mode permissive. On Windows this is
# normal and must not be interpreted as a POSIX trust failure.
root.chmod(0o777)
target.chmod(0o666)
monkeypatch.setattr(live, "_host_posix_metadata_enforced", lambda: False)
live._host_require_directory(root, root_owned=True)
live._host_require_regular(target, root_owned=True)
@pytest.mark.skipif(
__import__("os").name != "posix",
reason="POSIX mode-bit enforcement is a Linux/Unix trust boundary",
)
def test_host_posix_permission_gate_rejects_group_or_other_writable(tmp_path):
import aios_task_admission_live_adapters as live
root = tmp_path / "posix-mode"
root.mkdir(mode=0o700)
root.chmod(0o777)
with pytest.raises(
live.TaskAdmissionHostError,
match="SECURE_DIRECTORY_WRITABLE_BY_GROUP_OR_OTHER",
):
live._host_require_directory(root, root_owned=False)