Compare commits

..
Author SHA1 Message Date
owner 8bf363143e agent: baseline 1.1 evidence materialization + builder readiness split
Separate Builder readiness (infrastructure-only) from build (recipe-evidence
gated). Add Evidence Materializer (validator/projector, never invents release
evidence). Unify Source Admission policy digest to exact policy file SHA-256
through one helper/constructor. Add HMAC-audited isolated-peer policy rebind.

Base: 2852c2a403 (exact)
Files: 10 (6 modified + 4 added), +1513/-40
Patch SHA-256: 85be2b4e369a7d39eba0f7b7f1c2641a9eabdf0276d4d48d371cfc348465ed51
2026-09-08 15:45:34 +08:00
owner 2852c2a403 feat(governed): Release Migration Plan (AIOS-BASELINE-1.1-RELEASE-MIGRATION-PLAN) 2026-09-08 13:09:40 +08:00
owner 9adec87c4b feat(governed): Release Source Admission (AIOS-BASELINE-1.1-RELEASE-SOURCE-ADMISSION)
Make governed release source closure explicit so a pushed governed candidate
can enter the authoritative Git/release closure without manual runtime
overwrite or fixed-recipe bypass.

- Gitea machine transport: add _safe_branch() and exact OID/branch verification.
- release_source_admission.py (new): admit a governed source by verifying remote
  branch -> exact OID, fetching only to refs/aios/release-sources/<OID>,
  validating authoritative main ancestry + tree OID, and writing a policy-bound,
  HMAC-authenticated immutable admission record (source_admission_hmac).
- trusted candidate builder: read-only admission verifier, remains
  PrivateNetwork / read-only; missing admission surfaces SOURCE_ADMISSION_REQUIRED.
- Model API / release saga: consume admission records and return machine-actionable
  SOURCE_ADMISSION_REQUIRED (not NOT_FOUND) when a non-fixed source is unadmitted.
- systemd: add aios-release-source-admission.service (oneshot, root/gitops,
  AIOS_GITOPS_WRITES_ENABLED=false, independent source_admission_hmac credential);
  wire source_admission_hmac into trusted builder; update staging unit.
- Fixed V2R2C2/V2R2C3 recipes are not overridden by admission.
- Tests: new release_source_admission + adjusted builder/model-api/live-executor/
  readiness tests.

Verified: 13-file after_sha256 byte-exact; py_compile PASS;
Linux 326 targeted passed / 0 failed; acceptance 165; regression 1401;
systemd-analyze verify 3/3 PASS.

Base: a320ea3 (agent/baseline-1.1-principal-recovery-20260908).
2026-09-08 11:44:09 +08:00
owner a320ea391e feat(governed): TaskPrincipal auto-recovery (AIOS-BASELINE-1.1-PRODUCTION-CLOSURE)
Integrate the TaskPrincipal auto-recovery patch from the Agent Execution
Handoff 2026-09-08 onto the Baseline 1.1 mainline candidate.

Server-side execution principal recovery:
- TaskAdmissionStore.get_grant_by_id() / get_grant_by_task_id(): read-only
  immutable grant lookup from durable admission grant/receipt state.
- TaskAdmissionPrincipalProvider: recover short-lived execution principal
  from durable admission grant + Owner epoch + authoritative official Task;
  only accepts CLAIMED/RUNNING tasks; re-verifies epoch/capability/task/job/
  repository/branch/generation before every mutation; caller JSON cannot
  supply principal; all miss-scopes fail closed.
- Bridge main() wires the admission runtime verifier back into
  BridgeHandler.principal_verifier; trusted hosts may still inject.
- staging unit: AIOS_P0_TASK_PRINCIPAL_WRITES_ENABLED=true while
  governed_writes_enabled still fail-closed on verifier readiness + legacy
  writes=false.
- OpenAPI contract updated to server-side principal recovery.
- New negative tests + adjusted recovery tests.

Verified before applying: all 8 modified files matched manifest before_sha256
(LF-normalized), so applied after-state bytes directly (no 3-way merge needed).
Index blobs match manifest after_sha256 for all 9 files.
Targeted suite: 81 passed / 1 skipped (POSIX mode-bit, Linux env) on CPython 3.13.

Base: 6ffd7fe (Baseline 1.1 port); authoritative main 0136a534.
2026-09-08 10:07:06 +08:00
owner 6ffd7fe19c feat(governed): port Implementation Baseline 1.1 (57 files)
Apply the approved Implementation Baseline 1.1 architecture revisions onto the
current mainline, verified faithful to the delivered change bundle:

- Add aios_baseline: signed owner-delegation-grant.v1 / operational-epoch.v1 /
  task-principal.v1 auth chain, runtime contract + deployment-truth evaluator,
  declarative operation registry (effect-class + fail-closed dispatch),
  lineage ledger, release/recipe/saga ledger, bounded optimization controller,
  authenticated HTTP transport, resident worker loop and systemd host probe.
- Extend existing Job authority (aios_gitops_job_controller/executor) with
  durable intent/receipt/reconcile and fail-closed admission; harden
  task_plane/store heartbeat CAS and terminal-state fencing; make
  reconciliation_controller fail-closed on incomplete/drift evidence.
- Add isolated-validation profile templates (deploy/baseline-v1.1, systemd
  .example), source change/rollback tooling (tools/baseline_v1_1), the
  AT-01..AT-23 acceptance matrix, M0-M5 runbook, installation/rollback docs and
  the baseline_v1_1 test suite.
- Keep legacy write authorities untouched; isolated-validation profile only.

Staged blobs verified byte-identical to the delivered source-changes bundle.
Cross-platform baseline tests pass; systemd/fcntl-dependent paths require the
declared Linux x86_64 + CPython 3.12 + systemd runtime (site-bound).
2026-09-08 03:51:26 +08:00
331 changed files with 933 additions and 89234 deletions
+3 -10
View File
@@ -4,14 +4,7 @@ title = "AIOS repository secret scan policy"
useDefault = true
[allowlist]
description = "Exact non-secret fixtures: Task Admission idempotency test fixture and release-freeze git commit OIDs (generic-api-key false positive on 40-hex git hashes)"
description = "Exact non-secret Task Admission test idempotency fixture in the historical PR commit"
condition = "AND"
paths = [
'''tests/test_task_admission_bootstrap_http\.py$''',
'''docs/V2R2C2-P0-convergence-20260909\.md$''',
'''artifacts/round3-provider-auth/ledger\.json$''',
]
regexes = [
'''idempotency-1''',
'''f7f53d0568767d6deec2c47008a89f4d8413f93d''',
]
paths = ['''tests/test_task_admission_bootstrap_http\.py$''']
regexes = ['''idempotency-1''']
+14 -53
View File
@@ -1,79 +1,40 @@
"""API adapter for agent context routing and visibility."""
from collections.abc import Mapping
from typing import Any
try:
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter
except Exception:
APIRouter = HTTPException = None
APIRouter = None
from .agent_context_dashboard import AgentContextDashboard
from .agent_context_rbac import AgentContextRBAC
from .agent_context_resolver import AgentContextResolver
from .agent_observability_api import ApiSecurityError, authenticated_principal, set_authenticator
resolver = AgentContextResolver()
rbac = AgentContextRBAC()
dashboard = AgentContextDashboard()
_AUTHORITY_KEYS = frozenset({"user_id", "group_id", "role", "owner", "owner_policy", "group_policy", "agent_default"})
def resolve_agent_context(payload: dict[str, Any]) -> dict[str, Any]:
principal = authenticated_principal()
user_id = principal["user_id"]
group_id = principal.get("group_id")
role = principal["role"]
if role == "group_admin" and (not isinstance(group_id, str) or not group_id):
raise ApiSecurityError(403, "GROUP_SCOPE_REQUIRED")
route = rbac.route(user_id, group_id, role)
def server_policy(name: str) -> dict[str, Any] | None:
value = principal.get(name)
if value is None:
return None
if not isinstance(value, Mapping):
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_POLICY")
return dict(value)
def client_overlay(name: str) -> dict[str, Any] | None:
value = payload.get(name)
if value is None:
return None
if not isinstance(value, Mapping):
raise ApiSecurityError(403, "INVALID_CONTEXT_OVERLAY")
return {key: item for key, item in value.items() if key not in _AUTHORITY_KEYS}
defaults = server_policy("agent_default") or {}
defaults.update(user_id=user_id, group_id=group_id or "", role=role)
route = rbac.route(payload["user_id"], payload.get("group_id"), payload.get("role", "user"))
context = resolver.resolve(
user_id=user_id,
group_id=group_id or "",
session=client_overlay("session"),
user_profile=client_overlay("user_profile"),
group_policy=server_policy("group_policy"),
agent_default=defaults,
owner_policy=server_policy("owner_policy") if role == "owner" else None,
owner=role == "owner",
user_id=payload["user_id"],
group_id=payload.get("group_id", ""),
session=payload.get("session"),
user_profile=payload.get("user_profile"),
group_policy=payload.get("group_policy"),
agent_default=payload.get("agent_default"),
owner_policy=payload.get("owner_policy"),
owner=payload.get("role") == "owner",
)
return {"routing": route.to_dict(), "context": context.to_dict()}
def get_context_dashboard() -> dict[str, Any]:
authenticated_principal()
return dashboard.overview()
def _http(call):
try:
return call()
except ApiSecurityError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=exc.status_code, detail=exc.code) from exc
def build_router():
if APIRouter is None:
return None
@@ -81,15 +42,15 @@ def build_router():
@router.post("/resolve", operation_id="resolveAgentContext")
def resolve(payload: dict[str, Any]):
return _http(lambda: resolve_agent_context(payload))
return resolve_agent_context(payload)
@router.get("/overview", operation_id="getAgentContextDashboard")
def overview():
return _http(get_context_dashboard)
return get_context_dashboard()
return router
router = build_router()
__all__ = ["router", "set_authenticator", "resolve_agent_context", "get_context_dashboard"]
__all__ = ["router", "resolve_agent_context", "get_context_dashboard"]
+36 -122
View File
@@ -1,129 +1,47 @@
"""Authenticated loopback API for the unified Agent Event Bus."""
"""Actions API for unified Agent Event Bus."""
from __future__ import annotations
from collections.abc import Callable, Mapping
import ipaddress
from typing import Any
try:
from fastapi import APIRouter, HTTPException, Request
except Exception: # pragma: no cover
APIRouter = HTTPException = Request = None
from fastapi import APIRouter
except Exception:
APIRouter = None
from .agent_event_bus import AgentEventBus
from .agent_observability_api import ApiSecurityError, authenticated_principal, set_authenticator
bus = AgentEventBus()
_CREATE_FIELDS = frozenset({"kind", "source", "target", "summary", "payload", "correlation_id"})
def _authorize(client_host: str | None) -> dict[str, Any]:
try:
address = ipaddress.ip_address(client_host or "")
except ValueError as exc:
raise ApiSecurityError(403, "LOOPBACK_REQUIRED") from exc
if not address.is_loopback:
raise ApiSecurityError(403, "LOOPBACK_REQUIRED")
return authenticated_principal()
def create_agent_event(payload: dict[str, Any]) -> dict[str, Any]:
return {"status": "ok", "event": bus.publish(**payload)}
def _event_scope(principal: Mapping[str, Any]) -> tuple[str, frozenset[str]]:
tenant_id = principal.get("tenant_id")
targets = principal.get("allowed_event_targets")
if not isinstance(tenant_id, str) or not tenant_id:
raise ApiSecurityError(403, "EVENT_TENANT_SCOPE_REQUIRED")
if (not isinstance(targets, (list, tuple, set, frozenset)) or not targets
or any(not isinstance(target, str) or not target for target in targets)):
raise ApiSecurityError(403, "EVENT_TARGET_SCOPE_REQUIRED")
return tenant_id, frozenset(targets)
def list_agent_events(kind: str | None = None, status: str = "pending") -> dict[str, Any]:
return {"status": "ok", "events": bus.list_events(kind, status)}
def _authorized_event(
event: Mapping[str, Any] | None, tenant_id: str, targets: frozenset[str],
) -> Mapping[str, Any] | None:
if event is None:
return None
if event.get("tenant_id") != tenant_id or event.get("target") not in targets:
raise ApiSecurityError(403, "EVENT_SCOPE_DENIED")
return event
def get_agent_event(event_id: str) -> dict[str, Any]:
return {"status": "ok", "event": bus.get(event_id)}
def create_agent_event(payload: Mapping[str, Any], *, client_host: str | None = None) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
if set(payload) - _CREATE_FIELDS:
raise ApiSecurityError(403, "EVENT_FIELDS_FORBIDDEN")
required = ("kind", "target", "summary", "payload")
if any(name not in payload for name in required):
raise ApiSecurityError(403, "INVALID_EVENT")
normalized = {name: payload[name] for name in required}
if not isinstance(normalized["target"], str) or normalized["target"] not in targets:
raise ApiSecurityError(403, "EVENT_TARGET_SCOPE_DENIED")
normalized["source"] = principal["user_id"]
normalized["correlation_id"] = payload.get("correlation_id")
normalized["tenant_id"] = tenant_id
return {"status": "ok", "event": bus.publish(**normalized)}
def transition_agent_event(event_id: str, status: str, actor: str = "system") -> dict[str, Any]:
return {"status": "ok", "event": bus.transition(event_id, status, actor)}
def list_agent_events(
kind: str | None = None, status: str = "pending", *, client_host: str | None = None,
) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
return {"status": "ok", "events": bus.list_events(
kind, status, tenant_id=tenant_id, targets=targets,
)}
def agent_event_stats() -> dict[str, Any]:
return {"status": "ok", "stats": bus.stats()}
def get_agent_event(event_id: str, *, client_host: str | None = None) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
return {"status": "ok", "event": _authorized_event(bus.get(event_id), tenant_id, targets)}
# Compatibility adapters
def list_gpt_inbox_messages():
return list_agent_events(kind="gpt_inbox")
def transition_agent_event(
event_id: str, status: str, actor: str = "system", *, client_host: str | None = None,
) -> dict[str, Any]:
del actor
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
_authorized_event(bus.get(event_id), tenant_id, targets)
return {"status": "ok", "event": bus.transition(
event_id, status, principal["user_id"], expected_tenant_id=tenant_id,
expected_targets=targets,
)}
def agent_event_stats(*, client_host: str | None = None) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
return {"status": "ok", "stats": bus.stats(tenant_id=tenant_id, targets=targets)}
def list_gpt_inbox_messages(*, client_host: str | None = None):
return list_agent_events(kind="gpt_inbox", client_host=client_host)
def ack_gpt_inbox_message(event_id: str, *, client_host: str | None = None):
return transition_agent_event(event_id, "completed", client_host=client_host)
def _host(request: Any) -> str | None:
client = getattr(request, "client", None)
return getattr(client, "host", None)
def _http(call: Callable[[], Any]) -> Any:
try:
return call()
except ApiSecurityError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=exc.status_code, detail=exc.code) from exc
except ValueError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=400, detail=str(exc)) from exc
def ack_gpt_inbox_message(event_id: str):
return transition_agent_event(event_id, "completed", "custom-gpt")
def build_router():
@@ -132,32 +50,28 @@ def build_router():
router = APIRouter(prefix="/agent-events", tags=["agent-events"])
@router.post("", operation_id="createAgentEvent")
def create(payload: dict[str, Any], request: Request):
return _http(lambda: create_agent_event(payload, client_host=_host(request)))
def create(payload: dict[str, Any]):
return create_agent_event(payload)
@router.get("", operation_id="listAgentEvents")
def listing(request: Request, kind: str | None = None, status: str = "pending"):
return _http(lambda: list_agent_events(kind, status, client_host=_host(request)))
@router.get("/stats/overview", operation_id="getAgentEventStats")
def stats(request: Request):
return _http(lambda: agent_event_stats(client_host=_host(request)))
def listing(kind: str | None = None, status: str = "pending"):
return list_agent_events(kind, status)
@router.get("/{event_id}", operation_id="getAgentEvent")
def get(event_id: str, request: Request):
return _http(lambda: get_agent_event(event_id, client_host=_host(request)))
def get(event_id: str):
return get_agent_event(event_id)
@router.post("/{event_id}/transition", operation_id="transitionAgentEvent")
def transition(event_id: str, payload: dict[str, Any], request: Request):
return _http(lambda: transition_agent_event(
event_id, payload.get("status", ""), payload.get("actor", "system"),
client_host=_host(request),
))
def transition(event_id: str, payload: dict[str, Any]):
return transition_agent_event(event_id, payload["status"], payload.get("actor", "system"))
@router.get("/stats/overview", operation_id="getAgentEventStats")
def stats():
return agent_event_stats()
return router
router = build_router()
__all__ = [
"router", "set_authenticator", "create_agent_event", "list_agent_events",
"get_agent_event", "transition_agent_event", "agent_event_stats",
]
__all__ = ["router", "create_agent_event", "list_agent_events", "get_agent_event", "transition_agent_event"]
+57 -192
View File
@@ -1,4 +1,10 @@
"""Filesystem-backed Agent Event Bus with strict path contracts."""
"""Unified Agent Event Bus.
Unifies commands, GPT messages, replies, approvals, audit and reports into one
state machine. Compatibility layers can filter events by kind instead of keeping
separate queues.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
@@ -6,12 +12,8 @@ from datetime import datetime, timezone
from hashlib import sha256
from pathlib import Path
import json
import os
import stat
from typing import Any
from .security_path_guards import contained_path, safe_segment, write_text_no_follow
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
@@ -25,7 +27,6 @@ class AgentEvent:
target: str
summary: str
payload: dict[str, Any]
tenant_id: str | None = None
status: str = "pending"
correlation_id: str | None = None
created_at: str = field(default_factory=utc_now)
@@ -38,223 +39,87 @@ class AgentEvent:
class AgentEventBus:
STATES = ("pending", "running", "completed", "failed")
_DIR_FLAGS = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
def __init__(self, root: str = "agents/events"):
self.root = Path(root).absolute()
if self.root.is_symlink():
raise ValueError("symlink event root forbidden")
self.root = Path(root)
for state in self.STATES:
(self.root / state).mkdir(parents=True, exist_ok=True)
(self.root / "audit").mkdir(parents=True, exist_ok=True)
def publish(
self, kind: str, source: str, target: str, summary: str,
payload: dict[str, Any], correlation_id: str | None = None,
tenant_id: str | None = None,
self,
kind: str,
source: str,
target: str,
summary: str,
payload: dict[str, Any],
correlation_id: str | None = None,
) -> dict[str, Any]:
if not all(isinstance(value, str) and value for value in (kind, source, target, summary)):
raise ValueError("event text fields must be nonempty strings")
if not isinstance(payload, dict):
raise ValueError("event payload must be an object")
if tenant_id is not None and (not isinstance(tenant_id, str) or not tenant_id):
raise ValueError("invalid event tenant")
identity = json.dumps(
{"kind": kind, "source": source, "target": target, "summary": summary,
"payload": payload, "correlation_id": correlation_id, "tenant_id": tenant_id},
sort_keys=True, ensure_ascii=False, allow_nan=False, separators=(",", ":"),
)
event_id = "evt_" + sha256(identity.encode("utf-8")).hexdigest()[:16]
safe_segment(event_id)
event = AgentEvent(
event_id, kind, source, target, summary, payload,
tenant_id=tenant_id, correlation_id=correlation_id,
)
event_id = "evt_" + sha256(
json.dumps(
{"kind": kind, "source": source, "summary": summary, "payload": payload},
sort_keys=True,
ensure_ascii=False,
).encode("utf-8")
).hexdigest()[:16]
event = AgentEvent(event_id, kind, source, target, summary, payload, correlation_id=correlation_id)
self._write("pending", event.to_dict())
self._audit("created", event.to_dict())
return event.to_dict()
def list_events(
self, kind: str | None = None, status: str = "pending", limit: int = 100,
*, tenant_id: str | None = None, targets: frozenset[str] | None = None,
) -> list[dict[str, Any]]:
self._state(status)
if not isinstance(limit, int) or isinstance(limit, bool) or limit < 0 or limit > 1000:
raise ValueError("invalid event limit")
if limit == 0:
return []
result: list[dict[str, Any]] = []
for name in sorted(self._names(status)):
if not name.endswith(".json"):
continue
event_id = safe_segment(name[:-5])
event = self._read(status, event_id)
def list_events(self, kind: str | None = None, status: str = "pending", limit: int = 100) -> list[dict[str, Any]]:
result = []
for path in sorted((self.root / status).glob("*.json")):
event = self._read(path)
if kind and event.get("kind") != kind:
continue
if tenant_id is not None and event.get("tenant_id") != tenant_id:
continue
if targets is not None and event.get("target") not in targets:
continue
result.append(event)
if len(result) == limit:
break
return result
return result[:limit]
def get(self, event_id: str) -> dict[str, Any] | None:
safe_segment(event_id)
for state in self.STATES:
if f"{event_id}.json" in self._names(state):
return self._read(state, event_id)
path = self.root / state / f"{event_id}.json"
if path.exists():
return self._read(path)
return None
def transition(
self, event_id: str, status: str, actor: str | None = None,
*, expected_tenant_id: str | None = None,
expected_targets: frozenset[str] | None = None,
) -> dict[str, Any]:
safe_segment(event_id)
self._state(status) # validate before any lookup or mutation
def transition(self, event_id: str, status: str, actor: str | None = None) -> dict[str, Any]:
event = self.get(event_id)
if not event:
return {"status": "not_found", "id": event_id}
if expected_tenant_id is not None and event.get("tenant_id") != expected_tenant_id:
raise ValueError("event tenant mismatch")
if expected_targets is not None and event.get("target") not in expected_targets:
raise ValueError("event target mismatch")
old = event.get("status")
self._state(old)
event.update(status=status, actor=actor, updated_at=utc_now())
if status not in self.STATES:
return {"status": "invalid_state"}
old = event.get("status", "pending")
old_path = self.root / old / f"{event_id}.json"
event["status"] = status
event["actor"] = actor
event["updated_at"] = utc_now()
self._write(status, event)
if old != status:
self._unlink(old, event_id)
if old_path.exists() and old != status:
old_path.unlink()
self._audit("transition", event, {"from": old, "to": status})
return event
def stats(
self, *, tenant_id: str | None = None, targets: frozenset[str] | None = None,
) -> dict[str, Any]:
if tenant_id is not None:
events = {
state: len(self.list_events(status=state, limit=1000, tenant_id=tenant_id, targets=targets))
for state in self.STATES
}
audit_events = 0
for name in self._names("audit"):
if not name.endswith(".json"):
continue
audit = self._read("audit", safe_segment(name[:-5]))
event = audit.get("event") if isinstance(audit, dict) else None
if (isinstance(event, dict) and event.get("tenant_id") == tenant_id
and (targets is None or event.get("target") in targets)):
audit_events += 1
return {"events": events, "audit_events": audit_events, "status": "healthy"}
def stats(self) -> dict[str, Any]:
return {
"events": {state: sum(name.endswith(".json") for name in self._names(state)) for state in self.STATES},
"audit_events": sum(name.endswith(".json") for name in self._names("audit")),
"events": {state: len(list((self.root / state).glob("*.json"))) for state in self.STATES},
"audit_events": len(list((self.root / "audit").glob("*.json"))),
"status": "healthy",
}
def _state(self, state: Any) -> str:
if state not in self.STATES:
raise ValueError("invalid event state")
return str(state)
def _ensure_root(self) -> None:
if os.name != "posix" or not hasattr(os, "O_NOFOLLOW"):
raise OSError("event storage requires POSIX no-follow support")
self.root.mkdir(parents=True, exist_ok=True)
if self.root.is_symlink() or not self.root.is_dir():
raise ValueError("invalid event root")
def _write(self, state: str, data: dict[str, Any]) -> None:
if state != "audit":
self._state(state)
if not isinstance(data.get("id"), str):
raise ValueError("event id must be a string")
event_id = safe_segment(data["id"])
self._ensure_root()
write_text_no_follow(
self.root, f"{state}/{event_id}.json",
json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False) + "\n",
def _write(self, state: str, data: dict[str, Any]):
(self.root / state / f"{data['id']}.json").write_text(
json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
)
def _open_state(self, state: str, *, create: bool = False) -> tuple[int, int] | None:
if state != "audit":
self._state(state)
if not self.root.exists():
if not create:
return None
self._ensure_root()
contained_path(self.root, state)
root_fd = os.open(self.root, self._DIR_FLAGS)
try:
try:
state_fd = os.open(state, self._DIR_FLAGS, dir_fd=root_fd)
except FileNotFoundError:
if not create:
os.close(root_fd)
return None
os.mkdir(state, mode=0o750, dir_fd=root_fd)
state_fd = os.open(state, self._DIR_FLAGS, dir_fd=root_fd)
return root_fd, state_fd
except Exception:
try:
os.close(root_fd)
except OSError:
pass
raise
def _read(self, path: Path):
return json.loads(path.read_text(encoding="utf-8"))
def _names(self, state: str) -> set[str]:
if os.name != "posix" or not hasattr(os, "O_NOFOLLOW"):
if not self.root.exists():
return set()
raise OSError("event storage requires POSIX no-follow support")
opened = self._open_state(state)
if opened is None:
return set()
root_fd, state_fd = opened
try:
return set(os.listdir(state_fd))
finally:
os.close(state_fd); os.close(root_fd)
def _read(self, state: str, event_id: str) -> dict[str, Any]:
safe_segment(event_id)
opened = self._open_state(state)
if opened is None:
raise FileNotFoundError(event_id)
root_fd, state_fd = opened
try:
fd = os.open(f"{event_id}.json", os.O_RDONLY | os.O_NOFOLLOW, dir_fd=state_fd)
info = os.fstat(fd)
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
os.close(fd)
raise ValueError("unsafe event file")
with os.fdopen(fd, "r", encoding="utf-8") as stream:
value = json.load(stream)
if (not isinstance(value, dict) or value.get("id") != event_id
or (state != "audit" and value.get("status") != state)):
raise ValueError("invalid event record")
return value
finally:
os.close(state_fd); os.close(root_fd)
def _unlink(self, state: str, event_id: str) -> None:
opened = self._open_state(state)
if opened is None:
return
root_fd, state_fd = opened
try:
info = os.stat(f"{event_id}.json", dir_fd=state_fd, follow_symlinks=False)
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
raise ValueError("unsafe event file")
os.unlink(f"{event_id}.json", dir_fd=state_fd)
os.fsync(state_fd)
finally:
os.close(state_fd); os.close(root_fd)
def _audit(self, action: str, event: dict[str, Any], extra: dict[str, Any] | None = None) -> None:
safe_segment(action)
audit = {"id": f"{safe_segment(event['id'])}-{action}", "action": action,
"event": event, "extra": extra or {}, "time": utc_now()}
self._write("audit", audit)
def _audit(self, action: str, event: dict[str, Any], extra: dict[str, Any] | None = None):
data = {"action": action, "event": event, "extra": extra or {}, "time": utc_now()}
(self.root / "audit" / f"{event['id']}-{action}.json").write_text(
json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
)
__all__ = ["AgentEvent", "AgentEventBus"]
+20 -160
View File
@@ -1,178 +1,36 @@
"""Authenticated API for agent orchestration observability."""
"""API for agent orchestration observability."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from typing import Any
try:
from fastapi import APIRouter, HTTPException
except Exception: # pragma: no cover
APIRouter = HTTPException = None
from fastapi import APIRouter
except Exception:
APIRouter = None
from .agent_observability import AgentExecutionRecord
from .agent_observability_dashboard import AgentObservabilityDashboard
from .agent_observability_store import AgentObservabilityStore
store: AgentObservabilityStore | None = None
dashboard = AgentObservabilityDashboard()
_ROLE_WHITELIST = frozenset({"user", "group_admin", "owner"})
_RECORD_FIELDS = frozenset(AgentExecutionRecord.__dataclass_fields__)
_authenticator: Callable[[], Mapping[str, Any] | None] | None = None
store = AgentObservabilityStore()
dashboard = AgentObservabilityDashboard(store.load())
class ApiSecurityError(PermissionError):
def __init__(self, status_code: int, code: str):
super().__init__(code)
self.status_code, self.code = status_code, code
def record_agent_execution(payload: dict[str, Any]):
record = AgentExecutionRecord(**payload)
return {"status": "ok", "record": store.append(record)}
def _storage() -> AgentObservabilityStore:
global store
if store is None:
store = AgentObservabilityStore()
return store
def set_authenticator(fn: Callable[[], Mapping[str, Any] | None] | None) -> None:
"""Install the host authenticator; its result is fetched for every call."""
if fn is not None and not callable(fn):
raise TypeError("authenticator must be callable or None")
global _authenticator
_authenticator = fn
def _group_id(principal: Mapping[str, Any]) -> str | None:
value = principal.get("group_id")
return value.strip() if isinstance(value, str) and value.strip() else None
def authenticated_principal() -> dict[str, Any]:
"""Return a request-scoped copy of the server principal, or fail closed."""
if _authenticator is None:
raise ApiSecurityError(401, "AUTHENTICATION_REQUIRED")
try:
raw = _authenticator()
except ApiSecurityError:
raise
except Exception as exc:
raise ApiSecurityError(401, "AUTHENTICATION_FAILED") from exc
if not isinstance(raw, Mapping):
raise ApiSecurityError(401, "AUTHENTICATION_REQUIRED")
principal = dict(raw)
user_id, role = principal.get("user_id"), principal.get("role")
if not isinstance(user_id, str) or not user_id.strip() or role not in _ROLE_WHITELIST:
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_PRINCIPAL")
principal["user_id"] = user_id.strip()
if principal.get("group_id") is not None and _group_id(principal) is None:
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_PRINCIPAL")
if role == "group_admin" and not _group_id(principal):
raise ApiSecurityError(403, "GROUP_SCOPE_REQUIRED")
return principal
def _scope_set(principal: Mapping[str, Any], name: str) -> frozenset[str]:
raw = principal.get(name, ())
if not isinstance(raw, (list, tuple, set, frozenset)) or any(
not isinstance(item, str) or not item for item in raw
):
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_SCOPE")
return frozenset(raw)
def _owner_user(principal: Mapping[str, Any], requested: str | None) -> str:
own, target = str(principal["user_id"]), requested or str(principal["user_id"])
if target != own and target not in _scope_set(principal, "allowed_user_ids"):
raise ApiSecurityError(403, "OWNER_USER_SCOPE_DENIED")
return target
def _owner_group(principal: Mapping[str, Any], requested: str | None) -> str | None:
if requested is None:
return None
if requested != _group_id(principal) and requested not in _scope_set(principal, "allowed_group_ids"):
raise ApiSecurityError(403, "OWNER_GROUP_SCOPE_DENIED")
return requested
def _summary(records: list[dict[str, Any]]) -> dict[str, Any]:
count = len(records)
hermes = sum(1 for row in records if row.get("used_hermes"))
def get_agent_observability(user_id: str | None = None, group_id: str | None = None, role: str = "user"):
records = store.query(user_id, group_id, role)
return {
"total_executions": count,
"openclaw_independent_loops": count - hermes,
"hermes_orchestrated_loops": hermes,
"average_duration_ms": (
sum(int(row.get("duration_ms") or 0) for row in records) / count if count else 0
),
"token_usage": {
"input": sum(int(row.get("input_tokens") or 0) for row in records),
"output": sum(int(row.get("output_tokens") or 0) for row in records),
"total": sum(int(row.get("total_tokens") or 0) for row in records),
},
"status": "ok",
"records": records,
"summary": store.load().summary(user_id if role != "owner" else None),
}
def record_agent_execution(payload: Mapping[str, Any]):
principal = authenticated_principal()
if set(payload) - _RECORD_FIELDS:
raise ApiSecurityError(403, "EXECUTION_FIELDS_FORBIDDEN")
normalized, role = dict(payload), principal["role"]
if role == "owner":
requested_user = payload.get("user_id")
if requested_user is not None and not isinstance(requested_user, str):
raise ApiSecurityError(403, "INVALID_EXECUTION_SCOPE")
target_user = _owner_user(principal, requested_user)
requested_scope = payload.get("visible_scope")
if requested_scope is not None and not isinstance(requested_scope, str):
raise ApiSecurityError(403, "INVALID_EXECUTION_SCOPE")
allowed_group = _owner_group(principal, requested_scope) if requested_scope and requested_scope != target_user else None
normalized["user_id"], normalized["visible_scope"] = target_user, allowed_group or target_user
else:
normalized["user_id"] = principal["user_id"]
normalized["visible_scope"] = _group_id(principal) or principal["user_id"]
try:
record = AgentExecutionRecord(**normalized)
except (TypeError, ValueError) as exc:
raise ApiSecurityError(403, "INVALID_EXECUTION_RECORD") from exc
return {"status": "ok", "record": _storage().append(record)}
def get_agent_observability(
user_id: str | None = None, group_id: str | None = None,
role: str = "user",
):
del role # wire compatibility only; server principal is authoritative
principal, pr_role = authenticated_principal(), None
pr_role = principal["role"]
if pr_role == "user":
if user_id not in (None, principal["user_id"]) or group_id is not None:
raise ApiSecurityError(403, "TENANT_SCOPE_DENIED")
q_user, q_group, q_role = principal["user_id"], None, "user"
elif pr_role == "group_admin":
own_group = _group_id(principal)
if group_id not in (None, own_group) or user_id not in (None, principal["user_id"]):
raise ApiSecurityError(403, "TENANT_SCOPE_DENIED")
q_user, q_group, q_role = principal["user_id"], own_group, "group_admin"
elif user_id is None and group_id is None:
q_user, q_group, q_role = principal["user_id"], None, "user"
elif group_id is not None:
q_user = _owner_user(principal, user_id) if user_id is not None else principal["user_id"]
q_group, q_role = _owner_group(principal, group_id), "group_admin"
else:
q_user, q_group, q_role = _owner_user(principal, user_id), None, "user"
records = _storage().query(q_user, q_group, q_role)
return {"status": "ok", "records": records, "summary": _summary(records)}
def _http(call: Callable[[], Any]) -> Any:
try:
return call()
except ApiSecurityError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=exc.status_code, detail=exc.code) from exc
def build_router():
if APIRouter is None:
return None
@@ -180,13 +38,15 @@ def build_router():
@router.post("/execution", operation_id="recordAgentExecution")
def record(payload: dict[str, Any]):
return _http(lambda: record_agent_execution(payload))
return record_agent_execution(payload)
@router.get("/overview", operation_id="getAgentObservabilityOverview")
def overview(user_id: str | None = None, group_id: str | None = None, role: str = "user"):
return _http(lambda: get_agent_observability(user_id, group_id, role))
return get_agent_observability(user_id, group_id, role)
return router
router = build_router()
__all__ = ["ApiSecurityError", "authenticated_principal", "set_authenticator", "router", "record_agent_execution", "get_agent_observability"]
__all__ = ["router", "record_agent_execution", "get_agent_observability"]
@@ -8,15 +8,11 @@ metadata are stored for Web UI statistics and audit.
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any
from .agent_observability import AgentExecutionRecord, AgentObservability
# Constrain execution IDs (used as filename) to a single safe path segment.
_SAFE_EXECUTION_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
class AgentObservabilityStore:
def __init__(self, root: str = "runtime/agent-observability"):
@@ -25,17 +21,9 @@ class AgentObservabilityStore:
self.records_dir.mkdir(parents=True, exist_ok=True)
def append(self, record: AgentExecutionRecord) -> dict[str, Any]:
execution_id = str(record.execution_id or "")
if not _SAFE_EXECUTION_ID.fullmatch(execution_id) or ".." in execution_id:
raise ValueError(f"invalid execution_id: {execution_id!r}")
base = self.records_dir.resolve()
target = (self.records_dir / f"{execution_id}.json").resolve()
# The whitelist forbids separators/traversal, so the resolved target
# must sit directly inside the records dir; verify defensively.
if target.parent != base:
raise ValueError(f"invalid execution_id: {execution_id!r}")
path = self.records_dir / f"{record.execution_id}.json"
data = record.to_dict()
target.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
return data
def load(self) -> AgentObservability:
@@ -1,940 +0,0 @@
#!/usr/bin/env python3
"""Durable online task protocol for external AIOS agents.
The store is deliberately independent from Git/Gitea mutation. It provides a
small durable state machine for publishing work, atomic claim/lease ownership,
heartbeat, idempotent result submission, explicit server-side verification,
lease recovery, and audit events.
No plaintext agent credential or task lease token is persisted.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import hashlib
import hmac
import json
from pathlib import Path
import secrets
import sqlite3
from typing import Any, Callable, Iterable, Mapping
import uuid
from aios_capability_contract import (
CAPABILITY_VOCABULARY_DIGEST,
has_capabilities,
normalize_required_capabilities,
)
TERMINAL_STATES = frozenset({"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"})
ACTIVE_LEASE_STATES = frozenset({"CLAIMED", "RUNNING"})
TASK_STATES = frozenset({
"QUEUED", "CLAIMED", "RUNNING", "VERIFYING",
"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED",
})
VERIFY_DECISIONS = frozenset({"SUCCEEDED", "NEEDS_REWORK", "FAILED"})
class TaskProtocolError(RuntimeError):
def __init__(self, code: str, http_status: int = 409) -> None:
super().__init__(code)
self.code = code
self.http_status = http_status
@dataclass(frozen=True, slots=True)
class Principal:
kind: str
identifier: str
def utcnow() -> datetime:
return datetime.now(timezone.utc)
def iso(value: datetime) -> str:
return value.astimezone(timezone.utc).isoformat()
def parse_time(value: str | None) -> datetime | None:
if not value:
return None
parsed = datetime.fromisoformat(value)
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed.astimezone(timezone.utc)
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def _string_list(value: Iterable[object] | None, field: str, *, max_items: int = 128) -> list[str]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, Iterable):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result: list[str] = []
for item in value:
text = str(item).strip()
if not text or len(text) > 160:
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result.append(text)
if len(result) > max_items:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 400)
return sorted(set(result))
def _json_object(value: object | None, field: str, *, max_bytes: int = 128 * 1024) -> dict[str, Any]:
if value is None:
return {}
if not isinstance(value, Mapping):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result = dict(value)
if len(canonical_json(result).encode("utf-8")) > max_bytes:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 413)
return result
def _criteria(value: object | None) -> list[Any]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, list):
raise TaskProtocolError("SUCCESS_CRITERIA_INVALID", 400)
if len(value) > 128:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
if len(canonical_json(value).encode("utf-8")) > 128 * 1024:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
return list(value)
class TaskStore:
"""SQLite-backed durable task store.
SQLite BEGIN IMMEDIATE serializes claims so two agents cannot successfully
claim the same queued task. WAL mode allows concurrent readers.
"""
def __init__(
self,
path: str | Path,
*,
now: Callable[[], datetime] = utcnow,
token_pepper: str = "",
) -> None:
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.now = now
self.token_pepper = token_pepper
self._initialize()
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.path, timeout=10, isolation_level=None)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA journal_mode=WAL")
connection.execute("PRAGMA synchronous=FULL")
connection.execute("PRAGMA busy_timeout=10000")
return connection
def _initialize(self) -> None:
with self._connect() as connection:
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY,
agent_type TEXT NOT NULL,
agent_version TEXT NOT NULL,
capabilities_json TEXT NOT NULL,
capability_vocabulary_digest TEXT,
metadata_json TEXT NOT NULL,
token_hash TEXT NOT NULL UNIQUE,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY,
goal TEXT NOT NULL,
instructions TEXT NOT NULL,
success_criteria_json TEXT NOT NULL,
required_capabilities_json TEXT NOT NULL,
capability_vocabulary_digest TEXT,
constraints_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
deadline TEXT,
priority INTEGER NOT NULL,
max_attempts INTEGER NOT NULL,
attempt INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL,
claimed_by TEXT,
lease_token_hash TEXT,
lease_expires_at TEXT,
result_json TEXT,
result_digest TEXT,
result_idempotency_key TEXT,
verification_json TEXT,
revision INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(claimed_by) REFERENCES agents(agent_id)
);
CREATE TABLE IF NOT EXISTS idempotency (
scope TEXT NOT NULL,
key TEXT NOT NULL,
payload_digest TEXT NOT NULL,
response_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY(scope, key)
);
CREATE TABLE IF NOT EXISTS events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT,
agent_id TEXT,
event_type TEXT NOT NULL,
payload_json TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_tasks_queue
ON tasks(status, priority DESC, created_at);
CREATE INDEX IF NOT EXISTS idx_tasks_lease
ON tasks(status, lease_expires_at);
CREATE INDEX IF NOT EXISTS idx_events_task
ON events(task_id, event_id);
"""
)
self._ensure_additive_column(
connection, "agents", "capability_vocabulary_digest", "TEXT"
)
self._ensure_additive_column(
connection, "tasks", "capability_vocabulary_digest", "TEXT"
)
@staticmethod
def _ensure_additive_column(
connection: sqlite3.Connection, table: str, column: str, definition: str
) -> None:
"""Add C6 handshake storage without rewriting historical task rows."""
columns = {
str(row["name"])
for row in connection.execute(f"PRAGMA table_info({table})").fetchall()
}
if column not in columns:
connection.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}")
@staticmethod
def _normalize_capability_vocabulary_digest(value: object | None) -> str | None:
"""Validate an opt-in formal vocabulary handshake.
An omitted digest is the explicit C5-compatible legacy mode. A
supplied digest has no downgrade path: it must name this exact formal
vocabulary before any durable state is changed.
"""
if value is None:
return None
if not isinstance(value, str) or value.strip().lower() != CAPABILITY_VOCABULARY_DIGEST:
raise TaskProtocolError("CAPABILITY_VOCABULARY_MISMATCH", 409)
return CAPABILITY_VOCABULARY_DIGEST
@staticmethod
def _negotiated_capability_vocabulary_matches(
*,
agent_digest: str | None,
task_digest: str | None,
requested_digest: str | None,
) -> bool:
"""Match only an explicitly negotiated formal task vocabulary.
Historical agents and tasks carry ``NULL`` and keep their C5 behavior.
A task admitted with a digest is never offered to or claimed by an
agent that did not register the same current vocabulary.
"""
if task_digest is None:
return True
return (
task_digest == CAPABILITY_VOCABULARY_DIGEST
and agent_digest == task_digest
and (requested_digest is None or requested_digest == task_digest)
)
def _token_hash(self, token: str) -> str:
return hashlib.sha256((self.token_pepper + "\0" + token).encode("utf-8")).hexdigest()
@staticmethod
def _row_task(row: sqlite3.Row, *, include_result: bool = True) -> dict[str, Any]:
value: dict[str, Any] = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"capability_vocabulary_digest": row["capability_vocabulary_digest"],
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"deadline": row["deadline"],
"priority": row["priority"],
"max_attempts": row["max_attempts"],
"attempt": row["attempt"],
"status": row["status"],
"claimed_by": row["claimed_by"],
"lease_expires_at": row["lease_expires_at"],
"revision": row["revision"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
}
if include_result:
value["result"] = json.loads(row["result_json"]) if row["result_json"] else None
value["verification"] = (
json.loads(row["verification_json"]) if row["verification_json"] else None
)
return value
def _event(
self,
connection: sqlite3.Connection,
event_type: str,
*,
task_id: str | None = None,
agent_id: str | None = None,
payload: Mapping[str, Any] | None = None,
) -> None:
connection.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at)"
" VALUES(?,?,?,?,?)",
(task_id, agent_id, event_type, canonical_json(dict(payload or {})), iso(self.now())),
)
def register_agent(
self,
*,
agent_id: str,
agent_type: str,
agent_version: str,
capabilities: Iterable[object],
metadata: Mapping[str, Any] | None = None,
capability_vocabulary_digest: str | None = None,
) -> dict[str, Any]:
agent_id = agent_id.strip()
agent_type = agent_type.strip()
agent_version = agent_version.strip()
if not agent_id or len(agent_id) > 160 or not agent_type or not agent_version:
raise TaskProtocolError("AGENT_IDENTITY_INVALID", 400)
caps = _string_list(capabilities, "capabilities")
meta = _json_object(metadata, "metadata")
vocabulary_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
token = "agt_" + secrets.token_urlsafe(32)
now = iso(self.now())
try:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
connection.execute(
"INSERT INTO agents(agent_id,agent_type,agent_version,capabilities_json,"
"capability_vocabulary_digest,metadata_json,token_hash,status,created_at,last_seen_at)"
" VALUES(?,?,?,?,?,?,?,?,?,?)",
(
agent_id,
agent_type,
agent_version,
canonical_json(caps),
vocabulary_digest,
canonical_json(meta),
self._token_hash(token),
"ACTIVE",
now,
now,
),
)
self._event(
connection,
"AGENT_REGISTERED",
agent_id=agent_id,
payload={"agent_type": agent_type, "agent_version": agent_version},
)
connection.commit()
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("AGENT_ALREADY_EXISTS", 409) from exc
return {
"agent_id": agent_id,
"agent_type": agent_type,
"agent_version": agent_version,
"capabilities": caps,
"capability_vocabulary_digest": vocabulary_digest,
"agent_token": token,
"credential_returned_once": True,
}
def authenticate_agent(self, token: str) -> dict[str, Any]:
if not token:
raise TaskProtocolError("UNAUTHORIZED", 401)
token_hash = self._token_hash(token)
with self._connect() as connection:
row = connection.execute(
"SELECT * FROM agents WHERE token_hash=? AND status='ACTIVE'",
(token_hash,),
).fetchone()
if row is None:
raise TaskProtocolError("UNAUTHORIZED", 401)
connection.execute(
"UPDATE agents SET last_seen_at=? WHERE agent_id=?",
(iso(self.now()), row["agent_id"]),
)
return {
"agent_id": row["agent_id"],
"agent_type": row["agent_type"],
"agent_version": row["agent_version"],
"capabilities": json.loads(row["capabilities_json"]),
"capability_vocabulary_digest": row["capability_vocabulary_digest"],
"metadata": json.loads(row["metadata_json"]),
}
def _idempotent_lookup(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
) -> dict[str, Any] | None:
row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(scope, key),
).fetchone()
if row is None:
return None
if not hmac.compare_digest(row["payload_digest"], payload_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
return json.loads(row["response_json"])
def _idempotent_store(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
response: Mapping[str, Any],
) -> None:
connection.execute(
"INSERT INTO idempotency(scope,key,payload_digest,response_json,created_at)"
" VALUES(?,?,?,?,?)",
(scope, key, payload_digest, canonical_json(dict(response)), iso(self.now())),
)
def create_task(
self,
*,
task_id: str | None,
goal: str,
instructions: str,
success_criteria: object | None,
required_capabilities: Iterable[object] | None = None,
capability_vocabulary_digest: str | None = None,
constraints: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
deadline: str | None = None,
priority: int = 0,
max_attempts: int = 3,
idempotency_key: str,
) -> dict[str, Any]:
task_id = (task_id or ("task-" + uuid.uuid4().hex)).strip()
goal = goal.strip()
instructions = instructions.strip()
idempotency_key = idempotency_key.strip()
if not task_id or len(task_id) > 180 or not goal or not instructions:
raise TaskProtocolError("TASK_BINDING_INVALID", 400)
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
if not isinstance(priority, int) or isinstance(priority, bool) or not -100 <= priority <= 100:
raise TaskProtocolError("PRIORITY_INVALID", 400)
if not isinstance(max_attempts, int) or isinstance(max_attempts, bool) or not 1 <= max_attempts <= 20:
raise TaskProtocolError("MAX_ATTEMPTS_INVALID", 400)
parsed_deadline = parse_time(deadline)
if parsed_deadline and parsed_deadline <= self.now():
raise TaskProtocolError("DEADLINE_INVALID", 400)
criteria = _criteria(success_criteria)
required = _string_list(required_capabilities, "required_capabilities")
normalized_required = normalize_required_capabilities(required)
if any(value.startswith("exact:") for value in normalized_required):
raise TaskProtocolError("TASK_CAPABILITY_UNKNOWN", 403)
vocabulary_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
constraints_value = _json_object(constraints, "constraints")
metadata_value = _json_object(metadata, "metadata")
payload = {
"task_id": task_id,
"goal": goal,
"instructions": instructions,
"success_criteria": criteria,
"required_capabilities": required,
"capability_vocabulary_digest": vocabulary_digest,
"constraints": constraints_value,
"metadata": metadata_value,
"deadline": iso(parsed_deadline) if parsed_deadline else None,
"priority": priority,
"max_attempts": max_attempts,
}
payload_digest = digest_json(payload)
now = iso(self.now())
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
replay = self._idempotent_lookup(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
)
if replay is not None:
connection.commit()
return {**replay, "idempotent": True}
try:
connection.execute(
"INSERT INTO tasks(task_id,goal,instructions,success_criteria_json,"
"required_capabilities_json,capability_vocabulary_digest,constraints_json,metadata_json,deadline,priority,"
"max_attempts,attempt,status,revision,created_at,updated_at)"
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
task_id,
goal,
instructions,
canonical_json(criteria),
canonical_json(required),
vocabulary_digest,
canonical_json(constraints_value),
canonical_json(metadata_value),
payload["deadline"],
priority,
max_attempts,
0,
"QUEUED",
1,
now,
now,
),
)
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("TASK_ID_CONFLICT", 409) from exc
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
result = self._row_task(row)
self._idempotent_store(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
response=result,
)
self._event(connection, "TASK_CREATED", task_id=task_id, payload={"priority": priority})
connection.commit()
return result
def _expire_locked(self, connection: sqlite3.Connection) -> None:
now_dt = self.now()
now = iso(now_dt)
rows = connection.execute(
"SELECT * FROM tasks WHERE status IN ('QUEUED','CLAIMED','RUNNING')"
).fetchall()
for row in rows:
deadline = parse_time(row["deadline"])
if deadline is not None and deadline <= now_dt:
connection.execute(
"UPDATE tasks SET status='EXPIRED',claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(now, row["task_id"]),
)
self._event(connection, "TASK_EXPIRED", task_id=row["task_id"])
continue
if row["status"] in ACTIVE_LEASE_STATES:
lease_expires = parse_time(row["lease_expires_at"])
if lease_expires is not None and lease_expires <= now_dt:
if row["attempt"] >= row["max_attempts"]:
next_state = "FAILED"
else:
next_state = "QUEUED"
if next_state == "FAILED":
connection.execute(
"UPDATE tasks SET status=?,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
self._event(
connection,
"LEASE_EXPIRED",
task_id=row["task_id"],
agent_id=row["claimed_by"],
payload={"next_state": next_state, "attempt": row["attempt"]},
)
def expire_leases(self) -> int:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
before = connection.total_changes
self._expire_locked(connection)
changed = connection.total_changes - before
connection.commit()
return changed
def poll_tasks(
self,
*,
agent_id: str,
limit: int = 20,
capability_vocabulary_digest: str | None = None,
) -> list[dict[str, Any]]:
if not 1 <= int(limit) <= 100:
raise TaskProtocolError("LIMIT_INVALID", 400)
requested_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
agent = connection.execute(
"SELECT capabilities_json,capability_vocabulary_digest FROM agents "
"WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if agent is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
self._expire_locked(connection)
capabilities = json.loads(agent["capabilities_json"])
rows = connection.execute(
"SELECT * FROM tasks WHERE status='QUEUED' ORDER BY priority DESC,created_at ASC"
).fetchall()
result = []
for row in rows:
required = json.loads(row["required_capabilities_json"])
if (
self._negotiated_capability_vocabulary_matches(
agent_digest=agent["capability_vocabulary_digest"],
task_digest=row["capability_vocabulary_digest"],
requested_digest=requested_digest,
)
and has_capabilities(capabilities, required)
):
result.append(self._row_task(row, include_result=False))
if len(result) >= int(limit):
break
connection.commit()
return result
def _agent_capabilities(self, connection: sqlite3.Connection, agent_id: str) -> set[str]:
row = connection.execute(
"SELECT capabilities_json FROM agents WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if row is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
return set(json.loads(row["capabilities_json"]))
def claim_task(
self,
*,
task_id: str,
agent_id: str,
lease_seconds: int = 300,
capability_vocabulary_digest: str | None = None,
) -> dict[str, Any]:
if not isinstance(lease_seconds, int) or isinstance(lease_seconds, bool) or not 30 <= lease_seconds <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
requested_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
lease_token = "lease_" + secrets.token_urlsafe(32)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
agent = connection.execute(
"SELECT capabilities_json,capability_vocabulary_digest FROM agents "
"WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if agent is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
if not self._negotiated_capability_vocabulary_matches(
agent_digest=agent["capability_vocabulary_digest"],
task_digest=row["capability_vocabulary_digest"],
requested_digest=requested_digest,
):
raise TaskProtocolError("CAPABILITY_VOCABULARY_MISMATCH", 409)
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row["status"] != "QUEUED":
raise TaskProtocolError("TASK_NOT_CLAIMABLE", 409)
capabilities = set(json.loads(agent["capabilities_json"]))
required = json.loads(row["required_capabilities_json"])
if not has_capabilities(capabilities, required):
raise TaskProtocolError("AGENT_CAPABILITY_MISMATCH", 403)
attempt = int(row["attempt"]) + 1
if attempt > int(row["max_attempts"]):
raise TaskProtocolError("TASK_ATTEMPTS_EXHAUSTED", 409)
expires = self.now() + timedelta(seconds=lease_seconds)
now = iso(self.now())
updated = connection.execute(
"UPDATE tasks SET status='CLAIMED',claimed_by=?,lease_token_hash=?,"
"lease_expires_at=?,attempt=?,revision=revision+1,updated_at=? "
"WHERE task_id=? AND status='QUEUED'",
(
agent_id,
self._token_hash(lease_token),
iso(expires),
attempt,
now,
task_id,
),
)
if updated.rowcount != 1:
raise TaskProtocolError("TASK_CLAIM_CONFLICT", 409)
self._event(
connection,
"TASK_CLAIMED",
task_id=task_id,
agent_id=agent_id,
payload={"attempt": attempt, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return {
**self._row_task(row, include_result=False),
"lease_token": lease_token,
"lease_token_returned_once": True,
}
def _validate_lease(
self,
connection: sqlite3.Connection,
*,
task_id: str,
agent_id: str,
lease_token: str,
allowed_states: frozenset[str] = ACTIVE_LEASE_STATES,
) -> sqlite3.Row:
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] not in allowed_states:
raise TaskProtocolError("TASK_LEASE_NOT_ACTIVE", 409)
if row["claimed_by"] != agent_id:
raise TaskProtocolError("TASK_LEASE_OWNER_MISMATCH", 403)
if not row["lease_token_hash"] or not hmac.compare_digest(
row["lease_token_hash"], self._token_hash(lease_token)
):
raise TaskProtocolError("TASK_LEASE_TOKEN_INVALID", 403)
expires = parse_time(row["lease_expires_at"])
if expires is None or expires <= self.now():
raise TaskProtocolError("TASK_LEASE_EXPIRED", 409)
return row
def heartbeat(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
progress: Mapping[str, Any] | None = None,
lease_seconds: int = 300,
) -> dict[str, Any]:
if not 30 <= int(lease_seconds) <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
progress_value = _json_object(progress, "progress", max_bytes=64 * 1024)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
expires = self.now() + timedelta(seconds=int(lease_seconds))
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='RUNNING',lease_expires_at=?,revision=revision+1,"
"updated_at=? WHERE task_id=?",
(iso(expires), now, task_id),
)
self._event(
connection,
"TASK_HEARTBEAT",
task_id=task_id,
agent_id=agent_id,
payload={"progress": progress_value, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row, include_result=False)
def submit_result(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
idempotency_key: str,
result: Mapping[str, Any],
) -> dict[str, Any]:
idempotency_key = idempotency_key.strip()
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
result_value = _json_object(result, "result", max_bytes=1024 * 1024)
result_digest = digest_json(result_value)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["result_idempotency_key"] == idempotency_key:
if not row["result_digest"] or not hmac.compare_digest(row["result_digest"], result_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
connection.commit()
return {**self._row_task(row), "idempotent": True}
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='VERIFYING',result_json=?,result_digest=?,"
"result_idempotency_key=?,lease_token_hash=NULL,lease_expires_at=NULL,"
"revision=revision+1,updated_at=? WHERE task_id=?",
(
canonical_json(result_value),
result_digest,
idempotency_key,
now,
task_id,
),
)
self._event(
connection,
"RESULT_SUBMITTED",
task_id=task_id,
agent_id=agent_id,
payload={"result_digest": result_digest, "idempotency_key": idempotency_key},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row)
def verify_task(
self,
*,
task_id: str,
decision: str,
verification: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
decision = decision.strip().upper()
if decision not in VERIFY_DECISIONS:
raise TaskProtocolError("VERIFICATION_DECISION_INVALID", 400)
verification_value = _json_object(verification, "verification")
verification_value = {"decision": decision, **verification_value}
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] != "VERIFYING":
raise TaskProtocolError("TASK_NOT_VERIFYING", 409)
if decision == "SUCCEEDED":
next_state = "SUCCEEDED"
elif decision == "FAILED":
next_state = "FAILED"
elif int(row["attempt"]) >= int(row["max_attempts"]):
next_state = "FAILED"
verification_value["reason"] = verification_value.get(
"reason", "MAX_ATTEMPTS_EXHAUSTED"
)
else:
next_state = "QUEUED"
now = iso(self.now())
if next_state in {"SUCCEEDED", "FAILED"}:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
self._event(
connection,
"TASK_VERIFIED",
task_id=task_id,
agent_id=row["claimed_by"],
payload={"decision": decision, "next_state": next_state},
)
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def cancel_task(self, *, task_id: str, reason: str = "") -> dict[str, Any]:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] in TERMINAL_STATES:
connection.commit()
return self._row_task(row)
connection.execute(
"UPDATE tasks SET status='CANCELLED',lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(iso(self.now()), task_id),
)
self._event(connection, "TASK_CANCELLED", task_id=task_id, payload={"reason": reason})
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def get_task(self, task_id: str) -> dict[str, Any]:
self.expire_leases()
with self._connect() as connection:
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
return self._row_task(row)
def get_events(self, task_id: str, *, limit: int = 200) -> list[dict[str, Any]]:
with self._connect() as connection:
rows = connection.execute(
"SELECT * FROM events WHERE task_id=? ORDER BY event_id DESC LIMIT ?",
(task_id, max(1, min(int(limit), 1000))),
).fetchall()
return [
{
"event_id": row["event_id"],
"task_id": row["task_id"],
"agent_id": row["agent_id"],
"event_type": row["event_type"],
"payload": json.loads(row["payload_json"]),
"created_at": row["created_at"],
}
for row in reversed(rows)
]
+7 -36
View File
@@ -6,7 +6,7 @@ The built-in host adapter is READ_ONLY. Mutating operations require separately
registered governed adapters with receiver fencing/readback, never arbitrary argv.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from dataclasses import dataclass
from contextlib import contextmanager
from datetime import datetime,timezone
import base64
@@ -18,7 +18,7 @@ import time
from pathlib import Path
from typing import Mapping
from task_plane.models import TaskPlan,WorkerRegistration,ProbeEvidence,ProbeOutcome,Budget,EvidenceEnvelope
from task_plane.models import TaskPlan,WorkerRegistration,ProbeEvidence,ProbeOutcome,Budget
from task_plane.errors import TaskPlaneHTTPError
from aios_gitops_job_controller import GitOpsJobController,JobStore,JobState,JobError
from .evidence import ReceiptSigner,canonical,digest
@@ -69,7 +69,6 @@ class BaselineService:
self.store.conn.execute('''CREATE TABLE IF NOT EXISTS baseline_worker_policy(
worker_id TEXT PRIMARY KEY,policy_json TEXT NOT NULL)''')
self.store.probe_attestation_verifier=self._verify_probe
self.store.evidence_attestation_verifier=self._verify_task_evidence
def _user(self,identity):
if not isinstance(identity,Identity) or identity.role not in {'user','owner'}:
@@ -380,39 +379,11 @@ class BaselineService:
def _close_task(self,task,claim,envelope):
if not self._receipt_valid(task,envelope): raise ServiceError('RESULT_ATTESTATION_INVALID',403)
with self.store._transaction():
current=self.store.get_task(task.task_id)
if current.status=='CANCELLED': raise ServiceError('TASK_CANCELLED')
if current.status=='CLAIMED': self.store.update_task_status(task.task_id,current.revision,'RUNNING')
evidence=EvidenceEnvelope.create(task_id=task.task_id,claim_id=claim.claim_id,
kind='diagnostic',worker_identity=claim.worker_identity,payload=envelope,
previous_evidence_hash=self.store._last_evidence_hash(task.task_id),
attestation_ref='baseline-machine:'+self.signer.key_id)
proof=canonical({'domain':'aios.baseline.task-evidence.v1',
'key_id':self.signer.key_id,'evidence':evidence.digest_payload()})
evidence=replace(evidence,signature=base64.b64encode(self.signer.private_key.sign(proof)).decode())
self.store.complete_claim_with_evidence_and_event(task.task_id,claim.claim_id,
'COMPLETED',envelope,[evidence])
def _verify_task_evidence(self,evidence):
"""Verify the TaskPlane wrapper and its separately signed operation receipt."""
try:
if not isinstance(evidence,EvidenceEnvelope) or evidence.kind!='diagnostic': return False
prefix='baseline-machine:'
if not evidence.attestation_ref or not evidence.attestation_ref.startswith(prefix): return False
key_id=evidence.attestation_ref[len(prefix):]
key=self.signer.trusted_public_keys.get(key_id)
if key is None or not evidence.signature: return False
proof=canonical({'domain':'aios.baseline.task-evidence.v1',
'key_id':key_id,'evidence':evidence.digest_payload()})
key.verify(base64.b64decode(evidence.signature,validate=True),proof)
claim=self.store.get_claim(evidence.claim_id)
task=self.store.get_task(evidence.task_id)
if claim.task_id!=task.task_id or claim.worker_identity!=evidence.worker_identity: return False
if claim.operation_digest!=task.task_plan.operation_digest(): return False
return self._receipt_valid(task,evidence.payload)
except Exception:
return False
current=self.store.get_task(task.task_id)
if current.status=='CANCELLED': raise ServiceError('TASK_CANCELLED')
if current.status=='CLAIMED': self.store.update_task_status(task.task_id,current.revision,'RUNNING')
self.store.complete_claim(task.task_id,claim.claim_id,'COMPLETED',envelope,
evidence_entries=[('diagnostic',envelope,claim.worker_identity)])
def cancel_task(self,identity,task_id):
self._user(identity)
+1 -27
View File
@@ -4,8 +4,6 @@ from __future__ import annotations
import hashlib
import json
import os
import re
from pathlib import Path
import stat
from typing import Any
@@ -15,29 +13,6 @@ class RecoveryEvidenceError(RuntimeError):
pass
# Relative evidence path: a safe relative path, never absolute, never with
# traversal segments, never with backslash separators.
_SAFE_EVIDENCE_REL = re.compile(r"^[A-Za-z0-9_./@-]+$")
def _contained_evidence(path: Path, base: Path) -> Path:
"""Resolve an evidence path and guarantee it stays under `base`."""
raw = str(path)
if (
os.path.isabs(raw)
or "\\" in raw
or raw.startswith("/")
or ".." in Path(raw).parts
or not _SAFE_EVIDENCE_REL.fullmatch(raw)
):
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_PATH_INVALID")
base_resolved = base.resolve()
target = (base_resolved / raw).resolve()
if base_resolved != target and not str(target).startswith(str(base_resolved) + os.sep):
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_PATH_ESCAPE")
return target
def _digest(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as stream:
@@ -106,11 +81,10 @@ def load_recovered_authorization(path: str | Path) -> dict[str, Any]:
evidence = receipt["evidence_files"]
if not isinstance(evidence, list) or not evidence:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_LIST_INVALID")
base = receipt_path.resolve().parent
for item in evidence:
if not isinstance(item, dict) or set(item) != {"path", "sha256"}:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_ENTRY_INVALID")
target = _contained_evidence(Path(item["path"]), base)
target = Path(item["path"])
if _digest(target) != item["sha256"]:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_DIGEST_MISMATCH")
return {
@@ -1,174 +0,0 @@
"""Canonical fail-closed capability semantics for formal AgentTask eligibility."""
from __future__ import annotations
from collections.abc import Iterable
import hashlib
import json
# These aliases are the verified C4 contract captured in
# ``qa/capability-contract.txt``. Unknown task names become exact requirements;
# unknown worker names are never granted.
_TASK_ALIASES: dict[str, frozenset[str]] = {
"release:candidate": frozenset({"release:candidate"}),
"repository_read": frozenset({"repo.read"}),
"git.read": frozenset({"repo.read"}),
"repo.read": frozenset({"repo.read"}),
"test_execution": frozenset({"test.execute"}),
"git.test": frozenset({"test.execute"}),
"test.execute": frozenset({"test.execute"}),
"governed_git_write": frozenset({"governed.git.write.branch", "governed.git.write.file"}),
"governed.git.write": frozenset({"governed.git.write.branch", "governed.git.write.file"}),
"governed.write.branch": frozenset({"governed.git.write.branch"}),
"governed.write.file": frozenset({"governed.git.write.file"}),
"shell": frozenset({"runtime.shell"}),
"git": frozenset({"runtime.git"}),
"python": frozenset({"runtime.python"}),
"code.read": frozenset({"legacy.code.read"}),
"code.review": frozenset({"legacy.code.review"}),
"code.write": frozenset({"legacy.code.write"}),
"code.write.patch": frozenset({"legacy.code.write.patch"}),
"test.python": frozenset({"legacy.test.python"}),
"test.shell.allowlisted": frozenset({"legacy.test.shell.allowlisted"}),
"git.commit": frozenset({"legacy.git.commit"}),
"git.diff.read": frozenset({"legacy.git.diff.read"}),
"git.force_push": frozenset({"legacy.git.force_push"}),
"deploy.production": frozenset({"legacy.deploy.production"}),
"secret.transform": frozenset({"legacy.secret.transform"}),
"secure_file_transformer": frozenset({"legacy.secure_file_transformer"}),
"runtime.shadow": frozenset({"legacy.runtime.shadow"}),
"review.security": frozenset({"legacy.review.security"}),
"review.api-contract": frozenset({"legacy.review.api-contract"}),
"gpu.train": frozenset({"legacy.gpu.train"}),
}
_WORKER_ALIASES = {
key: value for key, value in _TASK_ALIASES.items()
if key not in {"release:candidate", "governed_git_write", "governed.git.write"}
}
_WORKER_ALIASES.update({
"release:candidate": frozenset({"release:candidate"}),
"governed.git.write.branch": frozenset({"governed.git.write.branch"}),
"governed.git.write.file": frozenset({"governed.git.write.file"}),
})
_EXACT_LIFECYCLE_CAPABILITIES = frozenset(
{
"agent-task:control",
"governed.task.create",
"governed.write.files",
"production.plan.apply",
"production.promote",
"production.rollback",
"task.principal.issue",
"testing",
"verification",
"writer.lease",
}
)
for _capability in _EXACT_LIFECYCLE_CAPABILITIES:
_TASK_ALIASES[_capability] = frozenset({_capability})
_WORKER_ALIASES[_capability] = frozenset({_capability})
_UNKNOWN_PREFIX = "exact:"
CAPABILITY_VOCABULARY_SCHEMA = "aios.agent-task.capability-vocabulary.v1"
def capability_vocabulary_descriptor() -> dict[str, object]:
"""Return the immutable inputs to the formal capability vocabulary digest.
The generic Task Plane has deliberately different wildcard semantics. This
descriptor names only the formal AgentTask vocabulary used in this module.
"""
return {
"schema": CAPABILITY_VOCABULARY_SCHEMA,
"task_aliases": {
key: sorted(value) for key, value in sorted(_TASK_ALIASES.items())
},
"worker_aliases": {
key: sorted(value) for key, value in sorted(_WORKER_ALIASES.items())
},
"unknown_required_prefix": _UNKNOWN_PREFIX,
"unknown_required": "exact-and-deny",
"unknown_worker": "never-granted",
"wildcard": "formal-deny",
}
def capability_vocabulary_digest() -> str:
"""Return a deterministic digest for the formal capability vocabulary."""
encoded = json.dumps(
capability_vocabulary_descriptor(),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
# A constant lets registration, admission, poll, and claim compare one exact
# formal vocabulary without importing or interpreting the generic Task Plane.
CAPABILITY_VOCABULARY_DIGEST = capability_vocabulary_digest()
def _clean(value: object) -> str:
if not isinstance(value, str):
raise ValueError("capability must be a string")
text = value.strip().lower()
if not text:
raise ValueError("capability must not be empty")
return text
def normalize_required_capabilities(values: Iterable[object]) -> frozenset[str]:
result: set[str] = set()
for value in values:
text = _clean(value)
if "*" in text:
result.add(_UNKNOWN_PREFIX + text)
else:
result.update(_TASK_ALIASES.get(text, frozenset({_UNKNOWN_PREFIX + text})))
return frozenset(result)
def normalize_worker_capabilities(values: Iterable[object]) -> frozenset[str]:
result: set[str] = set()
for value in values:
text = _clean(value)
if "*" in text:
continue
mapped = _WORKER_ALIASES.get(text)
if mapped is not None:
result.update(mapped)
if {"governed.git.write.branch", "governed.git.write.file"}.issubset(result):
result.add("governed.git.write")
return frozenset(result)
def has_capabilities(worker_capabilities: Iterable[object], required_capabilities: Iterable[object]) -> bool:
required = normalize_required_capabilities(required_capabilities)
effective = normalize_worker_capabilities(worker_capabilities)
if any(value.startswith(_UNKNOWN_PREFIX) for value in required):
return False
return required.issubset(effective)
def required_capabilities_subset(granted_requirements: Iterable[object], required_requirements: Iterable[object]) -> bool:
granted = normalize_required_capabilities(granted_requirements)
required = normalize_required_capabilities(required_requirements)
if any(value.startswith(_UNKNOWN_PREFIX) for value in granted | required):
return False
return required.issubset(granted)
def normalized_capability_matrix(worker_capabilities: Iterable[object], required_capabilities: Iterable[object]) -> dict[str, object]:
worker_values = tuple(worker_capabilities)
required_values = tuple(required_capabilities)
worker = normalize_worker_capabilities(worker_values)
required = normalize_required_capabilities(required_values)
return {
"worker_effective": sorted(worker),
"task_required": sorted(required),
"eligible": has_capabilities(worker_values, required_values),
}
+11 -40
View File
@@ -110,11 +110,6 @@ SECRET_VAL_RE = re.compile(
r'(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,})'
)
# Control-plane file read: enforce auth, strict identifier, containment and a
# size cap on the command-detail read exposed by /admin/ai-evolution/api/data.
MAX_COMMAND_READ_BYTES = 1024 * 1024
_COMMAND_FILENAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*\.md$")
READ_ONLY_GET_PATHS = frozenset({
'/', '/health', '/api/health', '/api/openapi.yaml',
'/api/openapi-chatgpt-actions.yaml', '/api/schema', '/api', '/api/status',
@@ -158,10 +153,7 @@ def reject_non_read_http(handler, method: str, path: str) -> bool:
def now() -> str: return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
def uid(p='id') -> str: return f'{p}-{int(time.time()*1000)}-{uuid.uuid4().hex[:8]}'
from security_path_guards import safe_segment, relative_parts, write_text_no_follow
def safe(s: str, n=160) -> str:
return safe_segment(s, max_length=n)
def safe(s: str, n=160) -> str: return re.sub(r'[^A-Za-z0-9_./@-]+', '-', str(s or 'unknown')).strip('/-')[:n] or 'unknown'
def sh(args, check=False):
p = subprocess.run(['git'] + args, cwd=str(WS), text=True, capture_output=True)
if check and p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or 'git failed')
@@ -209,10 +201,7 @@ def relpath(path: str) -> str:
if not x or x.startswith('/') or '..' in Path(x).parts or Path(x).parts[0] == '.git': raise ValueError('invalid path')
return str(Path(x))
def branch_name(name: str | None, prefix='patch/chatgpt') -> str:
b = name or f'{prefix}-{uid("br")}'
if len(b) > 180: raise ValueError('branch name too long')
relative_parts(b)
if '..' in b or '@{' in b or b.endswith('.lock'): raise ValueError('invalid branch name')
b = safe(name or f'{prefix}-{uid("br")}', 180)
if b in ('main','master') or b.startswith('main/') or b.startswith('master/'): raise ValueError('direct main/master writes are forbidden')
return b
def _json_has_plain_secret(obj: Any) -> bool:
@@ -260,12 +249,8 @@ def token_records():
else:
out.append({'account':'legacy-token','role':'owner','scopes':['*'],'token_plain':line})
return out
from security_path_guards import require_isolated_dev_profile
def authenticate(token: str):
if os.getenv('AIOS_GITOPS_DEV_ALLOW','0') == '1':
require_isolated_dev_profile(HOST)
return {'account':'dev-owner','role':'owner','scopes':['*']}
if os.getenv('AIOS_GITOPS_DEV_ALLOW','0') == '1': return {'account':'dev-owner','role':'owner','scopes':['*']}
if not token: return None
digest=hashlib.sha256(token.encode()).hexdigest()
for r in token_records():
@@ -294,7 +279,7 @@ def checkout(branch, base='main', create=False):
return b
def write_file(path, content):
if content_has_secret(content): raise ValueError('secret_rejected: use secret_ref; plaintext API keys must not enter Git')
r=relpath(path); write_text_no_follow(WS, str(r).replace('\\','/'), content); return r
r=relpath(path); t=WS/r; t.parent.mkdir(parents=True, exist_ok=True); t.write_text(content, encoding='utf-8'); return r
def write_json(path, data): return write_file(path, json.dumps(data, ensure_ascii=False, indent=2) + '\n')
def sync_branch(branch: str) -> dict:
if not branch or branch in ('main','master') or branch.startswith('main/') or branch.startswith('master/'):
@@ -845,7 +830,7 @@ class H(BaseHTTPRequestHandler):
a=self.need('branch:create');
if not a: return
b=checkout(data.get('branch'), data.get('base','main'), True); meta={'type':'branch','branch':b,'base':data.get('base','main'),'created_by':a['account'],'created_at':now(),'direct_main_push':False,'owner_approval_required':True}
r=write_json(f'patches/{safe(b.replace("/","_"))}.branch.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create {b}'); audit(a['account'],'branch',b,meta); self.sendj({'status':'ok','branch':b,'commit':c})
r=write_json(f'patches/{safe(b).replace("/","_")}.branch.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create {b}'); audit(a['account'],'branch',b,meta); self.sendj({'status':'ok','branch':b,'commit':c})
def file(self,data):
a=self.need('file:write');
if not a: return
@@ -952,7 +937,7 @@ class H(BaseHTTPRequestHandler):
if not name or not base: return self.sendj({'error':'provider_name/base_url/model_name required'},400)
owner=safe(data.get('username') or a['account']); secret_ref=data.get('api_key_secret_ref') or data.get('secret_ref')
if data.get('api_key'):
sid=uid('llmkey'); write_text_no_follow(SECRETS, f'llm/{owner}/{sid}.secret', str(data['api_key'])); secret_ref=f'secret://llm/{owner}/{sid}'
d=SECRETS/'llm'/owner; d.mkdir(parents=True, exist_ok=True); sid=uid('llmkey'); p=d/f'{sid}.secret'; p.write_text(str(data['api_key']), encoding='utf-8'); os.chmod(p,0o600); secret_ref=f'secret://llm/{owner}/{sid}'
if not secret_ref: return self.sendj({'error':'api_key_secret_ref or api_key required'},400)
mid=data.get('model_id') or f'custom.{owner}.{hashlib.sha1((prov+base+name).encode()).hexdigest()[:10]}'; b=checkout(data.get('branch') or f'patch/model-{safe(mid)}','main',True)
doc=sanitize({k:v for k,v in data.items() if k!='api_key'}); doc.update({'model_id':mid,'provider_name':prov,'base_url':base,'model_name':name,'api_key_secret_ref':secret_ref,'created_by':a['account'],'created_at':now()})
@@ -1057,25 +1042,12 @@ return{vs,cmds,ar,cmd,cdet,tot,sok,sfail,runs,baseline,refreshMsg,loadCmd,selVer
commands.append(f.name)
command_detail=None
if cmd_filter:
actor = self.actor()
if not actor['ok']:
return self.sendj({'error':'unauthorized','hint':'Bearer token required'},401)
cmd = str(cmd_filter or '')
if not _COMMAND_FILENAME_RE.fullmatch(cmd) or '..' in cmd:
return self.sendj({'command':cmd_filter,'error':'unsafe_command'},400)
for d in cmd_dirs:
if not d.exists():
continue
base = d.resolve()
fp = (base / cmd).resolve()
# Strict containment: must resolve directly inside one of the
# allowed command directories.
if fp.parent != base or not fp.is_file():
continue
if fp.stat().st_size > MAX_COMMAND_READ_BYTES:
return self.sendj({'command':cmd_filter,'error':'command_too_large'},413)
command_detail={'command':cmd_filter,'content':fp.read_text(encoding='utf-8')}
break
if d.exists():
fp=d/cmd_filter
if fp.exists():
command_detail={'command':cmd_filter,'content':fp.read_text(encoding='utf-8')}
break
if not command_detail:
command_detail={'command':cmd_filter,'error':'not_found'}
return self.sendj(command_detail)
@@ -1090,7 +1062,6 @@ def api_spec():
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/repository/tree','GET /api/repository/search','GET /api/repository/diff-index','GET /api/repository/file','GET /api/patch','GET /api/model','GET /api/run','GET /getReleaseCandidate','GET /getApprovalEnvelope','GET /getReleaseSaga','GET /admin/ai-evolution'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl','POST /createReleaseCandidate','POST /submitOwnerApproval','POST /startDeployment'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True,'silent_release_execution_enabled':SILENT_RELEASE_EXECUTION_ENABLED,'silent_release_shadow_default':True}}
def main():
require_isolated_dev_profile(HOST)
validate_gitea_api_url(os.getenv('AIOS_GITOPS_GITEA_API_URL', ''))
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
+1 -36
View File
@@ -216,7 +216,6 @@ class ControlPlaneReleaseManifest:
repository_identity: str
authoritative_git_dir: Path
deployment_state: str
trusted_runtime_identity: Mapping[str, object] | None = None
def as_dict(self) -> dict[str, object]:
return {
@@ -230,35 +229,6 @@ class ControlPlaneReleaseManifest:
"deployment_state": self.deployment_state,
}
def runtime_identity(self) -> dict[str, object]:
"""Return only a host-injected, already trusted comparison result."""
identity = self.trusted_runtime_identity
required = {
"status", "declared_identity", "observed_identity", "manifest_digest",
"config_digest", "observed_at", "expires_at", "evidence_refs", "reason",
"mismatch_fields",
}
if (
not isinstance(identity, Mapping)
or identity.get("status") != "PASS"
or not required.issubset(identity)
or not isinstance(identity.get("declared_identity"), Mapping)
or not isinstance(identity.get("observed_identity"), Mapping)
or not isinstance(identity.get("evidence_refs"), list)
or not identity["evidence_refs"]
):
return {
"status": "UNKNOWN",
"declared_identity": None,
"observed_identity": None,
"observed_at": None,
"expires_at": None,
"evidence_refs": [],
"reason": "TRUSTED_OBSERVATION_UNAVAILABLE",
"mismatch_fields": [],
}
return dict(identity)
def validate(self, artifact: Path, *, observed_oid: str, approved_root: Path = Path("/opt/aios/gitops-control-plane")) -> dict[str, object]:
try:
runtime = self.runtime_path.resolve(strict=True)
@@ -284,12 +254,7 @@ class ControlPlaneReleaseManifest:
)
if not valid:
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
# Artifact and path validation proves only a local declaration. The
# composition root may inject the output of an already trusted runtime
# comparison, but this manifest must never promote its own checks to
# runtime-observation PASS.
identity = self.runtime_identity()
return {**self.as_dict(), "drift_status": identity["status"], "component_runtime_identity": identity}
return {**self.as_dict(), "drift_status": "PASS"}
class BridgeV2Adapter:
@@ -39,7 +39,6 @@ from aios_governed_task_bootstrap import (
)
from governed_production_release import build_executor_config
from aios_baseline.runtime_contract import evaluate_deployment
from component_runtime_identity import evaluate_component_runtime_identity
MAX_BODY = 2 * 1024 * 1024
TaskPrincipalVerifier = Callable[
@@ -105,10 +104,6 @@ class RuntimeConfig:
health_url: str = "http://127.0.0.1:18927/health"
release_plan_path: Path | None = None
repository_discovery_cursor_secret: str | bytes | None = None
component_manifest_path: Path | None = None
component_receipt_path: Path | None = None
component_id: str | None = None
component_identity_comparison: Callable[[Mapping[str, Any]], Mapping[str, Any] | None] | None = None
def _safe(value: str) -> str:
@@ -241,12 +236,6 @@ class GovernedRuntime:
blockers.append("LEGACY_GLOBAL_WRITES_MUST_REMAIN_DISABLED")
if not task_principal_writes_enabled:
blockers.append("TASK_PRINCIPAL_WRITES_DISABLED")
identity = evaluate_component_runtime_identity(
component_id=self.config.component_id,
manifest_path=self.config.component_manifest_path,
receipt_path=self.config.component_receipt_path,
trusted_comparison=self.config.component_identity_comparison,
)
return {
"status": "active",
"health": 200,
@@ -257,8 +246,7 @@ class GovernedRuntime:
"runtime_path": str(self.config.state_root),
"artifact_sha256": os.environ.get("AIOS_CONTROL_PLANE_ARTIFACT_SHA256"),
"deployment_state": "STAGING",
"drift_status": identity["status"],
"component_runtime_identity": identity,
"drift_status": "PASS",
"repository": self.config.repository,
"repository_identity": self.config.repository,
"authoritative_git_dir": str(self.config.authoritative_git_dir),
@@ -1344,17 +1332,6 @@ def main(
"/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
)),
repository_discovery_cursor_secret=discovery_cursor_secret,
component_manifest_path=(
Path(os.environ["AIOS_COMPONENT_MANIFEST_PATH"])
if os.environ.get("AIOS_COMPONENT_MANIFEST_PATH", "").strip()
else None
),
component_receipt_path=(
Path(os.environ["AIOS_COMPONENT_ARTIFACT_RECEIPT_PATH"])
if os.environ.get("AIOS_COMPONENT_ARTIFACT_RECEIPT_PATH", "").strip()
else None
),
component_id=os.environ.get("AIOS_COMPONENT_ID", "").strip() or None,
)
runtime = GovernedRuntime(config)
# Prefer a server-side TaskPrincipal provider recovered from the same
@@ -1365,39 +1342,21 @@ def main(
BridgeHandler.principal_verifier = task_principal_verifier
if task_admission_handlers is None:
admission_runtime = None
source_activation = os.environ.get("AIOS_SOURCE_PUBLICATION_ACTIVATION_FILE", "").strip()
try:
if os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_environment,
)
admission_runtime = build_task_admission_runtime_from_environment(
post_admission=runtime.project_admitted_task,
)
elif activation_raw and Path(activation_raw).is_file():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_activation_file(
Path(activation_raw),
post_admission=runtime.project_admitted_task,
)
except TaskAdmissionError as exc:
if not source_activation or str(exc) != "TASK_ADMISSION_EPOCH_EXPIRED":
raise
from source_publication_runtime import expired_admission_runtime
admission_runtime = expired_admission_runtime()
if source_activation:
from source_publication_runtime import (
AdmissionRuntimeRouter, build_source_runtime_from_activation_file,
if os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_environment,
)
if admission_runtime is None or task_principal_verifier is not None:
raise RuntimeError("SOURCE_ACTIVATION_ORDINARY_RUNTIME_REQUIRED")
source_runtime = build_source_runtime_from_activation_file(
Path(source_activation), post_admission=runtime.project_admitted_task,
ordinary_activation_file=Path(activation_raw),
admission_runtime = build_task_admission_runtime_from_environment(
post_admission=runtime.project_admitted_task,
)
elif activation_raw and Path(activation_raw).is_file():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_activation_file(
Path(activation_raw),
post_admission=runtime.project_admitted_task,
)
admission_runtime = AdmissionRuntimeRouter(admission_runtime, source_runtime).bundle()
if admission_runtime is not None:
task_admission_handlers = admission_runtime.handlers
if BridgeHandler.principal_verifier is None:
@@ -11,32 +11,10 @@ class RuntimeStatus:
read_plane: Mapping[str, bool]
write_plane: Mapping[str, bool]
release_plane: Mapping[str, bool]
required_facts: Mapping[str, frozenset[str]] | None = None
def _readiness_reason(self) -> str:
if self.required_facts is None:
return "REQUIRED_FACT_INVENTORY_MISSING"
expected_groups = {"READ_PLANE", "WRITE_PLANE", "RELEASE_PLANE"}
if set(self.required_facts) != expected_groups:
return "REQUIRED_FACT_INVENTORY_INVALID"
planes = {
"READ_PLANE": self.read_plane,
"WRITE_PLANE": self.write_plane,
"RELEASE_PLANE": self.release_plane,
}
for group, facts in self.required_facts.items():
if not isinstance(facts, frozenset) or not facts or any(not isinstance(name, str) or not name for name in facts):
return "REQUIRED_FACT_INVENTORY_INVALID"
plane = planes[group]
if set(plane) != set(facts):
return "REQUIRED_FACT_INVENTORY_MISMATCH"
if any(type(value) is not bool or value is not True for value in plane.values()):
return "REQUIRED_FACT_NOT_TRUE"
return "READY"
@property
def release_control_plane_ready(self) -> bool:
return self._readiness_reason() == "READY"
return all(self.read_plane.values()) and all(self.write_plane.values()) and all(self.release_plane.values())
def as_dict(self) -> dict[str, object]:
return {
@@ -44,5 +22,4 @@ class RuntimeStatus:
"WRITE_PLANE": dict(self.write_plane),
"RELEASE_PLANE": dict(self.release_plane),
"release_control_plane_ready": self.release_control_plane_ready,
"readiness_reason": self._readiness_reason(),
}
+113 -113
View File
@@ -24,25 +24,25 @@ def _safe_oid(value: str, field: str) -> str:
return value
def _safe_branch(value: str) -> str:
branch = str(value or "")
if (
not branch
or len(branch) > 240
or branch != branch.strip()
or branch.startswith(("/", "-", "."))
or branch.endswith(("/", ".", ".lock"))
or ".." in branch
or "//" in branch
or "@{" in branch
or "\\" in branch
or any(part in {"", ".", ".."} for part in branch.split("/"))
or any(ch not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._/-" for ch in branch)
):
raise GiteaTransportError("BRANCH_INVALID")
return branch
def _safe_branch(value: str) -> str:
branch = str(value or "")
if (
not branch
or len(branch) > 240
or branch != branch.strip()
or branch.startswith(("/", "-", "."))
or branch.endswith(("/", ".", ".lock"))
or ".." in branch
or "//" in branch
or "@{" in branch
or "\\" in branch
or any(part in {"", ".", ".."} for part in branch.split("/"))
or any(ch not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._/-" for ch in branch)
):
raise GiteaTransportError("BRANCH_INVALID")
return branch
@dataclass(slots=True)
class GiteaMachineTransport:
api_base: str
@@ -110,7 +110,7 @@ class GiteaMachineTransport:
return self.repository_info()
def read_branch(self, branch: str) -> str | None:
branch = _safe_branch(branch)
branch = _safe_branch(branch)
owner, name = self.repository_id.split("/", 1)
try:
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/branches/{quote(branch, safe='/')}")[1]
@@ -156,99 +156,99 @@ class GiteaMachineTransport:
finally:
askpass.unlink(missing_ok=True)
def fetch_branch_exact(self, branch: str, expected_oid: str) -> str:
"""Fetch one exact remote branch tip into a quarantine ref.
This never updates ``main`` or any caller-visible branch. The remote
ref is read before and after the fetch so a concurrent branch move
cannot be silently admitted.
"""
branch = _safe_branch(branch)
expected_oid = _safe_oid(expected_oid, "expected_oid")
remote_before = self.read_branch(branch)
if remote_before != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MISMATCH")
target_ref = f"refs/aios/release-sources/{expected_oid}"
current = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
if current.returncode == 0 and current.stdout.strip().lower() == expected_oid:
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
askpass, additions = self._askpass()
environment = os.environ.copy()
environment.update(additions)
try:
result = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"fetch",
"--no-tags",
"--no-write-fetch-head",
self.remote_url,
f"refs/heads/{branch}:{target_ref}",
],
text=True,
capture_output=True,
check=False,
env=environment,
timeout=self.timeout,
)
if result.returncode != 0:
raise GiteaTransportError("GITEA_SOURCE_FETCH_FAILED")
finally:
askpass.unlink(missing_ok=True)
local = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
local_oid = local.stdout.strip().lower()
if local.returncode != 0 or local_oid != expected_oid:
subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"update-ref",
"-d",
target_ref,
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
raise GiteaTransportError("GITEA_SOURCE_FETCH_MISMATCH")
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
def fetch_branch_exact(self, branch: str, expected_oid: str) -> str:
"""Fetch one exact remote branch tip into a quarantine ref.
This never updates ``main`` or any caller-visible branch. The remote
ref is read before and after the fetch so a concurrent branch move
cannot be silently admitted.
"""
branch = _safe_branch(branch)
expected_oid = _safe_oid(expected_oid, "expected_oid")
remote_before = self.read_branch(branch)
if remote_before != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MISMATCH")
target_ref = f"refs/aios/release-sources/{expected_oid}"
current = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
if current.returncode == 0 and current.stdout.strip().lower() == expected_oid:
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
askpass, additions = self._askpass()
environment = os.environ.copy()
environment.update(additions)
try:
result = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"fetch",
"--no-tags",
"--no-write-fetch-head",
self.remote_url,
f"refs/heads/{branch}:{target_ref}",
],
text=True,
capture_output=True,
check=False,
env=environment,
timeout=self.timeout,
)
if result.returncode != 0:
raise GiteaTransportError("GITEA_SOURCE_FETCH_FAILED")
finally:
askpass.unlink(missing_ok=True)
local = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
local_oid = local.stdout.strip().lower()
if local.returncode != 0 or local_oid != expected_oid:
subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"update-ref",
"-d",
target_ref,
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
raise GiteaTransportError("GITEA_SOURCE_FETCH_MISMATCH")
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str:
_safe_oid(expected_oid, "expected_oid")
_safe_oid(new_oid, "new_oid")
@@ -664,23 +664,6 @@ class GitOpsJobController:
raise JobError("LEASE_DURATION_EXCEEDED")
return _iso_utc(deadline)
@staticmethod
def internal_job_id_for_idempotency_key(client_idempotency_key: str) -> str:
"""Return the durable Job ID for a client idempotency key."""
if (
not isinstance(client_idempotency_key, str)
or not client_idempotency_key
or len(client_idempotency_key) > 128
or not all(
character.isalnum() or character in "._:-"
for character in client_idempotency_key
)
):
raise JobError("IDEMPOTENCY_KEY_INVALID")
return "job-" + sha256_json({
"client_idempotency_key": client_idempotency_key,
})[:32]
@staticmethod
def _binding(request: Mapping[str, Any]) -> dict[str, str]:
if request.get("schema") == "aios.operation-job.v1":
@@ -784,12 +767,7 @@ class GitOpsJobController:
key = sha256_json({"client_idempotency_key": client_idempotency_key})
else:
key = sha256_json(binding)
job_id = (
self.internal_job_id_for_idempotency_key(client_idempotency_key)
if binding.get("schema") != "aios.operation-job.v1"
and client_idempotency_key is not None
else "job-" + key[:32]
)
job_id = "job-" + key[:32]
durable_request = dict(binding)
if payload_ref is not None:
durable_request.update({"payload_ref": str(payload_ref), "payload_size": int(payload_size)})
+9 -71
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
from __future__ import annotations
import json, os, re, sqlite3, subprocess, time, uuid, hashlib, urllib.parse, html, ipaddress
import json, os, re, sqlite3, subprocess, time, uuid, hashlib, urllib.parse, html
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
@@ -16,25 +16,6 @@ HOST = os.getenv('AIOS_GITOPS_HOST', '127.0.0.1')
PORT = int(os.getenv('AIOS_GITOPS_PORT', '18890'))
ENTRY = os.getenv('AIOS_ENTRY', 'https://cnnetmkt.top')
DEV_ALLOW = os.getenv('AIOS_GITOPS_DEV_ALLOW', '0') == '1'
def _loopback(value):
try: return ipaddress.ip_address(value).is_loopback
except (ValueError, TypeError): return False
def validate_dev_profile():
if DEV_ALLOW and not (os.getenv('AIOS_RUNTIME_PROFILE') == 'isolated-staging' and _loopback(HOST)):
raise RuntimeError('DEV_ALLOW requires isolated-staging and loopback')
def runtime_selection(principal, data):
if not principal.get('token_ok'): raise PermissionError('authenticated principal required')
if any(key in data for key in ('username', 'user_id', 'role', 'tier')):
raise ValueError('runtime identity must come from authenticated principal')
username=principal['username']; role=principal['role']
if role not in _ROLE_WHITELIST or re.fullmatch(r'[A-Za-z0-9_@.-]{1,120}', username) is None or username in ('.','..'):
raise ValueError('invalid authenticated principal')
tier='ultra' if role=='owner' else 'regular'
return username, role, tier
BASE_MODELS = [
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display':'DeepSeek V4 Flash','tier':'regular','reasoning':1,'vision':0,'tools':1,'context':128000},
{'id':'base.qwen3.6-35b','provider':'one-api','model':'qwen3.6-35b','display':'Qwen 3.6 35B','tier':'pro','reasoning':1,'vision':0,'tools':1,'context':262144},
@@ -76,39 +57,10 @@ def seed_base_models():
if not one('SELECT * FROM gitops_versions WHERE version=?', ('V1R1C0',)):
exec_sql('INSERT INTO gitops_versions(version,tag,level,summary,status,created_by,created_at) VALUES(?,?,?,?,?,?,?)', ('V1R1C0','V1R1C0','R','MultiUser runtime selector + GitOps + custom LLM priority registry','ready','system',now()))
_ROLE_WHITELIST = {'owner', 'admin', 'user'}
def token_identity(token: str):
"""Resolve the server-authoritative identity bound to a token.
Format of each TOKEN_FILE line (one per line, ':'-separated):
<token> -> authenticated principal, role 'user'
<token>:<user>:<role> -> mapped principal; role restricted to owner/admin/user
Identity NEVER comes from a client-supplied header. In local dev mode the
DEV_ALLOW flag maps a fixed dev principal (must be explicitly enabled).
"""
if DEV_ALLOW:
validate_dev_profile()
return {'username': 'dev-owner', 'role': 'owner'}
if not token or not TOKEN_FILE.exists():
return None
for line in TOKEN_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = [x.strip() for x in line.split(':')]
if not parts or parts[0] != token:
continue
if len(parts) >= 3:
role = parts[2] if parts[2] in _ROLE_WHITELIST else 'user'
return {'username': safe_name(parts[1]), 'role': role}
return {'username': 'user-%s' % token[:8], 'role': 'user'}
return None
def valid_token(token: str) -> bool:
if DEV_ALLOW:
validate_dev_profile()
return True
return token_identity(token) is not None
if DEV_ALLOW: return True
if not token or not TOKEN_FILE.exists(): return False
return token.strip() in [x.strip() for x in TOKEN_FILE.read_text().splitlines() if x.strip()]
def git_cmd(args, check=False):
WORKSPACE.mkdir(parents=True, exist_ok=True); p = subprocess.run(['git'] + args, cwd=str(WORKSPACE), text=True, capture_output=True)
if check and p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or 'git command failed')
@@ -138,10 +90,8 @@ class Handler(BaseHTTPRequestHandler):
server_version = 'AIOSGitOpsModelAPI/V1R1C0'
def actor(self):
auth = self.headers.get('Authorization',''); token = auth.replace('Bearer ', '').strip() if auth.startswith('Bearer ') else self.headers.get('X-AIOS-Token','')
ident = token_identity(token)
if ident is None:
return {'username': 'anonymous', 'role': 'user', 'token_ok': False}
return {'username': ident['username'], 'role': ident['role'], 'token_ok': True}
ok = valid_token(token); user = self.headers.get('X-AIOS-User') or ('dev-owner' if DEV_ALLOW else 'anonymous'); role = self.headers.get('X-AIOS-Role') or ('owner' if DEV_ALLOW else 'user')
return {'username': safe_name(user), 'role': role, 'token_ok': ok}
def require_write(self, roles=None):
a = self.actor()
if not a['token_ok']: self.send_json({'error':'unauthorized','hint':'write APIs require Bearer token or AIOS_GITOPS_DEV_ALLOW=1'}, 401); return None
@@ -185,19 +135,7 @@ class Handler(BaseHTTPRequestHandler):
self.send_json({'error':'not_found','path':path},404)
except Exception as e: self.send_json({'error':str(e)},500)
def do_POST(self):
path = urllib.parse.urlparse(self.path).path
if path == '/api/runtime/select':
if not (_loopback(HOST) and _loopback(self.client_address[0])):
self.send_json({'error':'loopback ingress required'},403); return
a = self.require_write()
if not a: return
data = parse_body(self)
try: runtime_selection(a, data)
except (ValueError, PermissionError):
self.send_json({'error':'invalid runtime selection identity'},403); return
else:
data = parse_body(self)
init_db()
init_db(); path = urllib.parse.urlparse(self.path).path; data = parse_body(self)
try:
if path == '/api/model/custom/add':
a = self.require_write();
@@ -251,7 +189,7 @@ class Handler(BaseHTTPRequestHandler):
if not m: self.send_json({'error':'model not found'},404); return
self.send_json({'status':'ok' if m['base_url'] and m['model_name'] else 'failed','live_test':os.getenv('AIOS_ENABLE_MODEL_LIVE_TEST','0')=='1','model':m['model_name'],'base_url':m['base_url'],'note':'Network live call is disabled unless AIOS_ENABLE_MODEL_LIVE_TEST=1'}); return
if path == '/api/runtime/select':
username,role,tier=runtime_selection(a, data); risk=data.get('risk','normal')
a=self.actor(); username=safe_name(data.get('username') or a['username']); role=data.get('role') or a['role']; tier=data.get('tier') or ('ultra' if role=='owner' else 'regular'); risk=data.get('risk','normal')
if risk in ('high_tool','shell','docker','write_prod'): runtime,backend='ephemeral_sandbox','openclaw_executor'
elif role in ('owner','admin') or tier=='ultra': runtime,backend='dedicated','hermes'
elif tier=='pro': runtime,backend='dedicated_or_shared','hermes'
@@ -326,5 +264,5 @@ class Handler(BaseHTTPRequestHandler):
except Exception as e: self.send_json({'error':str(e)},500)
def api_spec(): return {'version':'V1R1C0','short_links':['/base','/ver','/diff','/patch','/run','/model','/api'],'gitops_get':['/api/status','/api/base','/api/ver','/api/diff','/api/patch'],'gitops_post':['/api/branch','/api/file','/api/commit','/api/tag','/api/rollback','/api/nl'],'model_get':['/api/model/base','/api/model/custom','/api/model/priority'],'model_post':['/api/model/custom/add','/api/model/custom/update','/api/model/delete','/api/model/priority/set','/api/model/route/resolve','/api/model/test'],'runtime':['/api/runtime/status','/api/runtime/select','/api/runtime/register','/api/runtime/heartbeat'],'auth':'write APIs require Bearer token unless AIOS_GITOPS_DEV_ALLOW=1 for local acceptance tests'}
def main(): validate_dev_profile(); init_db(); print(f'AIOS V1R1C0 GitOps Model API listening on {HOST}:{PORT}'); ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
def main(): init_db(); print(f'AIOS V1R1C0 GitOps Model API listening on {HOST}:{PORT}'); ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
if __name__ == '__main__': main()
@@ -1,890 +0,0 @@
"""Read-only four-authority evidence provider for one canonical C04 attachment.
This module never creates, claims, resumes, reconciles, or mutates a Task or
Job. The expired Owner epoch is verified as historical provenance only.
"""
from __future__ import annotations
import hashlib
import json
import os
import sqlite3
import stat
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable, Mapping
from app.aios_gitops_job_identity import legacy_task_storage_key, task_storage_key
from app.aios_single_host_authority import (
DEFAULT_OWNER_SIGNER_ID,
OWNER_NAMESPACE,
RegistryOwnerApprovalVerifier,
SignerRegistry,
)
from app.task_plane.legacy_attachment import (
ATTACH_EVIDENCE_SCHEMA,
AuthorityReadbackRef,
SOURCE_KIND,
VerifiedLegacyAttachmentEvidence,
)
class LegacyAttachmentRuntimeError(RuntimeError):
"""Stable fail-closed readback error."""
def _fail(code: str) -> None:
raise LegacyAttachmentRuntimeError(code)
def _strict_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
value: dict[str, Any] = {}
for key, item in pairs:
if key in value:
_fail("DUPLICATE_JSON_FIELD")
value[key] = item
return value
def _json_object(raw: bytes | str, code: str) -> dict[str, Any]:
try:
if isinstance(raw, bytes):
raw = raw.decode("utf-8")
value = json.loads(raw, object_pairs_hook=_strict_object)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise LegacyAttachmentRuntimeError(code) from exc
if not isinstance(value, dict):
_fail(code)
return value
def _canonical(value: object) -> bytes:
try:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_JSON_INVALID") from exc
def _sha(value: object) -> str:
return hashlib.sha256(_canonical(value)).hexdigest()
def _hex(value: object, code: str) -> str:
if (
not isinstance(value, str)
or len(value) != 64
or any(ch not in "0123456789abcdef" for ch in value)
):
_fail(code)
return value
def _text(value: object, code: str) -> str:
if not isinstance(value, str) or not value or value != value.strip():
_fail(code)
return value
def _integer(value: object, code: str) -> int:
if type(value) is not int or value < 0:
_fail(code)
return value
def _iso(value: object, code: str) -> str:
text = _text(value, code)
try:
parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
except ValueError as exc:
raise LegacyAttachmentRuntimeError(code) from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
_fail(code)
return parsed.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
def _observed(clock: Callable[[], float]) -> str:
return datetime.fromtimestamp(float(clock()), timezone.utc).isoformat().replace(
"+00:00", "Z"
)
def _mapping(value: object, code: str) -> dict[str, Any]:
if not isinstance(value, Mapping):
_fail(code)
return dict(value)
def _secure_file(
path: Path, *, require_root_owner: bool, writable_authority: bool = False
) -> os.stat_result:
try:
info = path.lstat()
except OSError as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_FILE_UNAVAILABLE") from exc
if path.is_symlink() or not stat.S_ISREG(info.st_mode):
_fail("AUTHORITY_FILE_INVALID")
if os.name == "posix":
if not writable_authority and stat.S_IMODE(info.st_mode) & 0o022:
_fail("AUTHORITY_FILE_WRITABLE")
if require_root_owner and info.st_uid != 0:
_fail("AUTHORITY_FILE_OWNER_INVALID")
return info
def _secure_directory(path: Path) -> os.stat_result:
try:
info = path.lstat()
except OSError as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_DIRECTORY_UNAVAILABLE") from exc
if path.is_symlink() or not stat.S_ISDIR(info.st_mode):
_fail("AUTHORITY_DIRECTORY_INVALID")
return info
def _stable_read(path: Path, *, require_root_owner: bool, maximum: int = 2_000_000) -> bytes:
before = _secure_file(path, require_root_owner=require_root_owner)
if before.st_size > maximum:
_fail("AUTHORITY_FILE_TOO_LARGE")
try:
raw = path.read_bytes()
except OSError as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_FILE_UNAVAILABLE") from exc
after = _secure_file(path, require_root_owner=require_root_owner)
if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (
after.st_dev,
after.st_ino,
after.st_size,
after.st_mtime_ns,
):
_fail("AUTHORITY_FILE_CHANGED")
return raw
def _ro_connection(path: Path, *, require_root_owner: bool) -> tuple[sqlite3.Connection, os.stat_result]:
before = _secure_file(
path, require_root_owner=require_root_owner, writable_authority=True
)
try:
resolved = path.resolve(strict=True)
connection = sqlite3.connect(
resolved.as_uri() + "?mode=ro", uri=True, timeout=5.0
)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA query_only=ON")
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("BEGIN")
return connection, before
except (OSError, sqlite3.Error) as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_DB_UNAVAILABLE") from exc
def _finish_ro(connection: sqlite3.Connection, path: Path, before: os.stat_result) -> None:
connection.close()
after = _secure_file(
path, require_root_owner=False, writable_authority=True
)
if (before.st_dev, before.st_ino) != (after.st_dev, after.st_ino):
_fail("AUTHORITY_DB_CHANGED")
def _columns(connection: sqlite3.Connection, table: str) -> tuple[str, ...]:
return tuple(str(row[1]) for row in connection.execute(f"PRAGMA table_info({table})"))
@dataclass(frozen=True, slots=True)
class LegacyC04Binding:
mission_id: str
root_task_id: str
anchor_task_id: str
owner_principal_id: str
original_acceptance_digest: str
source_task_id: str
external_job_id: str
def validate(self) -> None:
for value in (
self.mission_id,
self.root_task_id,
self.anchor_task_id,
self.owner_principal_id,
self.source_task_id,
self.external_job_id,
):
_text(value, "C04_BINDING_INVALID")
_hex(self.original_acceptance_digest, "C04_ACCEPTANCE_DIGEST_INVALID")
@dataclass(frozen=True, slots=True)
class CredentialFileBearerSupplier:
"""Read a systemd credential for one call without caching or displaying it."""
path: Path
def __post_init__(self) -> None:
if not self.path.is_absolute():
_fail("AGENT_TASK_CREDENTIAL_PATH_INVALID")
if os.name == "posix":
try:
self.path.resolve(strict=False).relative_to(Path("/run/credentials"))
except ValueError as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_CREDENTIAL_PATH_INVALID"
) from exc
def __call__(self) -> str:
raw = _stable_read(self.path, require_root_owner=False, maximum=8192)
try:
value = raw.decode("utf-8").strip()
except UnicodeDecodeError as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_CREDENTIAL_INVALID"
) from exc
if len(value) < 32 or len(value) > 4096:
_fail("AGENT_TASK_CREDENTIAL_INVALID")
return value
class _NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl): # noqa: ANN001
return None
_AGENT_TASK_COLUMNS = (
"task_id",
"goal",
"instructions",
"success_criteria_json",
"required_capabilities_json",
"constraints_json",
"metadata_json",
"deadline",
"priority",
"max_attempts",
"attempt",
"status",
"claimed_by",
"lease_token_hash",
"lease_expires_at",
"result_json",
"result_digest",
"result_idempotency_key",
"verification_json",
"revision",
"created_at",
"updated_at",
)
@dataclass(slots=True)
class AuthenticatedAgentTaskReader:
"""Authenticated non-mutating proof + read-only authority-row snapshot.
The authenticated HTTP request is intentionally sent to the task events
endpoint because the historical GET /v1/tasks/{id} path calls the global
lease-expiry mutator. The task row itself is read from the authoritative
SQLite database using query_only mode and an exact schema allowlist.
"""
base_url: str
bearer_supplier: Callable[[], str]
task_db_path: Path
timeout_seconds: float = 10.0
clock: Callable[[], float] = time.time
require_db_root_owner: bool = False
def __post_init__(self) -> None:
parsed = urllib.parse.urlsplit(self.base_url)
if (
parsed.scheme != "http"
or parsed.hostname not in {"127.0.0.1", "localhost", "::1"}
or parsed.username is not None
or parsed.password is not None
or parsed.query
or parsed.fragment
):
_fail("AGENT_TASK_URL_INVALID")
if not self.task_db_path.is_absolute():
_fail("AGENT_TASK_DB_PATH_INVALID")
self.base_url = self.base_url.rstrip("/")
def _authenticated_events(self, task_id: str) -> tuple[list[dict[str, Any]], str]:
token = self.bearer_supplier()
if not isinstance(token, str) or len(token.strip()) < 32:
_fail("AGENT_TASK_CREDENTIAL_INVALID")
url = (
self.base_url
+ "/v1/tasks/"
+ urllib.parse.quote(task_id, safe="._:-")
+ "/events"
)
request = urllib.request.Request(
url,
method="GET",
headers={
"Authorization": "Bearer " + token.strip(),
"Accept": "application/json",
},
)
try:
with urllib.request.build_opener(_NoRedirect).open(
request, timeout=self.timeout_seconds
) as response:
if response.status != 200 or response.geturl() != url:
_fail("AGENT_TASK_EVENTS_REJECTED")
raw = response.read(2_000_001)
except (urllib.error.URLError, TimeoutError, OSError) as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_EVENTS_UNAVAILABLE"
) from exc
finally:
token = ""
if len(raw) > 2_000_000:
_fail("AGENT_TASK_EVENTS_TOO_LARGE")
try:
value = json.loads(raw.decode("utf-8"), object_pairs_hook=_strict_object)
except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as exc:
raise LegacyAttachmentRuntimeError("AGENT_TASK_EVENTS_INVALID") from exc
if not isinstance(value, list):
_fail("AGENT_TASK_EVENTS_INVALID")
events: list[dict[str, Any]] = []
for event in value:
if not isinstance(event, Mapping) or event.get("task_id") != task_id:
_fail("AGENT_TASK_EVENTS_INVALID")
events.append(dict(event))
return events, url
@staticmethod
def _task_projection(row: sqlite3.Row) -> dict[str, Any]:
try:
body = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"deadline": row["deadline"],
"priority": row["priority"],
"max_attempts": row["max_attempts"],
"attempt": row["attempt"],
"status": row["status"],
"claimed_by": row["claimed_by"],
"lease_expires_at": row["lease_expires_at"],
"revision": row["revision"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
"result": json.loads(row["result_json"]) if row["result_json"] else None,
"verification": (
json.loads(row["verification_json"])
if row["verification_json"]
else None
),
}
except (KeyError, TypeError, json.JSONDecodeError) as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_ROW_INVALID"
) from exc
_mapping(body["constraints"], "AGENT_TASK_CONSTRAINTS_INVALID")
_mapping(body["metadata"], "AGENT_TASK_METADATA_INVALID")
_integer(body["revision"], "AGENT_TASK_REVISION_INVALID")
if body["status"] not in {"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"}:
_fail("AGENT_TASK_NOT_TERMINAL")
return body
def read(self, task_id: str) -> dict[str, Any]:
events, events_url = self._authenticated_events(task_id)
connection, before = _ro_connection(
self.task_db_path, require_root_owner=self.require_db_root_owner
)
try:
if _columns(connection, "tasks") != _AGENT_TASK_COLUMNS:
_fail("AGENT_TASK_DB_SCHEMA_INVALID")
row = connection.execute(
"SELECT * FROM tasks WHERE task_id=? LIMIT 1", (task_id,)
).fetchone()
if row is None:
_fail("AGENT_TASK_NOT_FOUND")
body = self._task_projection(row)
finally:
_finish_ro(connection, self.task_db_path, before)
event_digest = _sha(events)
return {
"body": body,
"observed_at": _observed(self.clock),
"readback_ref": (
events_url
+ "#authenticated-events-sha256="
+ event_digest
+ ";task-db-revision="
+ str(body["revision"])
),
"canonical_sha256": _sha(body),
"authenticated_events_sha256": event_digest,
}
_ADMISSION_COLUMNS = {
"task_admission_envelopes": (
"idempotency_key", "envelope_id", "request_digest", "envelope_json", "status", "created_at"
),
"task_admission_grants": (
"envelope_id", "grant_id", "grant_digest", "grant_json", "authorization_digest", "created_at"
),
"task_admission_receipts": ("envelope_id", "task_id", "receipt_json", "created_at"),
}
@dataclass(slots=True)
class AdmissionEpochRegistryReader:
db_path: Path
epoch_root: Path
revocation_file: Path
registry_path: Path
require_db_root_owner: bool = False
require_static_root_owner: bool = True
clock: Callable[[], float] = time.time
def __post_init__(self) -> None:
for path in (self.db_path, self.epoch_root, self.revocation_file, self.registry_path):
if not path.is_absolute():
_fail("AUTHORITY_PATH_NOT_ABSOLUTE")
def read(self, task_id: str) -> dict[str, Any]:
connection, before = _ro_connection(
self.db_path, require_root_owner=self.require_db_root_owner
)
try:
if any(_columns(connection, table) != columns for table, columns in _ADMISSION_COLUMNS.items()):
_fail("ADMISSION_SCHEMA_INVALID")
rows = connection.execute(
"SELECT e.*,g.grant_id,g.grant_digest,g.grant_json,g.authorization_digest,"
"g.created_at AS grant_created_at,r.task_id,r.receipt_json,"
"r.created_at AS receipt_created_at FROM task_admission_receipts r "
"JOIN task_admission_envelopes e ON e.envelope_id=r.envelope_id "
"JOIN task_admission_grants g ON g.envelope_id=r.envelope_id WHERE r.task_id=?",
(task_id,),
).fetchall()
if len(rows) != 1:
_fail("ADMISSION_READBACK_AMBIGUOUS")
row = dict(rows[0])
except sqlite3.Error as exc:
raise LegacyAttachmentRuntimeError("ADMISSION_READBACK_FAILED") from exc
finally:
_finish_ro(connection, self.db_path, before)
envelope = _json_object(row["envelope_json"], "ADMISSION_ENVELOPE_INVALID")
grant = _json_object(row["grant_json"], "ADMISSION_GRANT_INVALID")
receipt = _json_object(row["receipt_json"], "ADMISSION_RECEIPT_INVALID")
envelope_id = _text(row["envelope_id"], "ADMISSION_ENVELOPE_INVALID")
grant_id = _text(row["grant_id"], "ADMISSION_GRANT_INVALID")
epoch_id = _text(grant.get("parent_epoch_id"), "ADMISSION_EPOCH_ID_INVALID")
if (
row.get("status") != "ADMITTED"
or envelope.get("schema") != "aios.task-admission-envelope.v1"
or grant.get("schema") != "aios.task-admission-grant.v1"
or receipt.get("schema") != "aios.task-admission-receipt.v1"
or envelope.get("envelope_id") != envelope_id
or grant.get("envelope_id") != envelope_id
or receipt.get("envelope_id") != envelope_id
or envelope.get("task_id") != task_id
or grant.get("task_id") != task_id
or receipt.get("task_id") != task_id
or grant.get("grant_id") != grant_id
or receipt.get("grant_id") != grant_id
or grant.get("authorization_kind") != "delegation_epoch"
or receipt.get("authorization_kind") != "delegation_epoch"
or receipt.get("parent_epoch_id") != epoch_id
):
_fail("ADMISSION_BINDING_INVALID")
request_value = {
key: envelope.get(key)
for key in (
"task_id", "repository", "authority_base_oid", "target_branch",
"generation", "execution_digest", "requested_capabilities",
"file_allowlist", "risk", "trust_domain", "policy_digest",
)
}
if envelope.get("request_digest") != _sha(request_value) or row.get("request_digest") != _sha(request_value):
_fail("ADMISSION_REQUEST_DIGEST_INVALID")
grant_digest = _sha(grant)
if row.get("grant_digest") != grant_digest or receipt.get("grant_digest_sha256") != grant_digest:
_fail("ADMISSION_GRANT_DIGEST_INVALID")
auth_value = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": epoch_id,
}
if row.get("authorization_digest") != _sha(auth_value):
_fail("ADMISSION_AUTHORIZATION_DIGEST_INVALID")
linked = (
"repository", "authority_base_oid", "target_branch", "generation",
"execution_digest", "policy_digest", "trust_domain", "risk",
)
if any(grant.get(key) != envelope.get(key) for key in linked):
_fail("ADMISSION_GRANT_SCOPE_INVALID")
if grant.get("capabilities") != envelope.get("requested_capabilities") or grant.get("file_allowlist") != envelope.get("file_allowlist"):
_fail("ADMISSION_GRANT_SCOPE_INVALID")
if receipt.get("execution_digest") != grant.get("execution_digest"):
_fail("ADMISSION_RECEIPT_SCOPE_INVALID")
_secure_directory(self.epoch_root)
canonical_path = self.epoch_root / "owner-delegation-epoch.canonical.json"
signature_path = self.epoch_root / "owner-delegation-epoch.sig"
key_path = self.epoch_root / "owner-delegation-epoch.key-id"
canonical = _stable_read(canonical_path, require_root_owner=self.require_static_root_owner)
epoch = _json_object(canonical, "OWNER_EPOCH_INVALID")
if _canonical(epoch) != canonical:
_fail("OWNER_EPOCH_NOT_CANONICAL")
signature = _stable_read(signature_path, require_root_owner=self.require_static_root_owner).decode("utf-8")
key_id = _stable_read(key_path, require_root_owner=self.require_static_root_owner).decode("utf-8").strip()
registry_raw = _stable_read(self.registry_path, require_root_owner=self.require_static_root_owner)
registry_json = _json_object(registry_raw, "OWNER_REGISTRY_INVALID")
revocations = _json_object(
_stable_read(self.revocation_file, require_root_owner=self.require_static_root_owner),
"OWNER_REVOCATIONS_INVALID",
)
if (
set(revocations) != {"schema", "revoked_epoch_ids"}
or revocations.get("schema") != "aios.owner-delegation-revocations.v1"
or not isinstance(revocations.get("revoked_epoch_ids"), list)
or epoch_id in revocations["revoked_epoch_ids"]
):
_fail("OWNER_EPOCH_REVOKED")
try:
registry = SignerRegistry(
self.registry_path, require_root_owner=self.require_static_root_owner
)
record = registry.require(DEFAULT_OWNER_SIGNER_ID, role="OWNER_AUTHORITY")
verified = RegistryOwnerApprovalVerifier(
self.registry_path,
require_root_owner=self.require_static_root_owner,
signer_id=DEFAULT_OWNER_SIGNER_ID,
).verify(
namespace=OWNER_NAMESPACE,
payload=canonical,
signature=signature,
key_id=key_id,
)
except Exception as exc:
raise LegacyAttachmentRuntimeError("OWNER_REGISTRY_VERIFICATION_FAILED") from exc
now = int(self.clock())
provenance_checks = {
"signature": verified is True,
"fingerprint": record.fingerprint == key_id,
"schema": epoch.get("schema") == "aios.owner-delegation-epoch.v1",
"epoch_id": epoch.get("epoch_id") == epoch_id,
"owner": epoch.get("owner_identity") == grant.get("owner_identity"),
"repository": epoch.get("repository") == grant.get("repository"),
"trust_domain": epoch.get("trust_domain") == grant.get("trust_domain"),
"policy": epoch.get("policy_digest") == grant.get("policy_digest"),
"revocation_epoch": epoch.get("revocation_epoch") == 0,
"expires_type": type(epoch.get("expires_at")) is int,
"not_before_type": type(epoch.get("not_before")) is int,
}
failed = [name for name, passed in provenance_checks.items() if not passed]
if failed:
_fail("OWNER_EPOCH_PROVENANCE_INVALID:" + ",".join(sorted(failed)))
if (
now < epoch["expires_at"]
or type(epoch.get("issued_at")) is not int
or epoch["issued_at"] > grant.get("issued_at", 0)
or grant.get("issued_at", 0) < epoch["not_before"]
or grant.get("expires_at", 0) > epoch["expires_at"]
):
_fail("OWNER_EPOCH_PROVENANCE_INVALID:temporal")
if not any(str(grant["target_branch"]).startswith(str(prefix)) for prefix in epoch.get("allowed_branch_prefixes", [])):
_fail("OWNER_EPOCH_SCOPE_INVALID")
if not set(grant.get("capabilities", [])).issubset(set(epoch.get("allowed_capabilities", []))):
_fail("OWNER_EPOCH_SCOPE_INVALID")
risk_rank = {"low": 0, "medium": 1, "high": 2}
if (
grant.get("risk") not in risk_rank
or epoch.get("risk_ceiling") not in risk_rank
or risk_rank[grant["risk"]] > risk_rank[epoch["risk_ceiling"]]
):
_fail("OWNER_EPOCH_SCOPE_INVALID")
if any(
not any(path == prefix.rstrip("/") or path.startswith(prefix.rstrip("/") + "/") for prefix in epoch.get("allowed_file_prefixes", []))
for path in grant.get("file_allowlist", [])
):
_fail("OWNER_EPOCH_SCOPE_INVALID")
admission_projection = {
"envelope": envelope,
"grant": grant,
"receipt": receipt,
"request_digest": row["request_digest"],
"grant_digest": row["grant_digest"],
"authorization_digest": row["authorization_digest"],
"envelope_status": row["status"],
}
owner_projection = {
"epoch": epoch,
"epoch_sha256": hashlib.sha256(canonical).hexdigest(),
"epoch_key_id": key_id,
"signature_verified": True,
"epoch_currently_valid": False,
"revocations": revocations,
"registry_schema": registry_json.get("schema"),
"registry_generation": registry.authority_generation,
"signer": record.to_dict(),
}
observed = _observed(self.clock)
return {
"envelope": envelope,
"grant": grant,
"receipt": receipt,
"epoch": epoch,
"epoch_digest": hashlib.sha256(canonical).hexdigest(),
"signer_id": record.signer_id,
"signer_fingerprint": record.fingerprint,
"registry_generation": registry.authority_generation,
"observed_at": observed,
"admission_ref": f"sqlite:{self.db_path}#task={task_id};envelope={envelope_id}",
"admission_sha256": _sha(admission_projection),
"registry_ref": f"file:{self.registry_path}#generation={registry.authority_generation};epoch={epoch_id}",
"registry_sha256": _sha(owner_projection),
}
_JOB_COLUMNS = {
"job_aliases": (
"external_job_id", "internal_job_id", "task_id", "repository", "target_branch",
"branch_mode", "generation", "commit_idempotency_key", "expected_branch_oid",
"request_digest", "adopted_legacy", "created_at",
),
"jobs": (
"job_id", "idempotency_key", "campaign_id", "repository", "operation", "branch",
"expected_branch_oid", "payload_digest", "payload_ref", "payload_size", "state",
"side_effect_state", "revision", "request_json", "receipt_json", "created_at", "updated_at",
),
"idempotency_keys": ("key", "job_id", "binding_json", "created_at"),
}
@dataclass(slots=True)
class BridgeJobSQLiteReader:
jobs_root: Path
require_root_owner: bool = False
clock: Callable[[], float] = time.time
def __post_init__(self) -> None:
if not self.jobs_root.is_absolute():
_fail("BRIDGE_JOB_ROOT_NOT_ABSOLUTE")
def read(self, task_id: str, external_job_id: str) -> dict[str, Any]:
exact_root = self.jobs_root / task_storage_key(task_id)
legacy_root = self.jobs_root / legacy_task_storage_key(task_id)
db_path = exact_root / "state.sqlite3"
if legacy_root != exact_root and legacy_root.exists():
_fail("BRIDGE_JOB_STORE_AMBIGUOUS")
_secure_directory(self.jobs_root)
_secure_directory(exact_root)
try:
if db_path.resolve(strict=True).parent != exact_root.resolve(strict=True):
_fail("BRIDGE_JOB_STORE_INVALID")
except OSError as exc:
raise LegacyAttachmentRuntimeError("BRIDGE_JOB_STORE_INVALID") from exc
connection, before = _ro_connection(
db_path, require_root_owner=self.require_root_owner
)
try:
if any(_columns(connection, table) != columns for table, columns in _JOB_COLUMNS.items()):
_fail("BRIDGE_JOB_SCHEMA_INVALID")
aliases = connection.execute(
"SELECT * FROM job_aliases WHERE external_job_id=? AND task_id=?",
(external_job_id, task_id),
).fetchall()
if len(aliases) != 1:
_fail("BRIDGE_JOB_ALIAS_AMBIGUOUS")
alias = dict(aliases[0])
job_rows = connection.execute(
"SELECT * FROM jobs WHERE job_id=?", (alias["internal_job_id"],)
).fetchall()
key_rows = connection.execute(
"SELECT * FROM idempotency_keys WHERE job_id=?", (alias["internal_job_id"],)
).fetchall()
if len(job_rows) != 1 or len(key_rows) != 1:
_fail("BRIDGE_JOB_BINDING_MISSING")
job, key = dict(job_rows[0]), dict(key_rows[0])
except sqlite3.Error as exc:
raise LegacyAttachmentRuntimeError("BRIDGE_JOB_READBACK_FAILED") from exc
finally:
_finish_ro(connection, db_path, before)
request = _json_object(job.get("request_json") or "{}", "BRIDGE_JOB_REQUEST_INVALID")
binding = _mapping(request.get("binding", request), "BRIDGE_JOB_REQUEST_INVALID")
key_binding = _json_object(key.get("binding_json") or "{}", "BRIDGE_JOB_KEY_BINDING_INVALID")
external_commit_key = _text(alias.get("commit_idempotency_key"), "BRIDGE_EXTERNAL_IDEMPOTENCY_INVALID")
internal_key = _text(job.get("idempotency_key"), "BRIDGE_INTERNAL_IDEMPOTENCY_INVALID")
if (
alias.get("external_job_id") != external_job_id
or alias.get("task_id") != task_id
or job.get("job_id") != alias.get("internal_job_id")
or job.get("campaign_id") != task_id
or key.get("job_id") != job.get("job_id")
or key.get("key") != internal_key
or binding.get("task_id", binding.get("campaign_id")) != task_id
or binding.get("external_job_id") != external_job_id
or binding.get("idempotency_key") != external_commit_key
or key_binding.get("task_id", key_binding.get("campaign_id")) != task_id
or key_binding.get("external_job_id") != external_job_id
or key_binding.get("idempotency_key") != external_commit_key
or alias.get("repository") != job.get("repository")
or alias.get("repository") != binding.get("repository")
or alias.get("target_branch") != job.get("branch")
or alias.get("target_branch") != binding.get("branch", binding.get("target_branch"))
):
_fail("BRIDGE_JOB_BINDING_INVALID")
try:
if int(alias.get("generation")) != int(binding.get("generation")):
_fail("BRIDGE_JOB_BINDING_INVALID")
except (TypeError, ValueError) as exc:
raise LegacyAttachmentRuntimeError("BRIDGE_JOB_BINDING_INVALID") from exc
projection = {
"alias": alias,
"job": job,
"idempotency": key,
"external_commit_idempotency_key": external_commit_key,
"internal_idempotency_key": internal_key,
}
return {
**projection,
"observed_at": _observed(self.clock),
"readback_ref": f"sqlite:{db_path}#external={external_job_id};internal={job['job_id']}",
"canonical_sha256": _sha(projection),
}
@dataclass(slots=True)
class LegacyAttachmentRuntimeProvider:
binding: LegacyC04Binding
agent_task: AuthenticatedAgentTaskReader
admission: AdmissionEpochRegistryReader
bridge_job: BridgeJobSQLiteReader
def __post_init__(self) -> None:
self.binding.validate()
def read_verified(
self,
*,
mission_id: str,
root_task_id: str,
anchor_task_id: str,
source_kind: str,
source_task_id: str,
external_job_id: str,
original_acceptance_digest: str,
) -> VerifiedLegacyAttachmentEvidence:
expected = self.binding
if (
mission_id != expected.mission_id
or root_task_id != expected.root_task_id
or anchor_task_id != expected.anchor_task_id
or source_kind != SOURCE_KIND
or source_task_id != expected.source_task_id
or external_job_id != expected.external_job_id
or original_acceptance_digest != expected.original_acceptance_digest
):
_fail("C04_AUTHORITY_BINDING_MISMATCH")
task_read = self.agent_task.read(source_task_id)
admission_read = self.admission.read(source_task_id)
job_read = self.bridge_job.read(source_task_id, external_job_id)
task = task_read["body"]
metadata = _mapping(task["metadata"], "AGENT_TASK_METADATA_INVALID")
constraints = _mapping(task["constraints"], "AGENT_TASK_CONSTRAINTS_INVALID")
execution = _mapping(metadata.get("execution"), "AGENT_TASK_EXECUTION_INVALID")
grant = admission_read["grant"]
envelope = admission_read["envelope"]
alias, job = job_read["alias"], job_read["job"]
try:
immutable_metadata = _json_object(
constraints.get("immutable_metadata", ""), "AGENT_TASK_IMMUTABLE_METADATA_INVALID"
)
except LegacyAttachmentRuntimeError:
raise
if (
immutable_metadata != metadata
or metadata.get("grant_id") != grant.get("grant_id")
or metadata.get("grant_digest_sha256") != admission_read["receipt"].get("grant_digest_sha256")
or metadata.get("execution_digest") != grant.get("execution_digest")
or execution.get("execution_digest") != grant.get("execution_digest")
or execution.get("external_job_id") != external_job_id
or execution.get("task_id") != source_task_id
or metadata.get("repository") != grant.get("repository")
or metadata.get("target_branch") != grant.get("target_branch")
or metadata.get("generation") != grant.get("generation")
or constraints.get("repository") != grant.get("repository")
or constraints.get("target_branch") != grant.get("target_branch")
or constraints.get("generation") != grant.get("generation")
or envelope.get("task_id") != source_task_id
or alias.get("repository") != grant.get("repository")
or alias.get("target_branch") != grant.get("target_branch")
or alias.get("generation") != grant.get("generation")
or admission_read["signer_id"] != expected.owner_principal_id
):
_fail("FOUR_AUTHORITY_BINDING_INVALID")
return VerifiedLegacyAttachmentEvidence(
schema=ATTACH_EVIDENCE_SCHEMA,
source_kind=SOURCE_KIND,
source_task_id=source_task_id,
source_task_created_at=_iso(task.get("created_at"), "AGENT_TASK_CREATED_AT_INVALID"),
source_task_status=_text(task.get("status"), "AGENT_TASK_STATUS_INVALID"),
source_task_revision=_integer(task.get("revision"), "AGENT_TASK_REVISION_INVALID"),
source_task_attempt=_integer(task.get("attempt"), "AGENT_TASK_ATTEMPT_INVALID"),
source_task_max_attempts=_integer(task.get("max_attempts"), "AGENT_TASK_MAX_ATTEMPTS_INVALID"),
external_job_id=external_job_id,
internal_job_id=_text(job.get("job_id"), "BRIDGE_INTERNAL_JOB_INVALID"),
internal_idempotency_key=_text(job.get("idempotency_key"), "BRIDGE_INTERNAL_IDEMPOTENCY_INVALID"),
job_branch=_text(job.get("branch"), "BRIDGE_JOB_BRANCH_INVALID"),
job_state=_text(job.get("state"), "BRIDGE_JOB_STATE_INVALID"),
job_revision=_integer(job.get("revision"), "BRIDGE_JOB_REVISION_INVALID"),
side_effect_state=_text(job.get("side_effect_state"), "BRIDGE_SIDE_EFFECT_INVALID"),
grant_id=_text(grant.get("grant_id"), "ADMISSION_GRANT_INVALID"),
grant_digest=_hex(admission_read["receipt"].get("grant_digest_sha256"), "ADMISSION_GRANT_DIGEST_INVALID"),
execution_digest=_hex(grant.get("execution_digest"), "ADMISSION_EXECUTION_DIGEST_INVALID"),
epoch_id=_text(admission_read["epoch"].get("epoch_id"), "OWNER_EPOCH_ID_INVALID"),
epoch_digest=_hex(admission_read["epoch_digest"], "OWNER_EPOCH_DIGEST_INVALID"),
historical_signer_id=admission_read["signer_id"],
historical_signer_fingerprint=admission_read["signer_fingerprint"],
registry_generation=_integer(admission_read["registry_generation"], "OWNER_REGISTRY_GENERATION_INVALID"),
signature_verified=True,
epoch_currently_valid=False,
readbacks=(
AuthorityReadbackRef(
role="agent_task", authority="aios-agent-task-api",
readback_ref=task_read["readback_ref"], canonical_sha256=task_read["canonical_sha256"],
observed_at=task_read["observed_at"], revision=task["revision"],
),
AuthorityReadbackRef(
role="admission", authority="aios-task-admission-store",
readback_ref=admission_read["admission_ref"], canonical_sha256=admission_read["admission_sha256"],
observed_at=admission_read["observed_at"], revision=None,
),
AuthorityReadbackRef(
role="owner_registry", authority="aios-single-host-owner-authority",
readback_ref=admission_read["registry_ref"], canonical_sha256=admission_read["registry_sha256"],
observed_at=admission_read["observed_at"], revision=admission_read["registry_generation"],
),
AuthorityReadbackRef(
role="bridge_job", authority="aios-gitops-job-controller",
readback_ref=job_read["readback_ref"], canonical_sha256=job_read["canonical_sha256"],
observed_at=job_read["observed_at"], revision=job["revision"],
),
),
)
+20 -52
View File
@@ -135,11 +135,6 @@ SECRET_VAL_RE = re.compile(
r'(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,})'
)
# Control-plane file read: enforce auth, strict identifier, containment and a
# size cap on the command-detail read exposed by /admin/ai-evolution/api/data.
MAX_COMMAND_READ_BYTES = 1024 * 1024
_COMMAND_FILENAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*\.md$")
READ_ONLY_GET_PATHS = frozenset({
'/', '/health', '/api/health', '/api/openapi.yaml',
'/api/openapi-chatgpt-actions.yaml', '/api/schema', '/api', '/api/status',
@@ -160,7 +155,6 @@ SILENT_RELEASE_POST_PATHS = frozenset({
'/submitSignedApproval',
'/submitOwnerApproval',
'/startDeployment',
'/reopenOwnerApproval',
})
def reject_non_read_http(handler, method: str, path: str) -> bool:
@@ -486,12 +480,8 @@ def token_records():
else:
out.append({'account':'legacy-token','role':'owner','scopes':['*'],'token_plain':line})
return out
from security_path_guards import require_isolated_dev_profile
def authenticate(token: str):
if os.getenv('AIOS_GITOPS_DEV_ALLOW','0') == '1':
require_isolated_dev_profile(HOST)
return {'account':'dev-owner','role':'owner','scopes':['*']}
if os.getenv('AIOS_GITOPS_DEV_ALLOW','0') == '1': return {'account':'dev-owner','role':'owner','scopes':['*']}
if not token: return None
digest=hashlib.sha256(token.encode()).hexdigest()
for r in token_records():
@@ -699,6 +689,20 @@ class H(BaseHTTPRequestHandler):
return self.sendj(body, exc.code)
except (urllib.error.URLError, TimeoutError) as exc:
return self.sendj({'error': 'GOVERNED_BRIDGE_UNAVAILABLE', 'detail': type(exc).__name__}, 503)
def do_POST(self):
path = urllib.parse.urlparse(self.path).path
if path in {
'/createTagApprovalRequest', '/requestOrCreateTag', '/getTagPublicationStatus',
'/buildProductionArtifact', '/executeApprovedProductionRelease',
'/getReleaseConsistency', '/runReleaseAcceptance',
}:
actor = self.need('tag:create')
if not actor:
return
return self.bridge_release(path, self.body())
if reject_non_read_http(self, 'POST', path):
return
return self.sendj({'error': 'not_found', 'path': path}, 404)
def do_GET(self):
u=urllib.parse.urlparse(self.path); path=u.path.rstrip('/') if u.path != '/' else u.path
if path not in READ_ONLY_GET_PATHS: return self.sendj({'error':'not_found','path':path},404)
@@ -944,17 +948,6 @@ class H(BaseHTTPRequestHandler):
path=urllib.parse.urlparse(self.path).path.rstrip('/')
if reject_non_read_http(self, 'POST', path): return
data=self.body()
# Governed V2 bridge release routes (previously shadowed by the
# redefinition of do_POST in this class).
if path in {
'/createTagApprovalRequest', '/requestOrCreateTag', '/getTagPublicationStatus',
'/buildProductionArtifact', '/executeApprovedProductionRelease',
'/getReleaseConsistency', '/runReleaseAcceptance',
}:
actor = self.need('tag:create')
if not actor:
return
return self.bridge_release(path, self.body())
try:
if path == '/createReleaseCandidate':
a=self.need('release:candidate')
@@ -981,17 +974,6 @@ class H(BaseHTTPRequestHandler):
return self.sendj(
silent_release_service().submit_owner_approval(data)
)
if path == "/reopenOwnerApproval":
a = self.need("release:approve")
if not a:
return
from release_approval_reopen import ApprovalReopenError, reopen_owner_approval
try:
result = reopen_owner_approval(silent_release_service(), data)
except ApprovalReopenError as exc:
return self.sendj({"error": exc.code, "message": str(exc)}, 409)
return self.sendj(result)
if path == '/startDeployment':
a=self.need('release:deploy')
if not a: return
@@ -1254,25 +1236,12 @@ return{vs,cmds,ar,cmd,cdet,tot,sok,sfail,runs,baseline,refreshMsg,loadCmd,selVer
commands.append(f.name)
command_detail=None
if cmd_filter:
actor = self.actor()
if not actor['ok']:
return self.sendj({'error':'unauthorized','hint':'Bearer token required'},401)
cmd = str(cmd_filter or '')
if not _COMMAND_FILENAME_RE.fullmatch(cmd) or '..' in cmd:
return self.sendj({'command':cmd_filter,'error':'unsafe_command'},400)
for d in cmd_dirs:
if not d.exists():
continue
base = d.resolve()
fp = (base / cmd).resolve()
# Strict containment: must resolve directly inside one of the
# allowed command directories.
if fp.parent != base or not fp.is_file():
continue
if fp.stat().st_size > MAX_COMMAND_READ_BYTES:
return self.sendj({'command':cmd_filter,'error':'command_too_large'},413)
command_detail={'command':cmd_filter,'content':fp.read_text(encoding='utf-8')}
break
if d.exists():
fp=d/cmd_filter
if fp.exists():
command_detail={'command':cmd_filter,'content':fp.read_text(encoding='utf-8')}
break
if not command_detail:
command_detail={'command':cmd_filter,'error':'not_found'}
return self.sendj(command_detail)
@@ -1307,7 +1276,6 @@ def api_spec():
def main():
require_isolated_dev_profile(HOST)
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
if __name__ == '__main__': main()
@@ -1,446 +0,0 @@
"""aios_owner_based_executor — Owner 基于授权的可执行 CLI 入口。
把 "owner 基于授权 → Agent 自主完成受治理写链" 从测试/装配层提升为**可直接执行的命令**。
集成 V2R2C3 现场组合根 `field_governed_writer.py`,装配到生产入口
`GovernedWriterAutomation.acquire_verified()`。
子命令:
register <authorization_ref> 登记一条 Owner 授权到 authority(可 --policy-json 指定,
默认用内置参考授权物 azn-owner-gw-v1)。
acquire <authorization_ref> 装配 FieldGovernedWriterFactory,运行 acquire_verified(),
输出 GovernedWriterSession JSON。
行为契约:
- 生产入口唯一授权来源是 authority 已登记的 authorization_ref;未登记即 fail-closed。
- authority 未装配(--no-authority 或构造时 owner_authority=None)→ AuthorizationDenied。
- EXPIRED 任务无预签 recovery → OwnerActionRequired(OWNER_BOUNDARY,非自动恢复)。
- status==error 输出到 stderr,退出码非 0;成功输出会话 JSON,退出码 0。
- legacy_global_writes_enabled 默认 false = 安全不变式,不是 blocker。
用法示例:
python aios_owner_based_executor.py register azn-owner-gw-v1
python aios_owner_based_executor.py acquire azn-owner-gw-v1
python aios_owner_based_executor.py acquire azn-owner-gw-v1 --branch-mode continue --head-oid <oid>
python aios_owner_based_executor.py acquire azn-owner-gw-v1 --task-status EXPIRED # OwnerBoundary
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import tempfile
import time
from pathlib import Path
from app.task_plane.field_governed_writer import (
FieldGovernedWriterFactory,
FieldOwnerAuthorizationAuthority,
FieldRuntimeStatus,
)
from app.task_plane.governance_control_kernel import (
ActionLedger,
ConsistencyBarrier,
ManifestStore,
DiscoveryObservation,
)
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
GovernedWriterRequest,
GovernedWriterSession,
OwnerActionRequired,
OwnerAuthorizationPolicy,
TaskState,
)
# 版本与示例授权引用
VERSION = "V2R2C3"
DEFAULT_AZN_REF = "azn-owner-gw-v1"
_AZN_CAPS = frozenset(
{
"code.write",
"governed.write.branch",
"governed.write.file",
"test.execute",
"writer.lease",
"task.principal.issue",
}
)
_AZN_OPS = frozenset(
{
"claim",
"epoch_reissue",
"governed_commit",
"heartbeat",
"job_reconcile",
"readback",
"reconcile",
"successor_recovery",
"task_lease",
"task_principal",
"writer_lease",
}
)
# ---------------------------------------------------------------------------
# 授权物 / 请求构造
# ---------------------------------------------------------------------------
def reference_policy(*, expires_at: float | None = None) -> OwnerAuthorizationPolicy:
"""内置参考授权物 azn-owner-gw-v1(现场登记时 Owner 设定当下有效 expires_at)。"""
if expires_at is None:
expires_at = time.time() + 30 * 86400.0
return OwnerAuthorizationPolicy(
authorization_id=DEFAULT_AZN_REF,
subject="aios-autonomous-admin",
repository="aios/aios",
allowed_branch_prefixes=("agent/autonomy-",),
allowed_capabilities=_AZN_CAPS,
risk_ceiling="medium",
trust_domain="aios.owner",
revocation_epoch=1,
not_before=0.0,
expires_at=expires_at,
revoked=False,
allowed_operations=_AZN_OPS,
)
def _default_subject_repo_branch(task: str) -> tuple[str, str, str]:
return ("aios-autonomous-admin", "aios/aios", f"agent/autonomy-{task}")
def build_request(
*,
task_id: str,
manifest_id: str,
runtime_fingerprint: str,
process_start_identity: str,
plan_digest: str,
base_oid: str,
generation: int,
risk: str,
caps: frozenset[str],
files: tuple[str, ...],
overrides: dict | None = None,
) -> GovernedWriterRequest:
subject, repo, branch = _default_subject_repo_branch(task_id)
body = dict(
subject=subject,
task_id=task_id,
repository=repo,
target_branch=branch,
risk=risk,
generation=generation,
base_oid=base_oid,
required_capabilities=caps,
file_allowlist=files,
plan_digest=plan_digest,
manifest_id=manifest_id,
runtime_fingerprint=runtime_fingerprint,
process_start_identity=process_start_identity,
)
if overrides:
body.update(overrides)
return GovernedWriterRequest(**body)
# ---------------------------------------------------------------------------
# 依据配置构造 authority(含 --no-authority fail-closed 场景)
# ---------------------------------------------------------------------------
def build_authority(
*, policy_json_path: str | None, use_reference: bool, expires_at: float | None
) -> FieldOwnerAuthorizationAuthority | None:
if policy_json_path:
data = json.loads(Path(policy_json_path).read_text(encoding="utf-8"))
pol = OwnerAuthorizationPolicy(**data["policy"])
auth = FieldOwnerAuthorizationAuthority()
auth.register(data["authorization_ref"], pol)
return auth
if use_reference:
auth = FieldOwnerAuthorizationAuthority()
auth.register(DEFAULT_AZN_REF, reference_policy(expires_at=expires_at))
return auth
return None # authority 未装配 -> acquire_verified 直接 fail-closed
# ---------------------------------------------------------------------------
# 子命令实现
# ---------------------------------------------------------------------------
def cmd_register(args: argparse.Namespace) -> int:
expires_at = float(args.expires_at) if args.expires_at else None
if args.policy_json:
data = json.loads(Path(args.policy_json).read_text(encoding="utf-8"))
pol = OwnerAuthorizationPolicy(**data["policy"])
ref = data.get("authorization_ref", pol.authorization_id)
else:
pol = reference_policy(expires_at=expires_at)
ref = DEFAULT_AZN_REF
auth = FieldOwnerAuthorizationAuthority()
auth.register(ref, pol)
print(
json.dumps(
{
"status": "registered",
"authorization_ref": ref,
"policy_fingerprint": pol.policy_fingerprint,
"subject": pol.subject,
"repository": pol.repository,
"allowed_branch_prefixes": list(pol.allowed_branch_prefixes),
"risk_ceiling": pol.risk_ceiling,
"expires_at": pol.expires_at,
"note": "registered in-memory authority; field authority must persist",
},
ensure_ascii=False,
indent=2,
)
)
return 0
def cmd_acquire(args: argparse.Namespace) -> int:
from app.task_plane.governance_control_kernel import StaleManifest
# 使用独立工作目录构造 manifest + ledger,避免污染运行目录
workdir = Path(args.workdir) if args.workdir else None
if workdir is None:
workdir = Path(tempfile.mkdtemp(prefix="gw_exec_"))
store = ManifestStore(str(workdir / "manifest.db"))
ledger = ActionLedger(str(workdir / "ledger.db"))
barrier = ConsistencyBarrier(["W1"])
barrier.submit(
DiscoveryObservation.build(
worker="W1",
runtime_fingerprint=args.runtime,
process_start_identity=args.process_start,
activation_digest="activation-exec",
epoch_id="epoch-exec-prov",
policy_digest="policy-exec",
plan_provider_root="/plans/exec",
task_id=args.task,
grant_id="grant-exec",
facts={},
evidence_refs=(),
observed_at=100.0,
)
)
manifest_id = store.freeze(barrier.freeze())
auth = build_authority(
policy_json_path=args.policy_json,
use_reference=args.reference,
expires_at=float(args.expires_at) if args.expires_at else None,
)
task_status = args.task_status.upper()
task = TaskState(task_id=args.task, status=task_status, revision=1)
if args.branch_mode == "create":
branch_exists, head_oid = False, None
elif args.branch_mode == "continue":
branch_exists, head_oid = True, args.head_oid
else: # auto: 由 read 决定(默认新分支)
branch_exists, head_oid = (True, args.head_oid) if args.head_oid else (False, None)
runtime_status = FieldRuntimeStatus(
legacy_global_writes_enabled=args.legacy_global,
task_principal_writes_enabled=args.principal_writes,
task_principal_verifier_ready=args.verifier_ready,
governed_write_blockers=tuple(args.blocker or ()),
)
# fail-closed authority 场景:--no-authority 显式不装配
if args.no_authority:
auth = None
adapters = _make_adapters(
authority=auth,
manifest_store=store,
action_ledger=ledger,
task=task,
branch_exists=branch_exists,
head_oid=head_oid,
repo=args.repo if args.repo else "aios/aios",
branch=args.branch if args.branch else f"agent/autonomy-{args.task}",
generation=args.generation,
runtime_status=runtime_status,
)
if auth is None:
adapters.owner_authority = None
automation = _automation(store, ledger)
request = build_request(
task_id=args.task,
manifest_id=manifest_id,
runtime_fingerprint=args.runtime,
process_start_identity=args.process_start,
plan_digest=args.plan_digest,
base_oid=args.base_oid,
generation=args.generation,
risk=args.risk,
caps=set(_AZN_CAPS),
files=tuple(args.files),
)
try:
session = automation.acquire_verified(
request=request,
authorization_ref=args.authorization_ref,
readiness=runtime_status_to_readiness(runtime_status),
epoch=None,
adapters=adapters,
now=time.time(),
)
print(json.dumps({"status": "ok", "version": VERSION, "session": session.to_dict()},
ensure_ascii=False, indent=2))
return 0
except OwnerActionRequired as exc:
print(json.dumps({"status": "owner_boundary", "version": VERSION, "reason": str(exc)},
ensure_ascii=False, indent=2), file=sys.stderr)
return 3
except AuthorizationDenied as exc:
print(json.dumps({"status": "auth_denied", "version": VERSION, "reason": str(exc)},
ensure_ascii=False, indent=2), file=sys.stderr)
return 2
except Exception as exc: # noqa: BLE001 - CLI 边界统一收口
print(json.dumps({"status": "error", "version": VERSION,
"error": f"{type(exc).__name__}: {exc}"},
ensure_ascii=False, indent=2), file=sys.stderr)
return 1
finally:
_cleanup_workdir(workdir)
def _make_adapters(
*,
authority,
manifest_store,
action_ledger,
task,
branch_exists,
head_oid,
repo,
branch,
generation,
runtime_status,
):
from app.task_plane.field_governed_writer import _DefaultExecutionPlane, _DefaultGit, \
_DefaultJobs, _DefaultPrincipalIssuer, _DefaultTrustedSigner, _DefaultWriterLease
from app.task_plane.governed_writer_automation import GovernedWriterAdapters
from app.task_plane.governed_writer_automation import GovernedWriteReadiness
return GovernedWriterAdapters(
signer=_DefaultTrustedSigner(),
execution=_DefaultExecutionPlane(task),
principal_issuer=_DefaultPrincipalIssuer(),
jobs=_DefaultJobs(repository=repo, target_branch=branch, generation=generation),
git=_DefaultGit(exists=branch_exists, head_oid=head_oid),
writer_lease=_DefaultWriterLease(),
owner_authority=authority,
)
def runtime_status_to_readiness(rs: FieldRuntimeStatus):
from app.task_plane.governed_writer_automation import GovernedWriteReadiness
return GovernedWriteReadiness(
legacy_global_writes_enabled=rs.legacy_global_writes_enabled,
task_principal_writes_enabled=rs.task_principal_writes_enabled,
task_principal_verifier_ready=rs.task_principal_verifier_ready,
governed_write_blockers=tuple(rs.governed_write_blockers),
)
def _automation(store, ledger):
from app.task_plane.governed_writer_automation import GovernedWriterAutomation
return GovernedWriterAutomation(manifest_store=store, action_ledger=ledger)
def _cleanup_workdir(workdir: Path) -> None:
import gc
import shutil
try:
gc.collect()
shutil.rmtree(workdir, ignore_errors=True)
except Exception: # pragma: no cover - 清理失败不影响结果
pass
# ---------------------------------------------------------------------------
# 参数与入口
# ---------------------------------------------------------------------------
def build_parser() -> argparse.ArgumentParser:
import tempfile # noqa: F401 (确保模块可见)
parser = argparse.ArgumentParser(
description="AIOS Owner 基于授权执行器 (V2R2C3)",
prog="aios_owner_based_executor",
)
sub = parser.add_subparsers(dest="command", required=True)
p_register = sub.add_parser("register", help="登记 Owner 授权到 authority")
p_register.add_argument("authorization_ref", nargs="?", default=DEFAULT_AZN_REF)
p_register.add_argument("--policy-json", help="policy JSON 文件(含 authorization_ref + policy)")
p_register.add_argument("--expires-at", help="override expires_at (unix)")
p_register.set_defaults(func=cmd_register)
p_acquire = sub.add_parser("acquire", help="运行 acquire_verified() 完成受治理写链")
p_acquire.add_argument("authorization_ref", nargs="?", default=DEFAULT_AZN_REF)
p_acquire.add_argument("--no-authority", action="store_true",
help="显式不装配 authority -> fail-closed")
p_acquire.add_argument("--policy-json", help="从文件装载授权物(替代内置参考)")
p_acquire.add_argument("--reference", action="store_true",
help="使用内置参考授权物 azn-owner-gw-v1")
p_acquire.add_argument("--expires-at", help="override expires_at (unix)")
p_acquire.add_argument("--task", default="task-gw-exec-0001")
p_acquire.add_argument("--task-status", default="QUEUED",
choices=["QUEUED", "CLAIMED", "RUNNING", "EXPIRED"])
p_acquire.add_argument("--branch-mode", default="create", choices=["create", "continue", "auto"])
p_acquire.add_argument("--head-oid", help="继续分支的 live HEAD oid")
p_acquire.add_argument("--base-oid", default="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3")
p_acquire.add_argument("--repo", help="仓库(默认 aios/aios)")
p_acquire.add_argument("--branch", help="目标分支(默认 agent/autonomy-<task>)")
p_acquire.add_argument("--generation", type=int, default=1)
p_acquire.add_argument("--risk", default="medium")
p_acquire.add_argument("--runtime", default="runtime-gw-exec")
p_acquire.add_argument("--process-start", default="pid:gw:exec:1")
p_acquire.add_argument("--plan-digest", default="sha256:plan-exec")
p_acquire.add_argument("--files", nargs="*", default=[
"active-runtime/app/task_plane/governance_control_kernel.py",
"active-runtime/app/task_plane/governed_writer_automation.py",
"active-runtime/app/task_plane/field_governed_writer.py",
])
p_acquire.add_argument("--workdir", help="manifest/ledger 工作目录(默认临时)")
# 运行时状态(W-RUNTIME 只读线)
p_acquire.add_argument("--legacy-global", action="store_true",
help="legacy_global_writes_enabled(默认 false=不变式)")
p_acquire.add_argument("--principal-writes", action="store_true", default=True,
help="task_principal_writes_enabled(默认 true)")
p_acquire.add_argument("--verifier-ready", action="store_true", default=True,
help="task_principal_verifier_ready(默认 true)")
p_acquire.add_argument("--blocker", action="append",
help="governed_write_blockers 追加")
p_acquire.set_defaults(func=cmd_acquire)
parser.add_argument("--version", action="version", version=VERSION)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
return int(args.func(args))
if __name__ == "__main__":
import tempfile # noqa: F401
try:
raise SystemExit(main())
except KeyboardInterrupt:
raise SystemExit(130)
@@ -51,30 +51,15 @@ AUTHORIZATION_BUNDLE_SCHEMA = "aios.owner-authorization-bundle.v1"
MACHINE_CAPABILITY_SCHEMA = "aios.machine-derived-capability.v1"
BOOTSTRAP_STATE_SCHEMA = "aios.single-host-bootstrap-state.v1"
OWNER_EPOCH_SCHEMA = "aios.owner-delegation-epoch.v2"
SOURCE_PUBLICATION_POLICY_SCHEMA = "aios.source-publication-policy.v1"
SOURCE_PUBLICATION_POLICY_ID = "C5_SOURCE_PUBLICATION_ONLY"
SOURCE_PUBLICATION_CAPABILITIES = frozenset(
{
"agent-task:control",
"governed.task.create",
"governed.write.files",
"task.principal.issue",
"writer.lease",
}
)
DEFAULT_REGISTRY_PATH = Path("/etc/aios/single-host-signer-registry.json")
DEFAULT_OWNER_SIGNER_ID = "local-owner-authority"
DEFAULT_MACHINE_SIGNER_ID = "machine-operational"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_CAP = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$")
_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/-]{2,255}$")
# ``engineering`` is deliberately between medium and high. Adding this rank
# does not raise any configured ceiling; existing policy files keep their
# existing value (the normal local-owner policy is ``medium``).
_RISK = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
_RISK = {"low": 0, "medium": 1, "high": 2}
_MAX_JSON = 128 * 1024
_MAX_KEY = 64 * 1024
_MAGIC = b"SSHSIG"
@@ -706,30 +691,6 @@ class OwnerAuthorizationBundle:
return asdict(self)
@dataclass(frozen=True, slots=True)
class SourcePublicationPolicyRequest:
"""One exact, source-only unattended publication authorization request."""
policy_id: str
repository: str
authority_base_oid: str
source_oid: str
source_tree_oid: str
target_agent: str
target_branch: str
generation: int
execution_digest: str
exact_files: tuple[str, ...]
nonce: str
ttl_seconds: int
risk: str = "engineering"
production_impact: str = "none"
renewal: str = "none"
def __post_init__(self) -> None:
object.__setattr__(self, "exact_files", tuple(self.exact_files))
@dataclass(frozen=True, slots=True)
class MachineCapabilityRequest:
capability_id: str
@@ -794,28 +755,20 @@ class LocalOwnerAuthority(LocalFileSigner):
def __init__(
self,
key_path: Path | None,
key_path: Path,
policy: SingleHostOwnerPolicy,
*,
require_root_owner: bool = True,
clock: Any = time.time,
registered_signer: Any | None = None,
) -> None:
super().__init__(
key_path or Path("/dev/null"),
key_path,
signer_id=DEFAULT_OWNER_SIGNER_ID,
namespace=OWNER_NAMESPACE,
require_root_owner=require_root_owner,
)
self.policy = policy
self.clock = clock
self.registered_signer = registered_signer
@property
def fingerprint(self) -> str:
if self.registered_signer is not None:
return str(self.registered_signer.fingerprint)
return super().fingerprint
@staticmethod
def _prefix_narrows(requested: str, allowed: Sequence[str]) -> bool:
@@ -878,111 +831,6 @@ class LocalOwnerAuthority(LocalFileSigner):
value["canonical_payload_digest"] = sha256_json(value)
return self._sign_canonical(value)
def _sign_canonical(self, value: Mapping[str, Any]) -> SignedArtifact:
if self.registered_signer is None:
return super()._sign_canonical(value)
payload = canonical_json_bytes(dict(value))
signature = self.registered_signer._sign_canonical_payload(
self.namespace, payload
)
return SignedArtifact(
payload=dict(value),
canonical_payload=payload,
signature=signature,
key_id=self.fingerprint,
namespace=self.namespace,
)
def issue_source_publication_policy(
self, request: SourcePublicationPolicyRequest
) -> SignedArtifact:
"""Sign one exact C5 source-publication policy.
This is an issuance operation only. It cannot change the configured
owner policy, its ceiling, branch/file allowlists, or trust domain.
"""
now = int(self.clock())
files = tuple(request.exact_files)
def source_scope_narrows(value: str, allowed: Sequence[str]) -> bool:
base = value.rstrip("/")
return any(
base == prefix.rstrip("/")
or base.startswith(prefix.rstrip("/") + "/")
for prefix in allowed
)
if (
request.policy_id != SOURCE_PUBLICATION_POLICY_ID
or request.repository != self.policy.repository
or not _HEX40.fullmatch(request.authority_base_oid)
or not _HEX40.fullmatch(request.source_oid)
or not _HEX40.fullmatch(request.source_tree_oid)
or not _ID.fullmatch(request.target_agent)
or not request.target_agent.startswith("agent/")
or not source_scope_narrows(
request.target_branch, self.policy.allowed_branch_prefixes
)
or not request.target_branch.startswith("agent/")
or "\\" in request.target_branch
or ".." in PurePosixPath(request.target_branch).parts
or request.target_branch.endswith(("/", "."))
or type(request.generation) is not int
or request.generation < 1
or not _HEX64.fullmatch(request.execution_digest)
or not files
or any(
not item
or PurePosixPath(item).is_absolute()
or "\\" in item
or ".." in PurePosixPath(item).parts
or item.startswith(".git")
or not source_scope_narrows(item, self.policy.allowed_file_prefixes)
for item in files
)
or not SOURCE_PUBLICATION_CAPABILITIES.issubset(self.policy.allowed_capabilities)
or len(set(files)) != len(files)
or not _ID.fullmatch(request.nonce)
or request.risk != "engineering"
or request.risk not in _RISK
or _RISK[request.risk] > _RISK[self.policy.risk_ceiling]
or request.production_impact != "none"
or request.renewal != "none"
or type(request.ttl_seconds) is not int
or not 1 <= request.ttl_seconds <= 3600
or request.ttl_seconds > self.policy.maximum_epoch_ttl_seconds
):
_fail("SOURCE_PUBLICATION_REQUEST_WIDENS_POLICY")
value: dict[str, Any] = {
"schema": SOURCE_PUBLICATION_POLICY_SCHEMA,
"policy_id": SOURCE_PUBLICATION_POLICY_ID,
"owner_identity": OWNER_IDENTITY,
"repository": request.repository,
"trust_domain": self.policy.trust_domain,
"policy_digest": self.policy.digest,
"authority_base_oid": request.authority_base_oid,
"source_oid": request.source_oid,
"source_tree_oid": request.source_tree_oid,
"target_agent": request.target_agent,
"target_branch": request.target_branch,
"generation": request.generation,
"execution_digest": request.execution_digest,
"exact_files": list(files),
"capabilities": sorted(SOURCE_PUBLICATION_CAPABILITIES),
"risk": "engineering",
"production_impact": "none",
"renewal": "none",
"nonce": request.nonce,
"issued_at": now,
"not_before": max(0, now - 30),
"expires_at": now + request.ttl_seconds,
"status": "ACTIVE",
"signature_namespace": OWNER_NAMESPACE,
"signature_key_id": self.fingerprint,
}
value["canonical_payload_digest"] = sha256_json(value)
return self._sign_canonical(value)
issue_source_only_policy = issue_source_publication_policy
def issue_authorization_bundle(
self,
*,
@@ -35,8 +35,6 @@ 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_EPOCH_V2 = "aios.owner-delegation-epoch.v2"
SUPPORTED_EPOCH_SCHEMAS = frozenset({SCHEMA_EPOCH, SCHEMA_EPOCH_V2})
SCHEMA_RECEIPT = "aios.task-admission-receipt.v1"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
@@ -144,30 +142,9 @@ class OwnerDelegationEpoch:
revocation_epoch: int
policy_digest: str
schema: str = SCHEMA_EPOCH
production_scope: str | None = None
destructive_policy: str | None = None
# Provenance retained by the consumer. These fields are populated by the
# signed-directory provider for v2 (and left at their v1-compatible
# defaults when reading legacy epochs); they are never used to translate or
# re-sign the original artifact.
status: str = ""
signature_namespace: str = ""
signature_key_id: str = ""
canonical_payload_digest: str = ""
canonical_payload: bytes = b""
@property
def issuer(self) -> str:
"""The signed epoch issuer, retained under the legacy identity name."""
return self.owner_identity
@property
def canonical_payload_bytes(self) -> bytes:
return self.canonical_payload
def as_public_dict(self) -> dict[str, Any]:
value = asdict(self)
value.pop("canonical_payload", None)
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)
@@ -332,7 +309,7 @@ def _normalize_capabilities(
def _risk_rank(value: str) -> int:
ranks = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
ranks = {"low": 0, "medium": 1, "high": 2}
try:
return ranks[value]
except KeyError:
@@ -861,7 +838,7 @@ class TaskAdmissionBootstrapService:
now: int,
) -> OwnerDelegationEpoch:
epoch = self.authorization.verify_delegation_epoch(epoch_id)
if epoch.schema not in SUPPORTED_EPOCH_SCHEMAS:
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)
@@ -952,23 +929,10 @@ class TaskAdmissionBootstrapService:
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,
# V2R2C2 TTL contract: the admission envelope is a *signing
# window* only. Once authorization succeeds (epoch verified or
# Owner signature verified) the envelope is consumed, and the
# derived grant's lifecycle is bounded by the task/grant policy
# ceiling (max_task_ttl_seconds) and, for a delegation epoch,
# by the parent epoch's own expiry. It must NOT be truncated by
# the already-consumed envelope's remaining window, otherwise a
# nominal 3600s policy collapses to the ~900s envelope window
# (dual-semantic TTL defect). The envelope's temporal validity
# still gates authorization (now < envelope.expires_at above).
expires_at=(
now + self.policy.max_task_ttl_seconds
if parent_epoch is None
else min(
now + self.policy.max_task_ttl_seconds,
parent_epoch.expires_at,
)
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(
@@ -11,12 +11,10 @@ import base64
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import hmac
import importlib.util
import json
import os
import re
import sqlite3
from pathlib import Path
import stat
import time
@@ -26,39 +24,15 @@ import urllib.request
from aios_task_admission_bootstrap import (
OwnerDelegationEpoch,
SCHEMA_EPOCH,
SCHEMA_EPOCH_V2,
TaskAdmissionError,
TaskAdmissionGrant,
canonical_json_bytes,
sha256_json,
)
from aios_gitops_job_controller import (
GitOpsJobController as _FLGitOpsJobController,
JobState as _FLJobState,
sha256_json as _fl_job_sha256_json,
)
from aios_gitops_job_identity import (
legacy_task_storage_key as _fl_legacy_task_storage_key,
task_storage_key as _fl_task_storage_key,
)
OWNER_NAMESPACE = "aios-owner-approval-v1"
OWNER_SIGNER_IDENTITY = "aios-task-grant-signer"
_EPOCH_V1_FIELDS = frozenset({
"epoch_id", "owner_identity", "trust_domain", "repository",
"allowed_branch_prefixes", "allowed_capabilities", "allowed_file_prefixes",
"risk_ceiling", "issued_at", "not_before", "expires_at",
"revocation_epoch", "policy_digest", "schema",
})
_EPOCH_V2_FIELDS = frozenset({
*_EPOCH_V1_FIELDS,
"business_goal", "production_scope", "destructive_policy",
"signature_namespace", "signature_key_id", "canonical_payload_digest",
"status",
})
_SSH_FINGERPRINT_RE = re.compile(r"^SHA256:[A-Za-z0-9+/]{43}$")
class OfficialTaskTransport(Protocol):
@@ -478,7 +452,6 @@ class TaskAdmissionPrincipalProvider:
class TaskAdmissionRuntimeBundle:
handlers: Mapping[str, Any]
principal_verifier: TaskAdmissionPrincipalProvider
service: Any | None = None
class TaskAdmissionHostError(RuntimeError):
@@ -638,15 +611,11 @@ class SignedEpochDirectoryProvider:
"TASK_ADMISSION_EPOCH_CANONICAL_MISMATCH",
403,
)
schema = value.get("schema")
if schema not in {SCHEMA_EPOCH, SCHEMA_EPOCH_V2}:
if value.get("schema") != "aios.owner-delegation-epoch.v1":
raise TaskAdmissionError(
"TASK_ADMISSION_EPOCH_SCHEMA_INVALID",
403,
)
expected_fields = _EPOCH_V1_FIELDS if schema == SCHEMA_EPOCH else _EPOCH_V2_FIELDS
if set(value) != expected_fields:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_FIELDS_INVALID", 403)
if value.get("epoch_id") != epoch_id:
raise TaskAdmissionError(
"TASK_ADMISSION_EPOCH_ID_MISMATCH",
@@ -662,34 +631,7 @@ class SignedEpochDirectoryProvider:
if epoch_id in self._revoked_ids():
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REVOKED", 403)
# v2 is a producer-authored envelope. Validate its self-digest and
# signature binding before the cryptographic verifier sees the exact
# bytes from disk. The digest is over the payload with only the digest
# member removed; no schema translation is permitted.
if schema == SCHEMA_EPOCH_V2:
if (
value.get("status") != "ACTIVE"
or not isinstance(value.get("business_goal"), str)
or not value["business_goal"]
or not isinstance(value.get("production_scope"), str)
or not value["production_scope"]
or not isinstance(value.get("destructive_policy"), str)
or value["destructive_policy"] not in {"forbidden", "rollback-only"}
or value.get("signature_namespace") != OWNER_NAMESPACE
or not isinstance(value.get("signature_key_id"), str)
or not _SSH_FINGERPRINT_RE.fullmatch(value["signature_key_id"])
or not isinstance(value.get("canonical_payload_digest"), str)
or not re.fullmatch(r"[0-9a-f]{64}", value["canonical_payload_digest"])
):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_V2_BINDING_INVALID", 403)
digest_value = dict(value)
supplied_digest = digest_value.pop("canonical_payload_digest", None)
if sha256_json(digest_value) != supplied_digest:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_PAYLOAD_DIGEST_INVALID", 403)
key_id = key_id_file.read_text(encoding="utf-8").strip()
if schema == SCHEMA_EPOCH_V2 and key_id != value["signature_key_id"]:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_KEY_BINDING_INVALID", 403)
armored = signature.read_text(encoding="utf-8")
if self.verifier.verify(
namespace=OWNER_NAMESPACE,
@@ -706,23 +648,6 @@ class SignedEpochDirectoryProvider:
not_before = value.get("not_before")
expires_at = value.get("expires_at")
if (
not isinstance(value.get("epoch_id"), str)
or not value["epoch_id"]
or not isinstance(value.get("owner_identity"), str)
or not value["owner_identity"]
or not isinstance(value.get("trust_domain"), str)
or not value["trust_domain"]
or not isinstance(value.get("repository"), str)
or not value["repository"]
or not isinstance(value.get("risk_ceiling"), str)
or value["risk_ceiling"] not in {"low", "medium", "engineering", "high"}
or not isinstance(value.get("issued_at"), int)
or isinstance(value.get("issued_at"), bool)
or not isinstance(value.get("revocation_epoch"), int)
or isinstance(value.get("revocation_epoch"), bool)
or not isinstance(value.get("policy_digest"), str)
or not value["policy_digest"]
or
not isinstance(not_before, int)
or isinstance(not_before, bool)
or not isinstance(expires_at, int)
@@ -736,16 +661,13 @@ class SignedEpochDirectoryProvider:
files = value.get("allowed_file_prefixes")
if (
not isinstance(branches, list)
or not branches
or not all(isinstance(item, str) and item for item in branches)
or not isinstance(capabilities, list)
or not capabilities
or not all(
isinstance(item, str) and item
for item in capabilities
)
or not isinstance(files, list)
or not files
or not all(isinstance(item, str) and item for item in files)
):
raise TaskAdmissionError(
@@ -768,19 +690,6 @@ class SignedEpochDirectoryProvider:
revocation_epoch=int(value["revocation_epoch"]),
policy_digest=str(value["policy_digest"]),
schema=str(value["schema"]),
production_scope=(
str(value["production_scope"])
if schema == SCHEMA_EPOCH_V2 else None
),
destructive_policy=(
str(value["destructive_policy"])
if schema == SCHEMA_EPOCH_V2 else None
),
status=str(value.get("status", "")),
signature_namespace=str(value.get("signature_namespace", "")),
signature_key_id=str(value.get("signature_key_id", "")),
canonical_payload_digest=str(value.get("canonical_payload_digest", "")),
canonical_payload=payload,
)
@@ -995,7 +904,6 @@ def _build_task_admission_runtime(
task_port=task_port,
epoch_id=epoch.epoch_id,
),
service=service,
)
@@ -1161,8 +1069,6 @@ PLAN_FIELDS_V3=frozenset({
_SHA256=re.compile(r"^[0-9a-f]{64}$")
_OID=re.compile(r"^[0-9a-f]{40}$")
_JOB_ID=re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$")
_TASK_ID=re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
_TASK_BRANCH=re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class ProducerReceiptError(RuntimeError):
@@ -1224,38 +1130,6 @@ def require_target_grant(grant:object)->None:
fail("TASK_ADMISSION_EXECUTION_DIGEST_INVALID")
def require_reservable_grant(grant:object)->None:
"""Validate the authenticated, persisted grant accepted by the producer.
The historical target keeps its exact tuple. Every other target is driven
by the grant persisted by TaskAdmissionBootstrap; no Mission metadata or
caller-supplied execution identity is consulted here.
"""
task_id=_gv(grant,"task_id")
if task_id==TARGET_TASK_ID:
require_target_grant(grant)
return
if not isinstance(task_id,str) or not _TASK_ID.fullmatch(task_id):
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_TASK_INVALID")
if _gv(grant,"repository")!=TARGET_REPOSITORY:
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_REPOSITORY_INVALID")
base=_gv(grant,"authority_base_oid")
if not isinstance(base,str) or not _OID.fullmatch(base):
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_BASE_INVALID")
branch=_gv(grant,"target_branch")
if not isinstance(branch,str) or not _TASK_BRANCH.fullmatch(branch):
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_BRANCH_INVALID")
generation=_gv(grant,"generation")
if isinstance(generation,bool) or not isinstance(generation,int) or generation<1:
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_GENERATION_INVALID")
digest=_gv(grant,"execution_digest")
if not isinstance(digest,str) or not _SHA256.fullmatch(digest):
fail("TASK_ADMISSION_EXECUTION_DIGEST_INVALID")
grant_id=_gv(grant,"grant_id")
if not isinstance(grant_id,str) or not grant_id:
fail("TASK_ADMISSION_POST_OWNER_RESERVATION_GRANT_INVALID")
def _safe_path(value:object)->str:
if not isinstance(value,str) or not value or "\x00" in value:
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_PATH_INVALID")
@@ -1278,7 +1152,7 @@ def _unique_strings(value:object,code:str)->list[str]:
def validate_v3_plan(*,grant:object,raw:bytes)->dict[str,Any]:
require_reservable_grant(grant)
require_target_grant(grant)
plan=strict_json_object(raw)
if set(plan)!=PLAN_FIELDS_V3:
fail("TASK_ADMISSION_EXECUTION_PLAN_FIELDS_INVALID")
@@ -1321,17 +1195,14 @@ def validate_v3_plan(*,grant:object,raw:bytes)->dict[str,Any]:
def load_v3_plan(root:Path,grant:object)->dict[str,Any]:
require_reservable_grant(grant)
require_target_grant(grant)
try:
rst=root.lstat()
except OSError:
fail("TASK_ADMISSION_EXECUTION_PLAN_UNAVAILABLE")
if (
stat.S_ISLNK(rst.st_mode) or not stat.S_ISDIR(rst.st_mode)
or (
_host_posix_metadata_enforced()
and (rst.st_uid!=0 or rst.st_mode&0o022)
)
or rst.st_uid!=0 or rst.st_mode&0o022
):
fail("TASK_ADMISSION_EXECUTION_PLAN_DIRECTORY_INVALID")
path=root/(str(_gv(grant,"execution_digest"))+".json")
@@ -1341,10 +1212,7 @@ def load_v3_plan(root:Path,grant:object)->dict[str,Any]:
fail("TASK_ADMISSION_EXECUTION_PLAN_UNAVAILABLE")
if (
stat.S_ISLNK(st.st_mode) or not stat.S_ISREG(st.st_mode)
or (
_host_posix_metadata_enforced()
and (st.st_uid!=0 or st.st_mode&0o022)
)
or st.st_uid!=0 or st.st_mode&0o022
):
fail("TASK_ADMISSION_EXECUTION_PLAN_FILE_INVALID")
try:
@@ -1355,30 +1223,25 @@ def load_v3_plan(root:Path,grant:object)->dict[str,Any]:
def reservation_idempotency_key(grant:object)->str:
require_reservable_grant(grant)
require_target_grant(grant)
material=(
str(_gv(grant,"grant_id"))+"\0"+str(_gv(grant,"execution_digest"))
).encode("utf-8")
return "task-admission-execution-reservation-"+hashlib.sha256(material).hexdigest()[:32]
def build_reservation_request(
grant:object,*,expected_branch_oid:str|None=None,
)->dict[str,Any]:
require_reservable_grant(grant)
request={
"task_id":str(_gv(grant,"task_id")),
"repository":str(_gv(grant,"repository")),
"base_oid":str(_gv(grant,"authority_base_oid")),
"target_branch":str(_gv(grant,"target_branch")),
"generation":int(_gv(grant,"generation")),
def build_reservation_request(grant:object)->dict[str,Any]:
require_target_grant(grant)
return {
"task_id":TARGET_TASK_ID,
"repository":TARGET_REPOSITORY,
"base_oid":TARGET_BASE_OID,
"target_branch":TARGET_BRANCH,
"generation":TARGET_GENERATION,
"risk":str(_gv(grant,"risk")),
"description":"Reserve one inert durable GitOps Job identity after exact Owner TaskAdmission authorization; no Agent Task claim or execution.",
"idempotency_key":reservation_idempotency_key(grant),
}
if expected_branch_oid is not None:
request["expected_branch_oid"]=expected_branch_oid
return request
def _mapping(value:object,code:str)->Mapping[str,Any]:
@@ -1393,22 +1256,21 @@ def validate_bootstrap_response(
request:Mapping[str,Any],
response:object,
expected_side_effect_state:str,
expected_branch_mode:str="CONTINUE_EXISTING",
)->str:
require_reservable_grant(grant)
require_target_grant(grant)
result=_mapping(response,"TASK_ADMISSION_EXECUTION_RESERVATION_RESPONSE_INVALID")
job_id=result.get("job_id")
if not isinstance(job_id,str) or not _JOB_ID.fullmatch(job_id):
fail("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID")
expected={
"task_id":_gv(grant,"task_id"),
"task_id":TARGET_TASK_ID,
"job_id":job_id,
"repository":_gv(grant,"repository"),
"base_oid":_gv(grant,"authority_base_oid"),
"expected_branch_oid":request.get("expected_branch_oid",_gv(grant,"authority_base_oid")),
"target_branch":_gv(grant,"target_branch"),
"branch_mode":expected_branch_mode,
"generation":_gv(grant,"generation"),
"repository":TARGET_REPOSITORY,
"base_oid":TARGET_BASE_OID,
"expected_branch_oid":TARGET_BASE_OID,
"target_branch":TARGET_BRANCH,
"branch_mode":"CONTINUE_EXISTING",
"generation":TARGET_GENERATION,
"state":"BOUND",
}
for key,value in expected.items():
@@ -1471,20 +1333,13 @@ def validate_bootstrap_replay(
first:object,
replay:object,
replay_side_effect_state:str,
first_side_effect_state:str="QUEUED",
expected_branch_mode:str="CONTINUE_EXISTING",
)->str:
if first_side_effect_state not in {"QUEUED","SAME_JOB"}:
fail("TASK_ADMISSION_EXECUTION_RESERVATION_PHASE_INVALID")
first_id=validate_bootstrap_response(
grant=grant,request=request,response=first,
expected_side_effect_state=first_side_effect_state,
expected_branch_mode=expected_branch_mode,
grant=grant,request=request,response=first,expected_side_effect_state="QUEUED"
)
replay_id=validate_bootstrap_response(
grant=grant,request=request,response=replay,
expected_side_effect_state=replay_side_effect_state,
expected_branch_mode=expected_branch_mode,
expected_side_effect_state=replay_side_effect_state
)
if first_id!=replay_id:
fail("TASK_ADMISSION_EXECUTION_RESERVATION_REPLAY_JOB_CONFLICT")
@@ -1500,12 +1355,6 @@ import inspect as _fl_inspect
_FL_HARDENING_PROVIDER = ExecutionPlanDirectoryProvider
_FL_BUILD_FROM_ACTIVATION = build_task_admission_handlers_from_activation_file
_FL_BUILD_FROM_ENVIRONMENT = globals().get("build_task_admission_handlers_from_environment")
_FL_BUILD_RUNTIME_FROM_ACTIVATION = globals().get(
"build_task_admission_runtime_from_activation_file"
)
_FL_BUILD_RUNTIME_FROM_ENVIRONMENT = globals().get(
"build_task_admission_runtime_from_environment"
)
_FL_APPROVED_BOOTSTRAP_SHA256="758895a8407568890fd2f8eb2f232a3dbf2cfa369e5f65ae8c6975bf8d01e3b5"
REPLAY_SIDE_EFFECT_STATE="SAME_JOB"
@@ -1546,428 +1395,38 @@ def _fl_assert_bootstrap_source(bootstrap:object)->None:
if _fl_sha(path)!=_FL_APPROVED_BOOTSTRAP_SHA256:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BOOTSTRAP_SOURCE_DRIFT",503)
def _fl_reservation_refs(runtime:object,grant:object)->tuple[str,str|None,str,str]:
require_reservable_grant(grant)
authority=getattr(runtime,"authority",None)
if authority is None or not callable(getattr(authority,"read_ref",None)):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
base=str(_gv(grant,"authority_base_oid"))
branch=str(_gv(grant,"target_branch"))
try:
main=authority.read_ref("refs/heads/main")
target=authority.read_ref("refs/heads/"+branch)
except Exception as exc:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_UNAVAILABLE",503) from exc
if main!=base:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_DRIFT",409)
if _gv(grant,"task_id")==TARGET_TASK_ID:
if target!=base:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_DRIFT",409)
return main,target,"CONTINUE_EXISTING",base
if target is None:
if _gv(grant,"generation")!=1:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_GENERATION_CONFLICT",409)
return main,target,"CREATE_NEW",base
if target==base:
return main,target,"CONTINUE_EXISTING",base
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_DRIFT",409)
def _fl_validate_durable_binding(
*,grant:object,request:Mapping[str,Any],durable:object,job_id:str,
expected_branch_mode:str,
)->None:
expected={
"task_id":_gv(grant,"task_id"),
"job_id":job_id,
"repository":_gv(grant,"repository"),
"base_oid":_gv(grant,"authority_base_oid"),
"expected_branch_oid":request.get("expected_branch_oid"),
"target_branch":_gv(grant,"target_branch"),
"branch_mode":expected_branch_mode,
"generation":_gv(grant,"generation"),
"risk":_gv(grant,"risk"),
"idempotency_key":request.get("idempotency_key"),
"description":request.get("description"),
}
if any(getattr(durable,field,None)!=value for field,value in expected.items()):
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
def _fl_existing_reservation_readback(
*,bootstrap:object,grant:object,request:Mapping[str,Any],durable:object,
expected_branch_mode:str,
)->dict[str,Any]:
"""Verify an existing producer reservation without opening a mutating store.
``GovernedTaskBootstrap._store`` initializes missing directories and SQLite
state, while ``create`` may submit a new Job. Neither is valid after a
durable task binding already exists: missing producer state is corruption,
not crash recovery. Read the exact Job and idempotency rows through a
read-only SQLite URI and return an explicitly typed readback proof. This
is deliberately not a producer submit receipt: Job lifecycle state may
have advanced since the reservation was first created.
"""
task_id=str(_gv(grant,"task_id"))
job_id=getattr(durable,"job_id",None)
if not isinstance(job_id,str) or not _JOB_ID.fullmatch(job_id):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID",409)
key=request.get("idempotency_key")
if not isinstance(key,str) or not key:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_IDEMPOTENCY_INVALID",409)
try:
expected_job_id=_FLGitOpsJobController.internal_job_id_for_idempotency_key(key)
producer_key=_fl_job_sha256_json({"client_idempotency_key":key})
except Exception as exc:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_IDEMPOTENCY_INVALID",409) from exc
if job_id!=expected_job_id or job_id!="job-"+producer_key[:32]:
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
job_root=getattr(bootstrap,"job_root",None)
if not isinstance(job_root,(str,os.PathLike)):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
job_root=Path(job_root)
store_root=job_root/_fl_task_storage_key(task_id)
legacy_root=job_root/_fl_legacy_task_storage_key(task_id)
try:
legacy_stat=None
try:
legacy_stat=legacy_root.lstat()
except FileNotFoundError:
pass
if legacy_root!=store_root and legacy_stat is not None:
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_CONFLICT",409)
store_stat=store_root.lstat()
db_path=store_root/"state.sqlite3"
db_stat=db_path.lstat()
except FileNotFoundError as exc:
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_MISSING",409) from exc
except OSError as exc:
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_READ_FAILED",503) from exc
if (
stat.S_ISLNK(store_stat.st_mode) or not stat.S_ISDIR(store_stat.st_mode)
or stat.S_ISLNK(db_stat.st_mode) or not stat.S_ISREG(db_stat.st_mode)
):
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_INVALID",409)
values={
field:str(request[field]) for field in (
"task_id","repository","base_oid","target_branch","risk",
"description","idempotency_key",
)
}
payload=json.dumps(values,sort_keys=True,separators=(",",":")).encode()
payload_digest=hashlib.sha256(payload).hexdigest()
expected_branch_oid=str(
request.get("expected_branch_oid") or values["base_oid"]
)
producer_binding={
"campaign_id":task_id,
"repository":values["repository"],
"operation":"GOVERNED_TASK_CREATE",
"branch":values["target_branch"],
"expected_branch_oid":expected_branch_oid,
"payload_digest":payload_digest,
"idempotency_key":key,
"task_id":task_id,
"lineage_anchor_oid":values["base_oid"],
}
expected_request={
**producer_binding,
"binding":producer_binding,
"payload_ref":"sha256/"+payload_digest,
"payload_size":len(payload),
}
expected_job={
"job_id":job_id,
"idempotency_key":producer_key,
"campaign_id":task_id,
"repository":values["repository"],
"operation":"GOVERNED_TASK_CREATE",
"branch":values["target_branch"],
"expected_branch_oid":expected_branch_oid,
"payload_digest":payload_digest,
"payload_ref":"sha256/"+payload_digest,
"payload_size":len(payload),
}
# mode=ro prevents schema/data creation while still including committed WAL
# rows. query_only supplies a second SQLite-level write prohibition.
try:
resolved_store=store_root.resolve(strict=True)
resolved_db=db_path.resolve(strict=True)
if resolved_db.parent!=resolved_store:
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_INVALID",409)
connection=sqlite3.connect(
resolved_db.as_uri()+"?mode=ro",
uri=True,timeout=5.0,
)
connection.row_factory=sqlite3.Row
try:
connection.execute("PRAGMA query_only=ON")
connection.execute("BEGIN")
job=connection.execute(
"SELECT job_id,idempotency_key,campaign_id,repository,operation,"
"branch,expected_branch_oid,payload_digest,payload_ref,payload_size,"
"state,side_effect_state,revision,request_json FROM jobs WHERE job_id=?",
(job_id,),
).fetchone()
idempotency=connection.execute(
"SELECT key,job_id,binding_json FROM idempotency_keys WHERE key=?",
(producer_key,),
).fetchone()
store_after=store_root.lstat()
db_after=db_path.lstat()
if (
(store_stat.st_dev,store_stat.st_ino)!=(store_after.st_dev,store_after.st_ino)
or (db_stat.st_dev,db_stat.st_ino)!=(db_after.st_dev,db_after.st_ino)
or stat.S_ISLNK(store_after.st_mode)
or not stat.S_ISDIR(store_after.st_mode)
or stat.S_ISLNK(db_after.st_mode)
or not stat.S_ISREG(db_after.st_mode)
):
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_CHANGED",409)
finally:
connection.close()
del connection
except (OSError,sqlite3.Error) as exc:
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STORE_READ_FAILED",503) from exc
if job is None:
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_MISSING",409)
if idempotency is None:
raise _fl_error("TASK_ADMISSION_PRODUCER_IDEMPOTENCY_MISSING",409)
if any(job[field]!=value for field,value in expected_job.items()):
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_CONFLICT",409)
valid_states={item.value for item in _FLJobState}
if (
job["state"] not in valid_states
or not isinstance(job["side_effect_state"],str)
or not job["side_effect_state"]
or not isinstance(job["revision"],int)
or isinstance(job["revision"],bool)
or job["revision"]<0
):
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_STATE_INVALID",409)
try:
recorded_request=json.loads(job["request_json"])
recorded_binding=json.loads(idempotency["binding_json"])
except (TypeError,ValueError) as exc:
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_CONFLICT",409) from exc
if (
recorded_request!=expected_request
or idempotency["key"]!=producer_key
or idempotency["job_id"]!=job_id
or recorded_binding!=producer_binding
):
raise _fl_error("TASK_ADMISSION_PRODUCER_JOB_CONFLICT",409)
payload_path=store_root/"payloads"/payload_digest
descriptor=None
try:
descriptor=os.open(
payload_path,
os.O_RDONLY|getattr(os,"O_NOFOLLOW",0),
)
payload_stat=os.fstat(descriptor)
with os.fdopen(descriptor,"rb",closefd=False) as stream:
payload_bytes=stream.read(len(payload)+1)
payload_after=payload_path.lstat()
except FileNotFoundError as exc:
raise _fl_error("TASK_ADMISSION_PRODUCER_PAYLOAD_MISSING",409) from exc
except OSError as exc:
raise _fl_error("TASK_ADMISSION_PRODUCER_PAYLOAD_READ_FAILED",503) from exc
finally:
if descriptor is not None:
os.close(descriptor)
if (
not stat.S_ISREG(payload_stat.st_mode)
or stat.S_ISLNK(payload_after.st_mode)
or not stat.S_ISREG(payload_after.st_mode)
or (payload_stat.st_dev,payload_stat.st_ino)!=(payload_after.st_dev,payload_after.st_ino)
or len(payload_bytes)!=len(payload)
or not hmac.compare_digest(hashlib.sha256(payload_bytes).hexdigest(),payload_digest)
or payload_bytes!=payload
):
raise _fl_error("TASK_ADMISSION_PRODUCER_PAYLOAD_CONFLICT",409)
response=dict(durable.response(idempotent=True))
response["reservation_readback"]={
"schema":"aios.governed-task-reservation-readback.v1",
"job_id":job_id,
"state":job["state"],
"side_effect_state":job["side_effect_state"],
"revision":job["revision"],
"producer_idempotency_key":producer_key,
"request_idempotency_key":key,
}
return response
def _fl_validate_reservation_readback(
*,grant:object,request:Mapping[str,Any],durable:object,
response:object,expected_branch_mode:str,
)->str:
if not isinstance(response,Mapping):
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_INVALID",409)
result=response
expected_response=dict(durable.response(idempotent=True))
if (
set(result)!=set(expected_response)|{"reservation_readback"}
or any(result.get(field)!=value for field,value in expected_response.items())
):
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_CONFLICT",409)
proof=result.get("reservation_readback")
if not isinstance(proof,Mapping):
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_INVALID",409)
expected_keys={
"schema","job_id","state","side_effect_state","revision",
"producer_idempotency_key","request_idempotency_key",
}
key=request.get("idempotency_key")
try:
producer_key=_fl_job_sha256_json({"client_idempotency_key":key})
except Exception as exc:
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_INVALID",409) from exc
job_id=getattr(durable,"job_id",None)
if (
set(proof)!=expected_keys
or proof.get("schema")!="aios.governed-task-reservation-readback.v1"
or proof.get("job_id")!=job_id
or proof.get("producer_idempotency_key")!=producer_key
or proof.get("request_idempotency_key")!=key
or proof.get("state") not in {item.value for item in _FLJobState}
or not isinstance(proof.get("side_effect_state"),str)
or not proof.get("side_effect_state")
or not isinstance(proof.get("revision"),int)
or isinstance(proof.get("revision"),bool)
or proof.get("revision")<0
or getattr(durable,"branch_mode",None)!=expected_branch_mode
):
raise _fl_error("TASK_ADMISSION_PRODUCER_READBACK_CONFLICT",409)
return str(job_id)
class _FLProducerReceiptReserver:
def __init__(self,runtime:object):
self.runtime=runtime
self.readbacks:dict[str,dict[str,Any]]={}
def validate_v2_replay(self,grant:object,plan:Mapping[str,Any])->None:
"""Permit the old v2 contract only as an exact durable replay."""
require_reservable_grant(grant)
bootstrap=getattr(self.runtime,"bootstrap",None)
if bootstrap is None or not callable(getattr(bootstrap,"get_binding",None)):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
_fl_assert_bootstrap_source(bootstrap)
task_id=str(_gv(grant,"task_id"))
try:
durable=bootstrap.get_binding(task_id)
except Exception as exc:
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_V2_REPLAY_BINDING_REQUIRED",409) from exc
expected={
"task_id":task_id,
"job_id":plan.get("external_job_id"),
"repository":_gv(grant,"repository"),
"base_oid":_gv(grant,"authority_base_oid"),
"expected_branch_oid":_gv(grant,"authority_base_oid"),
"target_branch":_gv(grant,"target_branch"),
"generation":_gv(grant,"generation"),
"risk":_gv(grant,"risk"),
"idempotency_key":"task-admission:"+task_id,
"description":"P0 governed execution projection for officially admitted Agent Task.",
}
if any(getattr(durable,field,None)!=value for field,value in expected.items()):
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_V2_REPLAY_BINDING_CONFLICT",409)
if getattr(durable,"branch_mode",None) not in {"CREATE_NEW","CONTINUE_EXISTING"}:
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_V2_REPLAY_BINDING_CONFLICT",409)
if not isinstance(plan.get("external_job_id"),str) or not _JOB_ID.fullmatch(plan["external_job_id"]):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID",409)
def existing_v3_job(self,grant:object,plan:Mapping[str,Any])->str|None:
"""Reuse an exact durable reservation without reinterpreting live refs.
Once reserved, CREATE_NEW versus CONTINUE_EXISTING is immutable input
lineage. A later TaskPrincipal read must not turn legitimate branch
creation or forward progress into a fresh admission decision.
"""
try:
validate_v3_plan(grant=grant,raw=canonical_json_bytes(dict(plan)))
except ProducerReceiptError as exc:
raise _fl_error(exc.code,403) from exc
bootstrap=getattr(self.runtime,"bootstrap",None)
bindings=getattr(self.runtime,"bindings",None)
if (
bootstrap is None
or not callable(getattr(bootstrap,"get_binding",None))
or not isinstance(bindings,dict)
):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
_fl_assert_bootstrap_source(bootstrap)
task_id=str(_gv(grant,"task_id"))
try:
durable=bootstrap.get_binding(task_id)
except Exception as exc:
if str(exc)=="TASK_EXECUTION_PROJECTION_MISSING":
return None
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_READ_FAILED",503) from exc
request=build_reservation_request(
grant,expected_branch_oid=str(_gv(grant,"authority_base_oid")),
)
branch_mode=getattr(durable,"branch_mode",None)
if branch_mode not in {"CREATE_NEW","CONTINUE_EXISTING"}:
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
job_id=getattr(durable,"job_id",None)
if not isinstance(job_id,str) or not _JOB_ID.fullmatch(job_id):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_JOB_ID_INVALID",409)
_fl_validate_durable_binding(
grant=grant,request=request,durable=durable,job_id=job_id,
expected_branch_mode=branch_mode,
)
readback=_fl_existing_reservation_readback(
bootstrap=bootstrap,grant=grant,request=request,durable=durable,
expected_branch_mode=branch_mode,
)
_fl_validate_reservation_readback(
grant=grant,request=request,durable=durable,response=readback,
expected_branch_mode=branch_mode,
)
# Keep readback evidence separate from producer submit receipts. In
# particular, a principal re-read must not overwrite the real receipt
# retained by a fresh admission in this runtime.
self.readbacks[task_id]=dict(readback)
return job_id
def __call__(self,grant:object,plan:Mapping[str,Any])->str:
try:
validate_v3_plan(grant=grant,raw=canonical_json_bytes(dict(plan)))
except ProducerReceiptError as exc:
raise _fl_error(exc.code,403) from exc
authority=getattr(self.runtime,"authority",None)
bootstrap=getattr(self.runtime,"bootstrap",None)
bindings=getattr(self.runtime,"bindings",None)
if (
authority is None or bootstrap is None
or not callable(getattr(bootstrap,"create",None))
or not callable(getattr(bootstrap,"get_binding",None))
or not isinstance(bindings,dict)
):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
_fl_assert_bootstrap_source(bootstrap)
main_before,branch_before,branch_mode,expected_branch_oid=_fl_reservation_refs(self.runtime,grant)
try:
main_before=authority.read_ref("refs/heads/main")
branch_before=authority.read_ref("refs/heads/"+TARGET_BRANCH)
except Exception as exc:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_UNAVAILABLE",503) from exc
if main_before!=TARGET_BASE_OID or branch_before!=TARGET_BASE_OID:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_DRIFT",409)
request=build_reservation_request(grant,expected_branch_oid=expected_branch_oid)
request=build_reservation_request(grant)
request_before=dict(request)
try:
first=bootstrap.create(request)
if request!=request_before:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_REQUEST_MUTATED",503)
first_phase=_mapping(first,"TASK_ADMISSION_EXECUTION_RESERVATION_RESPONSE_INVALID").get("reservation_receipt",{}).get("side_effect_state")
if first_phase not in {"QUEUED","SAME_JOB"}:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_PHASE_INVALID",409)
validate_bootstrap_response(
grant=grant,request=request,response=first,
expected_side_effect_state=first_phase,expected_branch_mode=branch_mode,
)
validate_bootstrap_response(grant=grant,request=request,response=first,expected_side_effect_state="QUEUED")
replay=bootstrap.create(request)
if request!=request_before:
@@ -1975,12 +1434,6 @@ class _FLProducerReceiptReserver:
job_id=validate_bootstrap_replay(
grant=grant,request=request,first=first,replay=replay,
replay_side_effect_state=REPLAY_SIDE_EFFECT_STATE,
first_side_effect_state=first_phase,expected_branch_mode=branch_mode,
)
durable=bootstrap.get_binding(str(_gv(grant,"task_id")))
_fl_validate_durable_binding(
grant=grant,request=request,durable=durable,job_id=job_id,
expected_branch_mode=branch_mode,
)
except ProducerReceiptError as exc:
raise _fl_error(exc.code,409) from exc
@@ -1995,12 +1448,11 @@ class _FLProducerReceiptReserver:
# Existing runtime's public createGovernedTask projection performs this
# in-memory binding update after bootstrap.create(). Mirror it only
# after the exact producer receipt + replay proof has passed.
bindings[str(_gv(grant,"task_id"))]=dict(replay)
self.readbacks.pop(str(_gv(grant,"task_id")),None)
bindings[TARGET_TASK_ID]=dict(replay)
try:
main_after=authority.read_ref("refs/heads/main")
branch_after=authority.read_ref("refs/heads/"+str(_gv(grant,"target_branch")))
branch_after=authority.read_ref("refs/heads/"+TARGET_BRANCH)
except Exception as exc:
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_AUTHORITY_UNAVAILABLE",503) from exc
if main_after!=main_before or branch_after!=branch_before:
@@ -2015,33 +1467,29 @@ class V3ProducerReceiptExecutionPlanDirectoryProvider(_FL_HARDENING_PROVIDER):
raise _fl_error("TASK_ADMISSION_EXECUTION_RESERVATION_BACKEND_INVALID",503)
try:
plan=load_v3_plan(_fl_provider_root(self),grant)
job_id=self._reserver.existing_v3_job(grant,plan)
if job_id is None:
job_id=self._reserver(grant,plan)
job_id=self._reserver(grant,plan)
except ProducerReceiptError as exc:
raise _fl_error(exc.code,403) from exc
return {
**plan,
"external_job_id":job_id,
"execution_digest":_gv(grant,"execution_digest"),
}
return {**plan,"external_job_id":job_id}
def __call__(self,grant:object)->Mapping[str,Any]:
try:
return self._target(grant)
except Exception as v3_error:
if getattr(grant,"task_id",None)!=TARGET_TASK_ID:
parent=getattr(super(),"__call__",None)
if not callable(parent) or self._reserver is None:
raise v3_error
try:
legacy=parent(grant)
except Exception:
raise v3_error
self._reserver.validate_v2_replay(grant,legacy)
return legacy
if callable(parent):
return parent(grant)
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_PROVIDER_CONTRACT_INVALID",503)
return self._target(grant)
def load(self,grant:object)->Mapping[str,Any]:
return self(grant)
if getattr(grant,"task_id",None)!=TARGET_TASK_ID:
parent=getattr(super(),"load",None)
if callable(parent):
return parent(grant)
parent_call=getattr(super(),"__call__",None)
if callable(parent_call):
return parent_call(grant)
raise _fl_error("TASK_ADMISSION_EXECUTION_PLAN_PROVIDER_CONTRACT_INVALID",503)
return self._target(grant)
def _fl_runtime_from_post_admission(post_admission:object)->object:
runtime=getattr(post_admission,"__self__",None)
@@ -2061,93 +1509,31 @@ def _fl_call(builder,*args,post_admission=None,**kwargs):
previous=ExecutionPlanDirectoryProvider
ExecutionPlanDirectoryProvider=BoundProvider
try:
def exact_post_admission(grant,result):
task_id=str(_gv(grant,"task_id"))
durable=reserver.runtime.bootstrap.get_binding(task_id)
request=build_reservation_request(
grant,expected_branch_oid=str(_gv(grant,"authority_base_oid")),
)
if getattr(durable,"idempotency_key",None)!=request["idempotency_key"]:
return post_admission(grant,result)
branch_mode=getattr(durable,"branch_mode",None)
if branch_mode not in {"CREATE_NEW","CONTINUE_EXISTING"}:
raise _fl_error("TASK_ADMISSION_PRODUCER_DURABLE_BINDING_CONFLICT",409)
binding=reserver.runtime.bindings.get(task_id)
if isinstance(binding,Mapping) and "reservation_receipt" in binding:
# Fresh admission must retain the actual producer submit replay
# receipt produced by GovernedTaskBootstrap.create().
job_id=validate_bootstrap_response(
grant=grant,request=request,response=binding,
expected_side_effect_state="SAME_JOB",expected_branch_mode=branch_mode,
)
else:
# A process restart may leave only durable producer state. Its
# separately typed readback proves immutable identity without
# claiming that a producer submit happened in this request.
binding=reserver.readbacks.get(task_id)
job_id=_fl_validate_reservation_readback(
grant=grant,request=request,durable=durable,response=binding,
expected_branch_mode=branch_mode,
)
if result.get("task_id")!=task_id or result.get("external_job_id")!=job_id or result.get("state",result.get("status")) not in {"QUEUED","CLAIMED","RUNNING"}:
raise _fl_error("TASK_ADMISSION_PRODUCER_TASK_BINDING_CONFLICT",409)
_fl_validate_durable_binding(
grant=grant,request=request,durable=durable,job_id=job_id,
expected_branch_mode=branch_mode,
)
return dict(binding)
return builder(*args,post_admission=exact_post_admission,**kwargs)
return builder(*args,post_admission=post_admission,**kwargs)
finally:
ExecutionPlanDirectoryProvider=previous
def build_task_admission_runtime_from_activation_file(
activation_file:Path,*,
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
post_admission=None,
):
if not callable(_FL_BUILD_RUNTIME_FROM_ACTIVATION):
raise RuntimeError("TASK_ADMISSION_RUNTIME_ACTIVATION_BUILDER_MISSING")
return _fl_call(
_FL_BUILD_RUNTIME_FROM_ACTIVATION,activation_file,
caller_service=caller_service,post_admission=post_admission,
)
def build_task_admission_handlers_from_activation_file(
activation_file:Path,*,
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
post_admission=None,
):
return build_task_admission_runtime_from_activation_file(
activation_file,
caller_service=caller_service,post_admission=post_admission,
).handlers
def build_task_admission_runtime_from_environment(
*,
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
post_admission=None,
):
if not callable(_FL_BUILD_RUNTIME_FROM_ENVIRONMENT):
raise RuntimeError("TASK_ADMISSION_RUNTIME_ENVIRONMENT_BUILDER_MISSING")
return _fl_call(
_FL_BUILD_RUNTIME_FROM_ENVIRONMENT,
_FL_BUILD_FROM_ACTIVATION,activation_file,
caller_service=caller_service,post_admission=post_admission,
)
def build_task_admission_handlers_from_environment(
*,
caller_service:str="aios-chatgpt-bridge-v2-staging.service",
post_admission=None,
):
return build_task_admission_runtime_from_environment(
if not callable(_FL_BUILD_FROM_ENVIRONMENT):
raise RuntimeError("TASK_ADMISSION_ENVIRONMENT_BUILDER_MISSING")
return _fl_call(
_FL_BUILD_FROM_ENVIRONMENT,
caller_service=caller_service,post_admission=post_admission,
).handlers
)
# Keep the standalone legacy provider contract. The exported admission builders
# install a runtime-bound v3 subclass only while composing their trusted port;
# an unbound provider cannot prove a durable producer receipt.
ExecutionPlanDirectoryProvider=_FL_HARDENING_PROVIDER
ExecutionPlanDirectoryProvider=V3ProducerReceiptExecutionPlanDirectoryProvider
# ===== AIOS FORMAL LINEAGE POST-OWNER PRODUCER NATIVE V12 — END =====
-779
View File
@@ -1,779 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass, field, replace
from datetime import datetime, timezone
from enum import Enum
from hashlib import sha256
import re
from typing import Callable, Iterable, Mapping, Protocol, Sequence
class LineageError(ValueError):
pass
class RelationType(str, Enum):
ROOT = "ROOT"
CHILD = "CHILD"
SUCCESSOR = "SUCCESSOR"
RECOVERY = "RECOVERY"
class NextLegalAction(str, Enum):
ROOT_COMPLETE = "ROOT_COMPLETE"
CREATE_OR_RESUME_NEXT_CHILD = "CREATE_OR_RESUME_NEXT_CHILD"
WAIT_DEPENDENCY = "WAIT_DEPENDENCY"
RECOVER_EXISTING_CHILD = "RECOVER_EXISTING_CHILD"
OWNER_BOUNDARY = "OWNER_BOUNDARY"
TERMINAL_STATUSES = frozenset(
{"COMPLETE", "COMPLETED", "SUCCEEDED", "FAILED", "FAILED_FINAL", "CANCELLED"}
)
RECOVERY_STATUSES = frozenset(
{"RECONCILIATION_REQUIRED", "RECOVERY_REQUIRED", "FAILED_RECOVERABLE"}
)
BLOCKED_STATUSES = frozenset({"BLOCKED", "WAITING_DEPENDENCY", "WAITING_CAPACITY"})
_ALIAS_RE = re.compile(r"^[A-Z0-9][A-Z0-9_-]{1,31}$")
_SLUG_RE = re.compile(r"^[A-Z0-9][A-Z0-9-]{1,63}$")
def _require_utc(ts: datetime) -> datetime:
if ts.tzinfo is None or ts.utcoffset() is None:
raise LineageError("created_at/reviewed_at must be timezone-aware")
return ts.astimezone(timezone.utc)
def derive_safe_owner_alias(principal_id: str, preferred_alias: str | None = None) -> str:
"""Return a non-secret display alias.
A trusted identity adapter may supply a safe human alias. Otherwise we use a
deterministic pseudonym derived from the principal identifier. We never put
raw email addresses, tokens, or principal strings into task names.
"""
if preferred_alias and "@" not in preferred_alias and ":" not in preferred_alias:
candidate = re.sub(r"[^A-Za-z0-9_-]+", "-", preferred_alias.strip()).strip("-_").upper()
if 2 <= len(candidate) <= 32 and _ALIAS_RE.fullmatch(candidate):
return candidate
if not principal_id:
raise LineageError("principal_id is required")
return "U" + sha256(principal_id.encode("utf-8")).hexdigest()[:9].upper()
def normalize_goal_slug(value: str) -> str:
value = re.sub(r"[^A-Za-z0-9]+", "-", value.strip()).strip("-").upper()
value = re.sub(r"-+", "-", value)
if len(value) > 64:
value = value[:64].rstrip("-")
if not _SLUG_RE.fullmatch(value):
raise LineageError("goal_slug must normalize to 2-64 [A-Z0-9-] characters")
return value
def short_ref(task_id: str) -> str:
if not task_id:
raise LineageError("task_id is required")
compact = re.sub(r"[^A-Za-z0-9]", "", task_id).upper()
if len(compact) >= 8:
return compact[-8:]
return sha256(task_id.encode("utf-8")).hexdigest()[:8].upper()
@dataclass(frozen=True)
class TaskLineage:
task_id: str
display_name: str
owner_principal_id: str
owner_alias: str
created_at: datetime
mission_id: str
root_task_id: str
parent_task_id: str | None
lineage_depth: int
relation_type: RelationType
sibling_sequence: int
goal_slug: str
root_acceptance_ref: str
parent_revision: int | None = None
@dataclass(frozen=True)
class TaskRecord:
task_id: str
revision: int
status: str
lineage: TaskLineage
evidence_refs: tuple[str, ...] = ()
terminal_evidence_proven: bool = False
@dataclass(frozen=True)
class AcceptanceCondition:
key: str
satisfied: bool
evidence_refs: tuple[str, ...] = ()
owner_boundary: bool = False
@dataclass(frozen=True)
class RootAcceptanceSnapshot:
conditions: tuple[AcceptanceCondition, ...]
@property
def remaining(self) -> tuple[str, ...]:
return tuple(c.key for c in self.conditions if not c.satisfied)
@property
def complete(self) -> bool:
return all(c.satisfied for c in self.conditions)
@property
def owner_boundary_required(self) -> bool:
return any((not c.satisfied) and c.owner_boundary for c in self.conditions)
@property
def evidence_refs(self) -> tuple[str, ...]:
values: list[str] = []
for condition in self.conditions:
if condition.satisfied:
values.extend(condition.evidence_refs)
return tuple(dict.fromkeys(values))
def canonicalize_acceptance(
acceptance: RootAcceptanceSnapshot,
) -> RootAcceptanceSnapshot:
if not isinstance(acceptance, RootAcceptanceSnapshot):
raise LineageError("acceptance evaluator returned an invalid snapshot")
normalized: list[AcceptanceCondition] = []
keys: set[str] = set()
for condition in acceptance.conditions:
if not isinstance(condition, AcceptanceCondition):
raise LineageError("acceptance snapshot contains an invalid condition")
if not isinstance(condition.key, str) or not condition.key:
raise LineageError("acceptance condition key must be a non-empty string")
if condition.key in keys:
raise LineageError("acceptance condition keys must be unique")
if not isinstance(condition.satisfied, bool) or not isinstance(
condition.owner_boundary, bool
):
raise LineageError("acceptance condition flags must be booleans")
if not all(isinstance(ref, str) and ref for ref in condition.evidence_refs):
raise LineageError("acceptance evidence refs must be non-empty strings")
keys.add(condition.key)
normalized.append(
replace(
condition,
evidence_refs=tuple(sorted(set(condition.evidence_refs))),
)
)
return RootAcceptanceSnapshot(tuple(sorted(normalized, key=lambda c: c.key)))
@dataclass(frozen=True)
class RootReviewRecord:
review_id: str
root_task_id: str
reviewed_at: datetime
trigger_child_task_id: str
root_revision_before: int
root_revision_after: int
completed_children: tuple[str, ...]
active_children: tuple[str, ...]
blocked_children: tuple[str, ...]
remaining_acceptance_conditions: tuple[str, ...]
next_legal_action: NextLegalAction
root_complete: bool
evidence_refs: tuple[str, ...]
class RootReviewStore(Protocol):
def get_task(self, task_id: str) -> TaskRecord: ...
def list_descendants(self, root_task_id: str) -> Sequence[TaskRecord]: ...
def get_root_review(self, review_id: str) -> RootReviewRecord | None: ...
def compare_and_swap_root_review(
self,
root_task_id: str,
expected_root_revision: int,
review: RootReviewRecord,
) -> bool: ...
AcceptanceEvaluator = Callable[[TaskRecord, Sequence[TaskRecord]], RootAcceptanceSnapshot]
def _timestamp(ts: datetime) -> str:
return _require_utc(ts).strftime("%Y%m%dT%H%M%SZ")
def _relation_token(relation: RelationType, sequence: int) -> str:
if sequence < 1 or sequence > 99:
raise LineageError("non-root sibling_sequence must be 1..99")
prefix = {
RelationType.CHILD: "C",
RelationType.SUCCESSOR: "S",
RelationType.RECOVERY: "X",
}.get(relation)
if not prefix:
raise LineageError("ROOT does not use a child relation token")
return f"{prefix}{sequence:02d}"
def build_display_name(lineage: TaskLineage) -> str:
alias = lineage.owner_alias.upper()
if not _ALIAS_RE.fullmatch(alias):
raise LineageError("owner_alias must be a non-secret [A-Z0-9_-] alias")
goal = normalize_goal_slug(lineage.goal_slug)
ts = _timestamp(lineage.created_at)
if lineage.relation_type == RelationType.ROOT:
return f"TASK-{alias}-{ts}-ROOT-{goal}-{short_ref(lineage.task_id)[:4]}"
relation = _relation_token(lineage.relation_type, lineage.sibling_sequence)
return (
f"TASK-{alias}-{ts}"
f"-R{short_ref(lineage.root_task_id)}"
f"-P{short_ref(lineage.parent_task_id or '')}"
f"-D{lineage.lineage_depth:02d}"
f"-{relation}-{goal}"
)
def make_root_lineage(
*,
task_id: str,
owner_principal_id: str,
owner_alias: str,
created_at: datetime,
mission_id: str,
goal_slug: str,
root_acceptance_ref: str,
) -> TaskLineage:
lineage = TaskLineage(
task_id=task_id,
display_name="",
owner_principal_id=owner_principal_id,
owner_alias=owner_alias.upper(),
created_at=_require_utc(created_at),
mission_id=mission_id,
root_task_id=task_id,
parent_task_id=None,
lineage_depth=0,
relation_type=RelationType.ROOT,
sibling_sequence=0,
goal_slug=normalize_goal_slug(goal_slug),
root_acceptance_ref=root_acceptance_ref,
parent_revision=None,
)
lineage = replace(lineage, display_name=build_display_name(lineage))
validate_lineage(lineage)
return lineage
def make_child_lineage(
*,
task_id: str,
created_at: datetime,
parent: TaskRecord,
root: TaskRecord,
relation_type: RelationType,
sibling_sequence: int,
goal_slug: str,
) -> TaskLineage:
if relation_type == RelationType.ROOT:
raise LineageError("child relation_type cannot be ROOT")
lineage = TaskLineage(
task_id=task_id,
display_name="",
owner_principal_id=root.lineage.owner_principal_id,
owner_alias=root.lineage.owner_alias,
created_at=_require_utc(created_at),
mission_id=root.lineage.mission_id,
root_task_id=root.task_id,
parent_task_id=parent.task_id,
lineage_depth=parent.lineage.lineage_depth + 1,
relation_type=relation_type,
sibling_sequence=sibling_sequence,
goal_slug=normalize_goal_slug(goal_slug),
root_acceptance_ref=root.lineage.root_acceptance_ref,
parent_revision=parent.revision,
)
lineage = replace(lineage, display_name=build_display_name(lineage))
validate_lineage(lineage, parent=parent, root=root)
return lineage
def validate_lineage(
lineage: TaskLineage,
*,
parent: TaskRecord | None = None,
root: TaskRecord | None = None,
) -> None:
_validate_lineage_shape(lineage)
if lineage.relation_type == RelationType.ROOT:
return
if parent is None or root is None:
raise LineageError("non-root validation requires parent and root authority records")
if root.lineage.relation_type != RelationType.ROOT:
raise LineageError("root authority record must have ROOT lineage")
if parent.lineage.root_task_id != root.task_id:
raise LineageError("parent is not attached to the selected root")
if parent.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("parent owner does not match root owner")
if parent.lineage.mission_id != root.lineage.mission_id:
raise LineageError("parent mission does not match root mission")
if parent.lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise LineageError("parent acceptance reference does not match root")
if lineage.root_task_id != root.task_id:
raise LineageError("root_task_id mismatch")
if lineage.parent_task_id != parent.task_id:
raise LineageError("parent_task_id mismatch")
if lineage.lineage_depth != parent.lineage.lineage_depth + 1:
raise LineageError("lineage_depth must equal parent depth + 1")
if lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("cross-owner lineage attachment rejected")
if lineage.owner_alias != root.lineage.owner_alias:
raise LineageError("owner_alias must match root")
if lineage.mission_id != root.lineage.mission_id:
raise LineageError("mission_id must match root")
if lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise LineageError("root_acceptance_ref must match root")
if lineage.parent_revision != parent.revision:
raise LineageError("stale or mismatched parent revision")
def _validate_lineage_shape(lineage: TaskLineage) -> None:
if not lineage.task_id or not lineage.owner_principal_id or not lineage.mission_id:
raise LineageError("task_id, owner_principal_id and mission_id are required")
if not lineage.root_acceptance_ref:
raise LineageError("root_acceptance_ref is required")
if not _ALIAS_RE.fullmatch(lineage.owner_alias):
raise LineageError("invalid owner_alias")
if normalize_goal_slug(lineage.goal_slug) != lineage.goal_slug:
raise LineageError("goal_slug must already be normalized")
if not lineage.display_name or len(lineage.display_name) > 200:
raise LineageError("display_name must be non-empty and <= 200 characters")
if lineage.relation_type == RelationType.ROOT:
if lineage.root_task_id != lineage.task_id:
raise LineageError("root_task_id must equal task_id for ROOT")
if lineage.parent_task_id is not None:
raise LineageError("ROOT parent_task_id must be null")
if lineage.lineage_depth != 0:
raise LineageError("ROOT lineage_depth must be 0")
if lineage.sibling_sequence != 0:
raise LineageError("ROOT sibling_sequence must be 0")
if lineage.parent_revision is not None:
raise LineageError("ROOT parent_revision must be null")
return
if not lineage.root_task_id or not lineage.parent_task_id:
raise LineageError("non-root root_task_id and parent_task_id are required")
if lineage.lineage_depth < 1:
raise LineageError("non-root lineage_depth must be >= 1")
if lineage.parent_revision is None or lineage.parent_revision < 0:
raise LineageError("non-root parent_revision must be >= 0")
_relation_token(lineage.relation_type, lineage.sibling_sequence)
def attach_legacy_lineage(
*,
existing_task_id: str,
created_at: datetime,
parent: TaskRecord,
root: TaskRecord,
relation_type: RelationType,
sibling_sequence: int,
goal_slug: str,
) -> TaskLineage:
"""Return lineage metadata for an existing durable task without changing its ID."""
return make_child_lineage(
task_id=existing_task_id,
created_at=created_at,
parent=parent,
root=root,
relation_type=relation_type,
sibling_sequence=sibling_sequence,
goal_slug=goal_slug,
)
def rename_display_only(lineage: TaskLineage, new_display_name: str) -> TaskLineage:
if not new_display_name or len(new_display_name) > 200:
raise LineageError("display name must be non-empty and <= 200 characters")
return replace(lineage, display_name=new_display_name)
LINEAGE_SCHEMA = "aios.task-lineage.v1"
ROOT_REVIEW_SCHEMA = "aios.root-review.v1"
_LINEAGE_FIELDS = frozenset(
{
"schema",
"task_id",
"display_name",
"owner_principal_id",
"owner_alias",
"created_at",
"mission_id",
"root_task_id",
"parent_task_id",
"lineage_depth",
"relation_type",
"sibling_sequence",
"goal_slug",
"root_acceptance_ref",
"parent_revision",
}
)
_ROOT_REVIEW_FIELDS = frozenset(
{
"schema",
"review_id",
"root_task_id",
"reviewed_at",
"trigger_child_task_id",
"root_revision_before",
"root_revision_after",
"completed_children",
"active_children",
"blocked_children",
"remaining_acceptance_conditions",
"next_legal_action",
"root_complete",
"evidence_refs",
}
)
def _require_exact_fields(metadata: Mapping[str, object], expected: frozenset[str]) -> None:
if not all(isinstance(key, str) for key in metadata):
raise LineageError("metadata field names must be strings")
actual = frozenset(metadata)
if actual != expected:
missing = sorted(expected - actual)
extra = sorted(actual - expected)
raise LineageError(f"metadata fields mismatch: missing={missing} extra={extra}")
def _require_string(metadata: Mapping[str, object], key: str) -> str:
value = metadata[key]
if not isinstance(value, str) or not value:
raise LineageError(f"{key} must be a non-empty string")
return value
def _require_int(metadata: Mapping[str, object], key: str) -> int:
value = metadata[key]
if isinstance(value, bool) or not isinstance(value, int):
raise LineageError(f"{key} must be an integer")
return value
def _require_string_list(metadata: Mapping[str, object], key: str) -> tuple[str, ...]:
value = metadata[key]
if not isinstance(value, list) or not all(isinstance(item, str) and item for item in value):
raise LineageError(f"{key} must be a list of non-empty strings")
items = tuple(value)
if len(items) != len(set(items)):
raise LineageError(f"{key} must not contain duplicates")
return items
def lineage_to_metadata(lineage: TaskLineage) -> dict[str, object]:
return {
"schema": LINEAGE_SCHEMA,
"task_id": lineage.task_id,
"display_name": lineage.display_name,
"owner_principal_id": lineage.owner_principal_id,
"owner_alias": lineage.owner_alias,
"created_at": _require_utc(lineage.created_at).isoformat().replace("+00:00", "Z"),
"mission_id": lineage.mission_id,
"root_task_id": lineage.root_task_id,
"parent_task_id": lineage.parent_task_id,
"lineage_depth": lineage.lineage_depth,
"relation_type": lineage.relation_type.value,
"sibling_sequence": lineage.sibling_sequence,
"goal_slug": lineage.goal_slug,
"root_acceptance_ref": lineage.root_acceptance_ref,
"parent_revision": lineage.parent_revision,
}
def lineage_from_metadata(metadata: Mapping[str, object]) -> TaskLineage:
_require_exact_fields(metadata, _LINEAGE_FIELDS)
if metadata.get("schema") != LINEAGE_SCHEMA:
raise LineageError("unsupported lineage metadata schema")
try:
created_raw = _require_string(metadata, "created_at")
if created_raw.endswith("Z"):
created_raw = created_raw[:-1] + "+00:00"
parent_task_id = metadata["parent_task_id"]
if parent_task_id is not None and (
not isinstance(parent_task_id, str) or not parent_task_id
):
raise LineageError("parent_task_id must be null or a non-empty string")
parent_revision = metadata["parent_revision"]
if parent_revision is not None and (
isinstance(parent_revision, bool) or not isinstance(parent_revision, int)
):
raise LineageError("parent_revision must be null or an integer")
lineage = TaskLineage(
task_id=_require_string(metadata, "task_id"),
display_name=_require_string(metadata, "display_name"),
owner_principal_id=_require_string(metadata, "owner_principal_id"),
owner_alias=_require_string(metadata, "owner_alias"),
created_at=datetime.fromisoformat(created_raw),
mission_id=_require_string(metadata, "mission_id"),
root_task_id=_require_string(metadata, "root_task_id"),
parent_task_id=parent_task_id,
lineage_depth=_require_int(metadata, "lineage_depth"),
relation_type=RelationType(_require_string(metadata, "relation_type")),
sibling_sequence=_require_int(metadata, "sibling_sequence"),
goal_slug=_require_string(metadata, "goal_slug"),
root_acceptance_ref=_require_string(metadata, "root_acceptance_ref"),
parent_revision=parent_revision,
)
except (KeyError, TypeError, ValueError, LineageError) as exc:
raise LineageError(f"invalid lineage metadata: {exc}") from exc
_validate_lineage_shape(lineage)
return lineage
def root_review_to_record(review: RootReviewRecord) -> dict[str, object]:
return {
"schema": ROOT_REVIEW_SCHEMA,
"review_id": review.review_id,
"root_task_id": review.root_task_id,
"reviewed_at": _require_utc(review.reviewed_at).isoformat().replace("+00:00", "Z"),
"trigger_child_task_id": review.trigger_child_task_id,
"root_revision_before": review.root_revision_before,
"root_revision_after": review.root_revision_after,
"completed_children": list(review.completed_children),
"active_children": list(review.active_children),
"blocked_children": list(review.blocked_children),
"remaining_acceptance_conditions": list(review.remaining_acceptance_conditions),
"next_legal_action": review.next_legal_action.value,
"root_complete": review.root_complete,
"evidence_refs": list(review.evidence_refs),
}
def root_review_from_record(
record: Mapping[str, object], *, expected_review_id: str | None = None
) -> RootReviewRecord:
_require_exact_fields(record, _ROOT_REVIEW_FIELDS)
if record.get("schema") != ROOT_REVIEW_SCHEMA:
raise LineageError("unsupported root-review metadata schema")
try:
reviewed_raw = _require_string(record, "reviewed_at")
if reviewed_raw.endswith("Z"):
reviewed_raw = reviewed_raw[:-1] + "+00:00"
root_complete = record["root_complete"]
if not isinstance(root_complete, bool):
raise LineageError("root_complete must be a boolean")
review = RootReviewRecord(
review_id=_require_string(record, "review_id"),
root_task_id=_require_string(record, "root_task_id"),
reviewed_at=_require_utc(datetime.fromisoformat(reviewed_raw)),
trigger_child_task_id=_require_string(record, "trigger_child_task_id"),
root_revision_before=_require_int(record, "root_revision_before"),
root_revision_after=_require_int(record, "root_revision_after"),
completed_children=_require_string_list(record, "completed_children"),
active_children=_require_string_list(record, "active_children"),
blocked_children=_require_string_list(record, "blocked_children"),
remaining_acceptance_conditions=_require_string_list(
record, "remaining_acceptance_conditions"
),
next_legal_action=NextLegalAction(
_require_string(record, "next_legal_action")
),
root_complete=root_complete,
evidence_refs=_require_string_list(record, "evidence_refs"),
)
except (KeyError, TypeError, ValueError, LineageError) as exc:
raise LineageError(f"invalid root-review metadata: {exc}") from exc
if expected_review_id is not None and review.review_id != expected_review_id:
raise LineageError("root-review id does not match lookup key")
if review.root_revision_before < 0 or (
review.root_revision_after != review.root_revision_before + 1
):
raise LineageError("root-review revision transition is invalid")
if review.root_complete != (
review.next_legal_action == NextLegalAction.ROOT_COMPLETE
):
raise LineageError("root_complete does not match next_legal_action")
child_sets = (
set(review.completed_children),
set(review.active_children),
set(review.blocked_children),
)
if any(child_sets[i] & child_sets[j] for i in range(3) for j in range(i + 1, 3)):
raise LineageError("root-review child classifications overlap")
return review
def _review_id(
root_task_id: str,
trigger_child_task_id: str,
trigger_revision: int,
acceptance: RootAcceptanceSnapshot,
) -> str:
acceptance_digest = sha256(
"\n".join(
f"{c.key}:{int(c.satisfied)}:{int(c.owner_boundary)}:"
f"{','.join(sorted(set(c.evidence_refs)))}"
for c in acceptance.conditions
).encode("utf-8")
).hexdigest()
payload = f"{root_task_id}|{trigger_child_task_id}|{trigger_revision}|{acceptance_digest}"
return "RR-" + sha256(payload.encode("utf-8")).hexdigest()[:24]
def _next_action(
descendants: Sequence[TaskRecord],
acceptance: RootAcceptanceSnapshot,
) -> NextLegalAction:
if acceptance.complete:
return NextLegalAction.ROOT_COMPLETE
if acceptance.owner_boundary_required:
return NextLegalAction.OWNER_BOUNDARY
if any(d.status in RECOVERY_STATUSES for d in descendants):
return NextLegalAction.RECOVER_EXISTING_CHILD
if any(d.status in BLOCKED_STATUSES for d in descendants):
return NextLegalAction.WAIT_DEPENDENCY
return NextLegalAction.CREATE_OR_RESUME_NEXT_CHILD
def review_root_after_child_terminal(
*,
store: RootReviewStore,
trigger_child_task_id: str,
acceptance_evaluator: AcceptanceEvaluator,
reviewed_at: datetime,
max_cas_retries: int = 4,
) -> RootReviewRecord:
"""Persist a root review after a proven child terminal transition.
This function is deliberately storage-agnostic. TaskPlane remains the
authority and supplies the CAS implementation. The function never creates a
new root or mutates a child identity.
"""
if max_cas_retries < 1:
raise LineageError("max_cas_retries must be >= 1")
reviewed_at = _require_utc(reviewed_at)
for _ in range(max_cas_retries):
child = store.get_task(trigger_child_task_id)
if child.lineage.relation_type == RelationType.ROOT:
raise LineageError("root review hook requires a child task")
if child.status not in TERMINAL_STATUSES:
raise LineageError("child must be terminal before root review")
if not child.terminal_evidence_proven:
raise LineageError("child terminal evidence is not proven")
root = store.get_task(child.lineage.root_task_id)
if root.lineage.relation_type != RelationType.ROOT:
raise LineageError("root_task_id does not resolve to a ROOT task")
if child.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("trigger child owner does not match root owner")
if child.lineage.mission_id != root.lineage.mission_id:
raise LineageError("trigger child mission does not match root mission")
if child.lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise LineageError("trigger child acceptance reference does not match root")
descendants = tuple(
sorted(store.list_descendants(root.task_id), key=lambda item: item.task_id)
)
descendant_ids = tuple(item.task_id for item in descendants)
if len(descendant_ids) != len(set(descendant_ids)):
raise LineageError("descendant authority returned duplicate task ids")
if child.task_id not in descendant_ids:
raise LineageError("trigger child missing from authoritative descendants")
for descendant in descendants:
if descendant.lineage.root_task_id != root.task_id:
raise LineageError("descendant is attached to a different root")
if descendant.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("descendant owner does not match root owner")
if descendant.lineage.mission_id != root.lineage.mission_id:
raise LineageError("descendant mission does not match root mission")
if (
descendant.lineage.root_acceptance_ref
!= root.lineage.root_acceptance_ref
):
raise LineageError("descendant acceptance reference does not match root")
acceptance = canonicalize_acceptance(acceptance_evaluator(root, descendants))
review_id = _review_id(root.task_id, child.task_id, child.revision, acceptance)
completed = tuple(
d.task_id for d in descendants
if d.status in TERMINAL_STATUSES and d.terminal_evidence_proven
)
active = tuple(
d.task_id for d in descendants
if d.status not in TERMINAL_STATUSES and d.status not in BLOCKED_STATUSES
)
blocked = tuple(
d.task_id for d in descendants
if d.status in BLOCKED_STATUSES
)
evidence = tuple(sorted(set(child.evidence_refs + acceptance.evidence_refs)))
action = _next_action(descendants, acceptance)
existing = store.get_root_review(review_id)
if existing is not None:
if (
existing.root_task_id != root.task_id
or existing.trigger_child_task_id != child.task_id
):
raise LineageError("persisted root review identity conflicts with trigger")
if existing.root_revision_after > root.revision:
raise LineageError("persisted root review revision is ahead of authority")
expected_semantics = (
completed,
active,
blocked,
acceptance.remaining,
action,
action == NextLegalAction.ROOT_COMPLETE,
evidence,
)
actual_semantics = (
existing.completed_children,
existing.active_children,
existing.blocked_children,
existing.remaining_acceptance_conditions,
existing.next_legal_action,
existing.root_complete,
existing.evidence_refs,
)
if actual_semantics != expected_semantics:
raise LineageError("persisted root review conflicts with current semantics")
return existing
review = RootReviewRecord(
review_id=review_id,
root_task_id=root.task_id,
reviewed_at=reviewed_at,
trigger_child_task_id=child.task_id,
root_revision_before=root.revision,
root_revision_after=root.revision + 1,
completed_children=completed,
active_children=active,
blocked_children=blocked,
remaining_acceptance_conditions=acceptance.remaining,
next_legal_action=action,
root_complete=(action == NextLegalAction.ROOT_COMPLETE),
evidence_refs=evidence,
)
if store.compare_and_swap_root_review(root.task_id, root.revision, review):
return review
# Another sibling may have updated root progress. Re-read authority and
# recompute; never overwrite a newer root review.
raise LineageError("root review CAS retry budget exhausted")
@@ -1,285 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import datetime
from typing import Mapping
if not __package__:
from aios_task_lineage import AcceptanceEvaluator, RelationType, TaskLineage, lineage_to_metadata, lineage_from_metadata, root_review_to_record
from aios_task_lineage_integration import (
DISPLAY_NAME_METADATA_KEY,
IntegrationError,
LINEAGE_METADATA_KEY,
NewTaskIdentityRequest,
PreparedTaskIdentity,
TaskIdentityLineageService,
TerminalReviewOutcome,
)
from aios_task_lineage_taskplane_adapter import (
AuthenticatedTaskContext,
InProcessTaskPlaneLineageAdapter,
require_context_matches_task,
)
else:
from .aios_task_lineage import AcceptanceEvaluator, RelationType, TaskLineage, lineage_to_metadata, lineage_from_metadata, root_review_to_record
from .aios_task_lineage_integration import (
DISPLAY_NAME_METADATA_KEY,
IntegrationError,
LINEAGE_METADATA_KEY,
NewTaskIdentityRequest,
PreparedTaskIdentity,
TaskIdentityLineageService,
TerminalReviewOutcome,
)
from .aios_task_lineage_taskplane_adapter import (
AuthenticatedTaskContext,
InProcessTaskPlaneLineageAdapter,
require_context_matches_task,
)
class LifecycleHookError(IntegrationError):
pass
@dataclass(frozen=True)
class IdentityPersistenceOutcome:
prepared: PreparedTaskIdentity
persisted_revision_at_least: int
independently_verified: bool
@dataclass(frozen=True)
class LegacyAttachOutcome:
lineage: TaskLineage
independently_verified: bool
@dataclass(frozen=True)
class TerminalHookOutcome:
outcome: TerminalReviewOutcome
root_review_persisted: bool
dispatch_persisted: bool
independently_verified: bool
class TaskLineageLifecycleHooks:
"""Concrete lifecycle hook coordinator.
It does not own TaskPlane state. It orchestrates existing TaskPlane CAS,
journal/continuation and optional independent authority readback.
"""
def __init__(self, adapter: InProcessTaskPlaneLineageAdapter):
self._adapter = adapter
self._service = TaskIdentityLineageService(adapter)
def persist_new_task_identity(
self,
*,
request: NewTaskIdentityRequest,
context: AuthenticatedTaskContext,
max_cas_retries: int = 4,
require_independent_readback: bool = True,
) -> IdentityPersistenceOutcome:
if max_cas_retries < 1:
raise LifecycleHookError("max_cas_retries must be >= 1")
if require_independent_readback and not self._adapter.has_independent_readback:
raise LifecycleHookError("independent TaskPlane readback is not configured")
if request.owner_principal_id != context.principal_id:
raise LifecycleHookError("request principal does not match authenticated context")
if request.mission_id != context.mission_id:
raise LifecycleHookError("request mission does not match authenticated context")
for _ in range(max_cas_retries):
current = self._adapter.get_task_snapshot(request.task_id)
require_context_matches_task(context, current)
if current.created_at != request.created_at:
raise LifecycleHookError("request created_at does not match authority")
if (request.relation_type == RelationType.ROOT
and request.preferred_owner_alias not in (None, context.preferred_alias)):
raise LifecycleHookError("request alias does not match authenticated context")
request = replace(request, preferred_owner_alias=context.preferred_alias)
prepared = self._service.prepare_new_task_identity(request)
# Idempotent replay and legacy display-name repair are fail-closed:
# never overwrite an existing conflicting lineage record.
raw = current.metadata.get(LINEAGE_METADATA_KEY)
display = current.metadata.get(DISPLAY_NAME_METADATA_KEY)
if raw is not None and not isinstance(raw, Mapping):
raise LifecycleHookError("existing lineage metadata is malformed")
if isinstance(raw, Mapping):
# parent_revision records the creation observation, not the
# parent's current progress revision on an idempotent replay.
expected = dict(prepared.metadata_patch[LINEAGE_METADATA_KEY])
if request.parent_task_id is not None:
stored = lineage_from_metadata(dict(raw))
if (stored.parent_revision is None
or stored.parent_revision > prepared.lineage.parent_revision):
raise LifecycleHookError("existing parent revision conflicts with authority")
expected["parent_revision"] = stored.parent_revision
if raw != expected:
raise LifecycleHookError(
"existing lineage metadata conflicts with requested identity"
)
prepared = replace(prepared, lineage=lineage_from_metadata(dict(raw)),
metadata_patch={LINEAGE_METADATA_KEY: dict(raw),
DISPLAY_NAME_METADATA_KEY: prepared.display_name})
if display == prepared.display_name:
verified = False
if require_independent_readback:
self._adapter.verify_task_readback(
task_id=request.task_id,
expected_revision_at_least=current.revision,
expected_metadata=prepared.metadata_patch,
)
verified = True
return IdentityPersistenceOutcome(
prepared=prepared,
persisted_revision_at_least=current.revision,
independently_verified=verified,
)
if display not in (None, ""):
raise LifecycleHookError(
"existing display name conflicts with requested identity"
)
metadata_patch = {
DISPLAY_NAME_METADATA_KEY: prepared.display_name,
}
else:
if display not in (None, ""):
raise LifecycleHookError(
"display name exists without authoritative lineage"
)
metadata_patch = prepared.metadata_patch
ok = self._adapter.compare_and_swap_task_metadata(
task_id=request.task_id,
expected_revision=current.revision,
metadata_patch=metadata_patch,
)
if not ok:
continue
verified = False
if require_independent_readback:
self._adapter.verify_task_readback(
task_id=request.task_id,
expected_revision_at_least=current.revision + 1,
expected_metadata=prepared.metadata_patch,
)
verified = True
return IdentityPersistenceOutcome(
prepared=prepared,
persisted_revision_at_least=current.revision + 1,
independently_verified=verified,
)
raise LifecycleHookError("identity persistence CAS retry budget exhausted")
def attach_legacy_identity(
self,
*,
task_id: str,
parent_task_id: str,
relation_type: RelationType,
goal_slug: str,
context: AuthenticatedTaskContext,
max_cas_retries: int = 4,
require_independent_readback: bool = True,
) -> LegacyAttachOutcome:
if require_independent_readback and not self._adapter.has_independent_readback:
raise LifecycleHookError("independent TaskPlane readback is not configured")
before = self._adapter.get_task_snapshot(task_id)
require_context_matches_task(context, before)
lineage = self._service.attach_legacy_task(
task_id=task_id,
parent_task_id=parent_task_id,
relation_type=relation_type,
goal_slug=goal_slug,
max_cas_retries=max_cas_retries,
expected_owner_principal_id=context.principal_id,
expected_mission_id=context.mission_id,
)
verified = False
if require_independent_readback:
self._adapter.verify_task_readback(
task_id=task_id,
expected_revision_at_least=before.revision,
expected_metadata={
LINEAGE_METADATA_KEY: lineage_to_metadata(lineage),
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
)
# Domain serialization compatibility check below avoids requiring
# TaskLineage to expose a method in older versions.
verified = True
return LegacyAttachOutcome(lineage=lineage, independently_verified=verified)
def on_proven_terminal_child(
self,
*,
child_task_id: str,
context: AuthenticatedTaskContext,
acceptance_evaluator: AcceptanceEvaluator,
reviewed_at: datetime,
max_cas_retries: int = 4,
require_independent_readback: bool = True,
) -> TerminalHookOutcome:
if require_independent_readback and not self._adapter.has_independent_readback:
raise LifecycleHookError("independent TaskPlane readback is not configured")
child = self._adapter.get_task_snapshot(child_task_id)
require_context_matches_task(context, child)
if not child.terminal_evidence_proven:
raise LifecycleHookError(
"terminal child hook requires proven terminal evidence"
)
raw = child.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(raw, Mapping):
raise LifecycleHookError("terminal child lineage missing")
lineage = lineage_from_metadata(dict(raw))
root = self._adapter.get_task_snapshot(lineage.root_task_id)
require_context_matches_task(context, root)
def authorized_acceptance(root_record, descendants):
if (root_record.lineage.owner_principal_id != context.principal_id
or root_record.lineage.mission_id != context.mission_id):
raise LifecycleHookError("root authority changed during terminal review")
return acceptance_evaluator(root_record, descendants)
outcome = self._service.on_proven_terminal_child(
child_task_id=child_task_id,
acceptance_evaluator=authorized_acceptance,
reviewed_at=reviewed_at,
max_cas_retries=max_cas_retries,
)
persisted_review = self._adapter.get_root_review_record(
outcome.review.review_id
)
if persisted_review is None:
raise LifecycleHookError("root review was not persisted")
if persisted_review != root_review_to_record(outcome.review):
raise LifecycleHookError("persisted review does not match returned review")
dispatch = self._adapter.get_root_action_dispatch(outcome.review.review_id)
if dispatch is None:
raise LifecycleHookError("root action dispatch was not persisted")
if dispatch != outcome.dispatch:
raise LifecycleHookError("persisted dispatch does not match returned receipt")
verified = False
if require_independent_readback:
self._adapter.verify_root_readback(
root_task_id=outcome.review.root_task_id,
expected_review_id=outcome.review.review_id,
)
verified = True
return TerminalHookOutcome(
outcome=outcome,
root_review_persisted=True,
dispatch_persisted=True,
independently_verified=verified,
)
@@ -1,539 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Mapping, MutableMapping, Protocol, Sequence
if not __package__:
from aios_task_lineage import (
AcceptanceEvaluator,
LineageError,
NextLegalAction,
RelationType,
RootReviewRecord,
RootReviewStore,
TaskLineage,
TaskRecord,
attach_legacy_lineage,
derive_safe_owner_alias,
lineage_from_metadata,
lineage_to_metadata,
make_child_lineage,
make_root_lineage,
normalize_goal_slug,
review_root_after_child_terminal,
root_review_from_record,
root_review_to_record,
)
else:
from .aios_task_lineage import (
AcceptanceEvaluator,
LineageError,
NextLegalAction,
RelationType,
RootReviewRecord,
RootReviewStore,
TaskLineage,
TaskRecord,
attach_legacy_lineage,
derive_safe_owner_alias,
lineage_from_metadata,
lineage_to_metadata,
make_child_lineage,
make_root_lineage,
normalize_goal_slug,
review_root_after_child_terminal,
root_review_from_record,
root_review_to_record,
)
LINEAGE_METADATA_KEY = "aios_task_lineage"
LINEAGE_REQUEST_DIGEST_KEY = "aios_lineage_request_digest"
ROOT_REVIEW_RECORD_KEY = "aios_root_review"
DISPLAY_NAME_METADATA_KEY = "aios_task_display_name"
class IntegrationError(RuntimeError):
pass
@dataclass(frozen=True)
class TaskAuthoritySnapshot:
task_id: str
revision: int
status: str
owner_principal_id: str
mission_id: str
created_at: datetime
metadata: Mapping[str, object]
evidence_refs: tuple[str, ...] = ()
terminal_evidence_proven: bool = False
@dataclass(frozen=True)
class NewTaskIdentityRequest:
task_id: str
owner_principal_id: str
preferred_owner_alias: str | None
created_at: datetime
mission_id: str
goal_slug: str
root_acceptance_ref: str
relation_type: RelationType = RelationType.ROOT
parent_task_id: str | None = None
@dataclass(frozen=True)
class PreparedTaskIdentity:
task_id: str
display_name: str
lineage: TaskLineage
metadata_patch: Mapping[str, object]
@dataclass(frozen=True)
class TerminalReviewOutcome:
review: RootReviewRecord
dispatch: "RootActionDispatchReceipt"
@dataclass(frozen=True)
class RootActionDispatchReceipt:
review_id: str
root_task_id: str
action: NextLegalAction
idempotency_key: str
status: str
mission_id: str = ""
root_revision: int = 0
plan_digest: str | None = None
execution_key: str | None = None
PERSISTED_DISPATCH_STATUSES = frozenset(
{
"PENDING",
"CLAIMED",
"EFFECT_UNKNOWN",
"EXECUTED_PENDING_REREVIEW",
"WAITING_DEPENDENCY",
"WAITING_OWNER",
"CLOSED",
"SUPERSEDED",
"BLOCKED",
# Read compatibility only. A consumer must reconcile this legacy
# state unless a complete durable receipt is present.
"DISPATCHED",
}
)
class TaskPlaneLineagePort(Protocol):
"""Narrow authority port required by the lineage integration service.
The live adapter must implement these methods over the existing TaskPlane /
mission / journal / CAS mechanisms. This protocol deliberately avoids
guessing concrete DB tables, HTTP endpoints, or handler names.
"""
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot: ...
def list_descendant_snapshots(
self, root_task_id: str
) -> Sequence[TaskAuthoritySnapshot]: ...
def reserve_sibling_sequence(
self,
*,
parent_task_id: str,
relation_type: RelationType,
child_task_id: str,
) -> int:
"""Idempotently reserve one sibling sequence for child_task_id."""
def compare_and_swap_task_metadata(
self,
*,
task_id: str,
expected_revision: int,
metadata_patch: Mapping[str, object],
) -> bool: ...
def get_root_review_record(
self, review_id: str
) -> Mapping[str, object] | None: ...
def compare_and_swap_root_review_record(
self,
*,
root_task_id: str,
expected_root_revision: int,
review_record: Mapping[str, object],
) -> bool: ...
def get_root_action_dispatch(
self, review_id: str
) -> RootActionDispatchReceipt | None: ...
def dispatch_root_action(
self,
*,
review: RootReviewRecord,
idempotency_key: str,
) -> RootActionDispatchReceipt: ...
def _extract_lineage(snapshot: TaskAuthoritySnapshot) -> TaskLineage:
raw = snapshot.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(raw, dict):
raise IntegrationError(
f"task {snapshot.task_id} does not have authoritative lineage metadata"
)
lineage = lineage_from_metadata(raw)
if lineage.task_id != snapshot.task_id:
raise IntegrationError("lineage task_id does not match authority task")
if lineage.owner_principal_id != snapshot.owner_principal_id:
raise IntegrationError("lineage owner does not match authority owner")
if lineage.mission_id != snapshot.mission_id:
raise IntegrationError("lineage mission does not match authority mission")
if snapshot.created_at.tzinfo is None or snapshot.created_at.utcoffset() is None:
raise IntegrationError("authority created_at must be timezone-aware")
if lineage.created_at != snapshot.created_at:
raise IntegrationError("lineage created_at does not match authority task")
display = snapshot.metadata.get(DISPLAY_NAME_METADATA_KEY)
if display not in (None, "", lineage.display_name):
raise IntegrationError("lineage display name conflicts with authority metadata")
return lineage
def _as_task_record(snapshot: TaskAuthoritySnapshot) -> TaskRecord:
return TaskRecord(
task_id=snapshot.task_id,
revision=snapshot.revision,
status=snapshot.status,
lineage=_extract_lineage(snapshot),
evidence_refs=snapshot.evidence_refs,
terminal_evidence_proven=snapshot.terminal_evidence_proven,
)
def _validate_record_root_binding(record: TaskRecord, root: TaskRecord) -> None:
if root.lineage.relation_type != RelationType.ROOT:
raise IntegrationError("root_task_id does not resolve to a ROOT task")
if record.lineage.root_task_id != root.task_id:
raise IntegrationError("task lineage points to a different root")
if record.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise IntegrationError("task lineage owner does not match root owner")
if record.lineage.mission_id != root.lineage.mission_id:
raise IntegrationError("task lineage mission does not match root mission")
if record.lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise IntegrationError("task lineage acceptance reference does not match root")
def _load_authority_record(
port: TaskPlaneLineagePort, task_id: str
) -> tuple[TaskRecord, TaskRecord]:
record = _as_task_record(port.get_task_snapshot(task_id))
if record.lineage.relation_type == RelationType.ROOT:
return record, record
root = _as_task_record(port.get_task_snapshot(record.lineage.root_task_id))
current = record
seen = {record.task_id}
while current.lineage.relation_type != RelationType.ROOT:
_validate_record_root_binding(current, root)
parent_id = current.lineage.parent_task_id
if not parent_id or parent_id in seen:
raise IntegrationError("task lineage contains a parent cycle")
seen.add(parent_id)
parent = _as_task_record(port.get_task_snapshot(parent_id))
_validate_record_root_binding(parent, root)
if current.lineage.parent_task_id != parent.task_id:
raise IntegrationError("task lineage parent does not match authority parent")
if current.lineage.lineage_depth != parent.lineage.lineage_depth + 1:
raise IntegrationError("task lineage depth does not match authority parent")
if current.lineage.parent_revision is None or (
current.lineage.parent_revision > parent.revision
):
raise IntegrationError("task lineage parent revision is not authoritative")
current = parent
if current.task_id != root.task_id:
raise IntegrationError("task lineage parent chain ends at a different root")
return record, root
def _validate_dispatch_receipt(
receipt: RootActionDispatchReceipt,
*,
review: RootReviewRecord,
idempotency_key: str,
existing: bool,
) -> None:
if (
receipt.review_id != review.review_id
or receipt.root_task_id != review.root_task_id
or receipt.action != review.next_legal_action
or receipt.idempotency_key != idempotency_key
or receipt.status not in PERSISTED_DISPATCH_STATUSES
):
noun = "existing root-action dispatch" if existing else "root-action dispatch receipt"
raise IntegrationError(f"{noun} conflicts with persisted review")
class _RootReviewStoreAdapter(RootReviewStore):
def __init__(self, port: TaskPlaneLineagePort):
self._port = port
def get_task(self, task_id: str) -> TaskRecord:
record, _ = _load_authority_record(self._port, task_id)
return record
def list_descendants(self, root_task_id: str) -> Sequence[TaskRecord]:
root, _ = _load_authority_record(self._port, root_task_id)
if root.lineage.relation_type != RelationType.ROOT:
raise IntegrationError("descendant lookup key is not a ROOT task")
records: list[TaskRecord] = []
for snapshot in self._port.list_descendant_snapshots(root_task_id):
record, resolved_root = _load_authority_record(self._port, snapshot.task_id)
if resolved_root.task_id != root_task_id:
raise IntegrationError("descendant authority returned a cross-root task")
_validate_record_root_binding(record, root)
records.append(record)
return tuple(records)
def get_root_review(self, review_id: str) -> RootReviewRecord | None:
raw = self._port.get_root_review_record(review_id)
if raw is None:
return None
if not isinstance(raw, Mapping):
raise IntegrationError("persisted root-review record must be a mapping")
try:
return root_review_from_record(
raw,
expected_review_id=review_id,
)
except LineageError as exc:
raise IntegrationError(
f"invalid persisted root-review record: {exc}"
) from exc
def compare_and_swap_root_review(
self,
root_task_id: str,
expected_root_revision: int,
review: RootReviewRecord,
) -> bool:
return self._port.compare_and_swap_root_review_record(
root_task_id=root_task_id,
expected_root_revision=expected_root_revision,
review_record=root_review_to_record(review),
)
class TaskIdentityLineageService:
"""Application service that wires the lineage domain to TaskPlane authority."""
def __init__(self, port: TaskPlaneLineagePort):
self._port = port
def prepare_new_task_identity(
self, request: NewTaskIdentityRequest
) -> PreparedTaskIdentity:
alias = derive_safe_owner_alias(
request.owner_principal_id, request.preferred_owner_alias
)
if request.relation_type == RelationType.ROOT:
if request.parent_task_id is not None:
raise IntegrationError("ROOT task cannot have parent_task_id")
lineage = make_root_lineage(
task_id=request.task_id,
owner_principal_id=request.owner_principal_id,
owner_alias=alias,
created_at=request.created_at,
mission_id=request.mission_id,
goal_slug=request.goal_slug,
root_acceptance_ref=request.root_acceptance_ref,
)
else:
if not request.parent_task_id:
raise IntegrationError("non-root task requires parent_task_id")
parent, root = _load_authority_record(
self._port, request.parent_task_id
)
if request.owner_principal_id != root.lineage.owner_principal_id:
raise IntegrationError("cross-owner child creation rejected")
if request.mission_id != root.lineage.mission_id:
raise IntegrationError("cross-mission child creation rejected")
if request.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise IntegrationError("root acceptance reference mismatch")
sequence = self._port.reserve_sibling_sequence(
parent_task_id=parent.task_id,
relation_type=request.relation_type,
child_task_id=request.task_id,
)
lineage = make_child_lineage(
task_id=request.task_id,
created_at=request.created_at,
parent=parent,
root=root,
relation_type=request.relation_type,
sibling_sequence=sequence,
goal_slug=request.goal_slug,
)
return PreparedTaskIdentity(
task_id=request.task_id,
display_name=lineage.display_name,
lineage=lineage,
metadata_patch={
LINEAGE_METADATA_KEY: lineage_to_metadata(lineage),
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
)
def attach_legacy_task(
self,
*,
task_id: str,
parent_task_id: str,
relation_type: RelationType,
goal_slug: str,
expected_owner_principal_id: str | None = None,
expected_mission_id: str | None = None,
max_cas_retries: int = 4,
) -> TaskLineage:
if max_cas_retries < 1:
raise IntegrationError("max_cas_retries must be >= 1")
for _ in range(max_cas_retries):
current = self._port.get_task_snapshot(task_id)
if (
expected_owner_principal_id is not None
and current.owner_principal_id != expected_owner_principal_id
):
raise IntegrationError("legacy task owner changed after authorization")
if (
expected_mission_id is not None
and current.mission_id != expected_mission_id
):
raise IntegrationError("legacy task mission changed after authorization")
raw = current.metadata.get(LINEAGE_METADATA_KEY)
if isinstance(raw, Mapping):
record, _ = _load_authority_record(self._port, task_id)
lineage = record.lineage
expected_goal_slug = normalize_goal_slug(goal_slug)
if (
lineage.parent_task_id != parent_task_id
or lineage.relation_type != relation_type
or lineage.goal_slug != expected_goal_slug
):
raise IntegrationError(
"existing legacy lineage conflicts with requested binding"
)
display = current.metadata.get(DISPLAY_NAME_METADATA_KEY)
if display == lineage.display_name:
return lineage
if display not in (None, ""):
raise IntegrationError(
"legacy display name conflicts with authoritative lineage"
)
if self._port.compare_and_swap_task_metadata(
task_id=current.task_id,
expected_revision=current.revision,
metadata_patch={
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
):
return lineage
continue
parent, root = _load_authority_record(self._port, parent_task_id)
if root.status == "COMPLETED":
raise IntegrationError("cannot attach lineage below a completed root")
if (
expected_owner_principal_id is not None
and root.lineage.owner_principal_id != expected_owner_principal_id
):
raise IntegrationError("legacy root owner does not match authorization")
if (
expected_mission_id is not None
and root.lineage.mission_id != expected_mission_id
):
raise IntegrationError("legacy root mission does not match authorization")
if current.owner_principal_id != root.lineage.owner_principal_id:
raise IntegrationError("legacy task owner does not match root owner")
if current.mission_id != root.lineage.mission_id:
raise IntegrationError("legacy task mission does not match root mission")
sequence = self._port.reserve_sibling_sequence(
parent_task_id=parent.task_id,
relation_type=relation_type,
child_task_id=current.task_id,
)
lineage = attach_legacy_lineage(
existing_task_id=current.task_id,
created_at=current.created_at,
parent=parent,
root=root,
relation_type=relation_type,
sibling_sequence=sequence,
goal_slug=goal_slug,
)
if self._port.compare_and_swap_task_metadata(
task_id=current.task_id,
expected_revision=current.revision,
metadata_patch={
LINEAGE_METADATA_KEY: lineage_to_metadata(lineage),
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
):
return lineage
raise IntegrationError("legacy lineage CAS retry budget exhausted")
def on_proven_terminal_child(
self,
*,
child_task_id: str,
acceptance_evaluator: AcceptanceEvaluator,
reviewed_at: datetime,
max_cas_retries: int = 4,
) -> TerminalReviewOutcome:
store = _RootReviewStoreAdapter(self._port)
review = review_root_after_child_terminal(
store=store,
trigger_child_task_id=child_task_id,
acceptance_evaluator=acceptance_evaluator,
reviewed_at=reviewed_at,
max_cas_retries=max_cas_retries,
)
existing = self._port.get_root_action_dispatch(review.review_id)
if existing is not None:
idempotency_key = (
f"root-review:{review.review_id}:{review.next_legal_action.value}"
)
_validate_dispatch_receipt(
existing,
review=review,
idempotency_key=idempotency_key,
existing=True,
)
return TerminalReviewOutcome(review=review, dispatch=existing)
idempotency_key = f"root-review:{review.review_id}:{review.next_legal_action.value}"
receipt = self._port.dispatch_root_action(
review=review,
idempotency_key=idempotency_key,
)
_validate_dispatch_receipt(
receipt,
review=review,
idempotency_key=idempotency_key,
existing=False,
)
return TerminalReviewOutcome(review=review, dispatch=receipt)
@@ -27,7 +27,6 @@ CANONICAL_PROFILE = "aios.task-initial-execution.v1"
CANONICAL_PROFILE_VERSION = 1
SUPERSESSION_REASON = "formal-lineage-supersession-after-expired-unclaimed-source"
MAX_TTL = timedelta(minutes=5)
PARAMETERIZED_RECOVERY_NONCE_SCOPE = "aios.task-parameterized-recovery.v1:owner-nonce"
HEX64 = re.compile(r"^[0-9a-f]{64}$")
SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+:-]{2,511}$")
@@ -200,31 +199,6 @@ def _verify_source(row: sqlite3.Row, d: Mapping[str, Any], identity_digest: Call
_fail("SOURCE_GENERATION_CONSTRAINT_MISMATCH")
def _parameterized_recovery_nonce_reserved(
connection: sqlite3.Connection, nonce: str
) -> bool:
"""Share the existing authority nonce domain when C6 NOOP support exists.
Historical lineage databases can predate TaskStore's idempotency table;
their established lineage-only operation remains readable. The C6 NOOP
adapter refuses to activate on those schemas, so an absent table cannot
create a cross-operation uniqueness gap.
"""
tables = {
str(row[0])
for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")
}
if "idempotency" not in tables:
return False
columns = {str(row[1]) for row in connection.execute("PRAGMA table_info(idempotency)")}
if not {"scope", "key", "payload_digest", "response_json", "created_at"}.issubset(columns):
_fail("FORMAL_IDEMPOTENCY_SCHEMA_UNAVAILABLE", 503)
return connection.execute(
"SELECT 1 FROM idempotency WHERE scope=? AND key=?",
(PARAMETERIZED_RECOVERY_NONCE_SCOPE, nonce),
).fetchone() is not None
class TaskLineageSupersessionController:
def __init__(self, db_path: str | Path, verifier: SignatureVerifier, *, clock=None, identity_digest=None):
self.db_path = Path(db_path)
@@ -277,8 +251,6 @@ class TaskLineageSupersessionController:
connection.execute("BEGIN IMMEDIATE")
try:
self._assert_schema(connection)
if _parameterized_recovery_nonce_reserved(connection, decision["nonce"]):
_fail("PARAMETERIZED_RECOVERY_NONCE_CONSUMED")
replay = connection.execute(
"SELECT owner_decision_sha256,receipt_json FROM task_lineage_supersession_receipts WHERE decision_id=? OR nonce=?",
(decision["decision_id"], decision["nonce"]),
@@ -1,464 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import json
from pathlib import Path
import sqlite3
from typing import Callable, Mapping, Protocol, Sequence, runtime_checkable
if not __package__:
from aios_task_lineage import RelationType, RootReviewRecord, lineage_from_metadata
from aios_task_lineage_integration import (
IntegrationError,
LINEAGE_METADATA_KEY,
RootActionDispatchReceipt,
TaskAuthoritySnapshot,
TaskPlaneLineagePort,
)
else:
from .aios_task_lineage import RelationType, RootReviewRecord, lineage_from_metadata
from .aios_task_lineage_integration import (
IntegrationError,
LINEAGE_METADATA_KEY,
RootActionDispatchReceipt,
TaskAuthoritySnapshot,
TaskPlaneLineagePort,
)
class AuthorityAdapterError(IntegrationError):
pass
def _parse_datetime(value: object) -> datetime:
if isinstance(value, datetime):
if value.tzinfo is None or value.utcoffset() is None:
raise AuthorityAdapterError("authority datetime must be timezone-aware")
return value.astimezone(timezone.utc)
if not isinstance(value, str) or not value:
raise AuthorityAdapterError("authority datetime missing")
try:
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise AuthorityAdapterError("authority datetime invalid") from exc
if dt.tzinfo is None or dt.utcoffset() is None:
raise AuthorityAdapterError("authority datetime must include timezone")
return dt.astimezone(timezone.utc)
def _load_json_object(value: object, *, field: str) -> dict[str, object]:
if value in (None, ""):
return {}
if isinstance(value, Mapping):
return dict(value)
if not isinstance(value, str):
raise AuthorityAdapterError(f"{field} must be a JSON object")
try:
obj = json.loads(value)
except json.JSONDecodeError as exc:
raise AuthorityAdapterError(f"{field} is invalid JSON") from exc
if not isinstance(obj, dict):
raise AuthorityAdapterError(f"{field} must decode to an object")
return obj
def _metadata_identity(metadata: Mapping[str, object]) -> tuple[str, str]:
lineage = metadata.get(LINEAGE_METADATA_KEY)
if isinstance(lineage, Mapping):
owner = lineage.get("owner_principal_id")
mission = lineage.get("mission_id")
if isinstance(owner, str) and owner and isinstance(mission, str) and mission:
return owner, mission
# Pre-lineage/legacy compatibility. These keys are accepted only as
# authority-provided metadata; missing values fail closed rather than being
# invented by this adapter.
owner = metadata.get("owner_principal_id")
mission = metadata.get("mission_id")
if not isinstance(owner, str) or not owner:
raise AuthorityAdapterError("task owner_principal_id is not discoverable")
if not isinstance(mission, str) or not mission:
raise AuthorityAdapterError("task mission_id is not discoverable")
return owner, mission
def _metadata_evidence(metadata: Mapping[str, object]) -> tuple[str, ...]:
raw = metadata.get("evidence_refs")
if raw is None:
return ()
if not isinstance(raw, list) or not all(isinstance(x, str) for x in raw):
raise AuthorityAdapterError("evidence_refs must be a string list")
return tuple(dict.fromkeys(raw))
@runtime_checkable
class TaskPlaneReadback(Protocol):
"""Independent authority readback surface.
This may be backed by the TaskPlane query service, a read replica, or a
read-only SQLite authority. It must not mutate task state.
"""
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot: ...
def list_descendant_snapshots(
self, root_task_id: str
) -> Sequence[TaskAuthoritySnapshot]: ...
def get_root_review_record(self, review_id: str) -> Mapping[str, object] | None: ...
def get_root_action_dispatch(self, review_id: str) -> RootActionDispatchReceipt | None: ...
@dataclass(frozen=True)
class CallableTaskPlaneReadback:
"""Readback adapter for an existing official TaskPlane query/service."""
get_task: Callable[[str], TaskAuthoritySnapshot]
list_descendants: Callable[[str], Sequence[TaskAuthoritySnapshot]]
get_review: Callable[[str], Mapping[str, object] | None] | None = None
get_dispatch: Callable[[str], RootActionDispatchReceipt | None] | None = None
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot:
snapshot = self.get_task(task_id)
if not isinstance(snapshot, TaskAuthoritySnapshot):
raise AuthorityAdapterError("readback returned invalid task snapshot")
if snapshot.task_id != task_id:
raise AuthorityAdapterError("readback returned wrong task identity")
return snapshot
def list_descendant_snapshots(
self, root_task_id: str
) -> Sequence[TaskAuthoritySnapshot]:
values = tuple(self.list_descendants(root_task_id))
if not all(isinstance(v, TaskAuthoritySnapshot) for v in values):
raise AuthorityAdapterError("readback returned invalid descendant snapshot")
return values
def get_root_review_record(self, review_id: str) -> Mapping[str, object] | None:
if self.get_review is None:
raise AuthorityAdapterError("independent review readback is not configured")
return self.get_review(review_id)
def get_root_action_dispatch(self, review_id: str) -> RootActionDispatchReceipt | None:
if self.get_dispatch is None:
raise AuthorityAdapterError("independent dispatch readback is not configured")
return self.get_dispatch(review_id)
@dataclass(frozen=True)
class AuthoritySchemaReport:
task_table: str
columns: tuple[str, ...]
required_columns_present: bool
missing_columns: tuple[str, ...]
task_count: int
class ReadOnlySQLiteTaskPlane:
"""Read-only discovery/readback helper for the current TaskPlane SQLite authority.
This class never writes. It opens SQLite read-only, including committed WAL
data, and exists only for discovery, diagnostics and independent readback.
Execution writes must go through the live TaskPlane repository/service and
are supplied separately through ``TaskPlaneServiceBindings``.
"""
REQUIRED_TASK_COLUMNS = (
"task_id",
"status",
"revision",
"metadata_json",
"created_at",
)
def __init__(self, db_path: Path):
self.db_path = Path(db_path)
def _connect(self) -> sqlite3.Connection:
if not self.db_path.is_file():
raise AuthorityAdapterError(f"TaskPlane DB missing: {self.db_path}")
# A live WAL database is not immutable: ignoring its WAL can return
# stale state. as_uri also quotes URI metacharacters in file names.
uri = self.db_path.resolve().as_uri() + "?mode=ro"
con = sqlite3.connect(uri, uri=True)
con.row_factory = sqlite3.Row
return con
def inspect_schema(self) -> AuthoritySchemaReport:
con = self._connect()
try:
rows = list(con.execute("PRAGMA table_info(tasks)"))
columns = tuple(str(r["name"]) for r in rows)
missing = tuple(c for c in self.REQUIRED_TASK_COLUMNS if c not in columns)
count = int(con.execute("SELECT COUNT(*) FROM tasks").fetchone()[0]) if rows else 0
return AuthoritySchemaReport(
task_table="tasks",
columns=columns,
required_columns_present=not missing,
missing_columns=missing,
task_count=count,
)
finally:
con.close()
def _row_to_snapshot(self, row: sqlite3.Row) -> TaskAuthoritySnapshot:
keys = set(row.keys())
metadata = _load_json_object(
row["metadata_json"] if "metadata_json" in keys else None,
field="metadata_json",
)
owner, mission = _metadata_identity(metadata)
created_value = row["created_at"] if "created_at" in keys else None
return TaskAuthoritySnapshot(
task_id=str(row["task_id"]),
revision=int(row["revision"]),
status=str(row["status"]),
owner_principal_id=owner,
mission_id=mission,
created_at=_parse_datetime(created_value),
metadata=metadata,
evidence_refs=_metadata_evidence(metadata),
# A metadata flag cannot stand in for execution-integrity proof.
# The official service adapter must resolve verified receipts.
terminal_evidence_proven=False,
)
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot:
con = self._connect()
try:
row = con.execute("SELECT * FROM tasks WHERE task_id=? LIMIT 1", (task_id,)).fetchone()
if row is None:
raise AuthorityAdapterError(f"task not found: {task_id}")
return self._row_to_snapshot(row)
finally:
con.close()
def list_descendant_snapshots(self, root_task_id: str) -> tuple[TaskAuthoritySnapshot, ...]:
con = self._connect()
try:
rows = list(con.execute("SELECT * FROM tasks ORDER BY task_id"))
finally:
con.close()
out: list[TaskAuthoritySnapshot] = []
for row in rows:
try:
snap = self._row_to_snapshot(row)
except AuthorityAdapterError:
# Legacy tasks without discoverable identity cannot safely be
# attached to a root by this read-only adapter.
continue
raw = snap.metadata.get(LINEAGE_METADATA_KEY)
if (
isinstance(raw, Mapping)
and raw.get("root_task_id") == root_task_id
and snap.task_id != root_task_id
):
out.append(snap)
return tuple(out)
@dataclass(frozen=True)
class TaskPlaneServiceBindings:
"""In-process bindings to the existing TaskPlane authority.
These callables must be backed by the real TaskPlane repository/service,
including its existing transactions, revision/CAS, journal and continuation
mechanisms. They must NOT be implemented as out-of-band SQLite mutations.
"""
get_task_snapshot: Callable[[str], TaskAuthoritySnapshot]
list_descendant_snapshots: Callable[[str], Sequence[TaskAuthoritySnapshot]]
reserve_sibling_sequence: Callable[[str, RelationType, str], int]
compare_and_swap_task_metadata: Callable[[str, int, Mapping[str, object]], bool]
get_root_review_record: Callable[[str], Mapping[str, object] | None]
compare_and_swap_root_review_record: Callable[
[str, int, Mapping[str, object]], bool
]
get_root_action_dispatch: Callable[[str], RootActionDispatchReceipt | None]
dispatch_root_action: Callable[
[RootReviewRecord, str], RootActionDispatchReceipt
]
class InProcessTaskPlaneLineageAdapter(TaskPlaneLineagePort):
"""Concrete TaskPlaneLineagePort backed by existing TaskPlane service calls."""
def __init__(
self,
bindings: TaskPlaneServiceBindings,
*,
readback: TaskPlaneReadback | None = None,
):
self._b = bindings
self._readback = readback
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot:
return self._b.get_task_snapshot(task_id)
def list_descendant_snapshots(self, root_task_id: str) -> Sequence[TaskAuthoritySnapshot]:
return tuple(self._b.list_descendant_snapshots(root_task_id))
def reserve_sibling_sequence(
self,
*,
parent_task_id: str,
relation_type: RelationType,
child_task_id: str,
) -> int:
seq = self._b.reserve_sibling_sequence(
parent_task_id, relation_type, child_task_id
)
if not isinstance(seq, int) or seq < 1:
raise AuthorityAdapterError("TaskPlane returned invalid sibling sequence")
return seq
def compare_and_swap_task_metadata(
self,
*,
task_id: str,
expected_revision: int,
metadata_patch: Mapping[str, object],
) -> bool:
return bool(
self._b.compare_and_swap_task_metadata(
task_id, expected_revision, metadata_patch
)
)
def get_root_review_record(self, review_id: str) -> Mapping[str, object] | None:
return self._b.get_root_review_record(review_id)
def compare_and_swap_root_review_record(
self,
*,
root_task_id: str,
expected_root_revision: int,
review_record: Mapping[str, object],
) -> bool:
return bool(
self._b.compare_and_swap_root_review_record(
root_task_id, expected_root_revision, review_record
)
)
def get_root_action_dispatch(
self, review_id: str
) -> RootActionDispatchReceipt | None:
return self._b.get_root_action_dispatch(review_id)
def dispatch_root_action(
self,
*,
review: RootReviewRecord,
idempotency_key: str,
) -> RootActionDispatchReceipt:
return self._b.dispatch_root_action(review, idempotency_key)
def verify_task_readback(
self,
*,
task_id: str,
expected_revision_at_least: int,
expected_metadata: Mapping[str, object],
) -> TaskAuthoritySnapshot:
if self._readback is None:
raise AuthorityAdapterError("independent TaskPlane readback is not configured")
snap = self._readback.get_task_snapshot(task_id)
if snap.task_id != task_id:
raise AuthorityAdapterError("TaskPlane readback task identity mismatch")
if snap.revision < expected_revision_at_least:
raise AuthorityAdapterError("TaskPlane readback revision did not advance")
for key, expected in expected_metadata.items():
if snap.metadata.get(key) != expected:
raise AuthorityAdapterError(
f"TaskPlane readback metadata mismatch for {key}"
)
return snap
def verify_root_readback(
self,
*,
root_task_id: str,
expected_review_id: str,
) -> tuple[TaskAuthoritySnapshot, ...]:
"""Verify that independent authority descendants remain bound to root.
The persisted ROOT_REVIEW record itself may live in a journal rather
than the task row. This method verifies the task/lineage projection
independently while the service binding verifies the journal receipt.
"""
if self._readback is None:
raise AuthorityAdapterError("independent TaskPlane readback is not configured")
review = self.get_root_review_record(expected_review_id)
dispatch = self.get_root_action_dispatch(expected_review_id)
if (review is None or review.get("review_id") != expected_review_id
or review.get("root_task_id") != root_task_id or dispatch is None):
raise AuthorityAdapterError("root review or dispatch identity mismatch")
read_review = getattr(self._readback, "get_root_review_record", None)
read_dispatch = getattr(self._readback, "get_root_action_dispatch", None)
if not callable(read_review) or not callable(read_dispatch):
raise AuthorityAdapterError("independent review/dispatch readback is not configured")
if read_review(expected_review_id) != review:
raise AuthorityAdapterError("independent root review readback mismatch")
if read_dispatch(expected_review_id) != dispatch:
raise AuthorityAdapterError("independent dispatch readback mismatch")
root = self._readback.get_task_snapshot(root_task_id)
raw_root = root.metadata.get(LINEAGE_METADATA_KEY)
if root.task_id != root_task_id or not isinstance(raw_root, Mapping):
raise AuthorityAdapterError("root readback identity or lineage missing")
root_lineage = lineage_from_metadata(dict(raw_root))
if (root_lineage.relation_type != RelationType.ROOT
or root_lineage.task_id != root_task_id
or root_lineage.owner_principal_id != root.owner_principal_id
or root_lineage.mission_id != root.mission_id):
raise AuthorityAdapterError("root readback authority mismatch")
revision = review.get("root_revision_after")
if type(revision) is not int or root.revision < revision:
raise AuthorityAdapterError("root readback revision is stale")
descendants = tuple(self._readback.list_descendant_snapshots(root_task_id))
seen = set()
for snap in descendants:
raw = snap.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(raw, Mapping):
raise AuthorityAdapterError("descendant lineage missing")
lineage = lineage_from_metadata(dict(raw))
if (snap.task_id in seen or snap.task_id == root_task_id
or lineage.task_id != snap.task_id
or lineage.root_task_id != root_task_id
or lineage.owner_principal_id != root.owner_principal_id
or lineage.mission_id != root.mission_id
or snap.owner_principal_id != root.owner_principal_id
or snap.mission_id != root.mission_id
or lineage.root_acceptance_ref != root_lineage.root_acceptance_ref):
raise AuthorityAdapterError(
f"descendant {snap.task_id} is projected under wrong root"
)
seen.add(snap.task_id)
if review.get("trigger_child_task_id") not in seen:
raise AuthorityAdapterError("trigger child missing from independent readback")
return descendants
@property
def has_independent_readback(self) -> bool:
return self._readback is not None
@dataclass(frozen=True)
class AuthenticatedTaskContext:
principal_id: str
preferred_alias: str | None
mission_id: str
def require_context_matches_task(
context: AuthenticatedTaskContext,
task: TaskAuthoritySnapshot,
) -> None:
if context.principal_id != task.owner_principal_id:
raise AuthorityAdapterError("authenticated principal does not own task")
if context.mission_id != task.mission_id:
raise AuthorityAdapterError("authenticated mission does not match task")
@@ -1,242 +0,0 @@
"""Narrow staging Mission ingress authenticated by the registered Owner signer.
The signed payload binds the method, resource, body and idempotency key. This
adapter neither signs requests nor grants Git/worker/production capabilities.
"""
from __future__ import annotations
import argparse
import importlib.util
import json
from pathlib import Path
import re
import stat
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Any
from .task_plane.errors import (
ForbiddenError, TaskPlaneHTTPError, UnauthorizedError, ValidationError,
)
AUDIENCE = "aios.task-mission-authority.staging.v1"
NAMESPACE = "aios-owner-approval-v1"
MAX_BODY = 131072
REQUEST_FIELDS = frozenset({
"schema", "audience", "method", "path", "body", "idempotency_key",
"issued_at", "expires_at", "nonce",
})
def canonical_bytes(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False).encode("utf-8")
def strict_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValidationError("duplicate JSON key")
result[key] = value
return result
class SignedMissionIngress:
def __init__(
self, service, verifier, *, terminal_proof_importer=None, clock=time.time
):
if not callable(getattr(verifier, "verify", None)):
raise ValueError("registered Owner verifier required")
identity = getattr(verifier, "signer_id", None)
if not isinstance(identity, str) or not identity.strip():
raise ValueError("registered signer identity required")
self.service = service
self.verifier = verifier
self.identity = identity
self.terminal_proof_importer = terminal_proof_importer
self.clock = clock
def handle(self, envelope: dict) -> tuple[int, dict]:
if not isinstance(envelope, dict) or set(envelope) != {
"request", "signature", "key_id"
}:
raise ValidationError("invalid signed mission envelope")
request = envelope["request"]
if not isinstance(request, dict) or set(request) != REQUEST_FIELDS:
raise ValidationError("invalid mission request fields")
if (request["schema"] != "aios.signed-mission-request.v1"
or request["audience"] != AUDIENCE):
raise ForbiddenError("mission request audience mismatch")
issued, expires = request["issued_at"], request["expires_at"]
if (type(issued) is not int or type(expires) is not int
or not 0 < expires - issued <= 300
or not issued <= self.clock() < expires):
raise UnauthorizedError("mission request expired or not yet valid")
if (not isinstance(request["nonce"], str)
or re.fullmatch(r"[A-Za-z0-9_-]{16,128}", request["nonce"]) is None):
raise ValidationError("invalid mission request nonce")
if (not isinstance(envelope["signature"], str)
or not isinstance(envelope["key_id"], str)):
raise UnauthorizedError("Owner signature required")
try:
verified = self.verifier.verify(
namespace=NAMESPACE, payload=canonical_bytes(request),
signature=envelope["signature"], key_id=envelope["key_id"],
)
except Exception:
verified = False
if verified is not True:
raise UnauthorizedError("Owner signature invalid")
method, path, body = request["method"], request["path"], request["body"]
key = request["idempotency_key"]
if method == "POST" and path == "/missions":
if not isinstance(body, dict) or not isinstance(key, str) or not key.strip():
raise ValidationError("mission body and idempotency key required")
return 201, self.service.create_mission(
body, key, actor_identity=self.identity,
)
if (method == "GET" and isinstance(path, str)
and re.fullmatch(r"/missions/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}", path)
and body is None and key is None):
return 200, self.service.get_mission(
path.split("/")[2], actor_identity=self.identity,
)
if (method == "POST" and isinstance(path, str)
and re.fullmatch(r"/tasks/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/delegate", path)):
if not isinstance(body, dict) or not isinstance(key, str) or not key.strip():
raise ValidationError("child body and idempotency key required")
return 201, self.service.delegate_mission(
path.split("/")[2], body, key, actor_identity=self.identity,
)
if (method == "POST" and isinstance(path, str)
and re.fullmatch(
r"/missions/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/legacy-attachments",
path,
)):
if not isinstance(body, dict) or not isinstance(key, str) or not key.strip():
raise ValidationError(
"legacy attachment body and idempotency key required"
)
result = self.service.attach_legacy_attachment(
path.split("/")[2], body, key, actor_identity=self.identity,
)
return (200 if result.get("idempotent") is True else 201), result
if (method == "GET" and isinstance(path, str)
and re.fullmatch(
r"/missions/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/"
r"legacy-attachments/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}",
path,
) and body is None and key is None):
pieces = path.split("/")
return 200, self.service.get_legacy_attachment(
pieces[2], pieces[4], actor_identity=self.identity,
)
if (method == "POST" and isinstance(path, str)
and re.fullmatch(
r"/tasks/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/reconcile-terminal-proof",
path,
)):
if body != {} or not isinstance(key, str) or not key.strip():
raise ValidationError(
"terminal proof trigger requires an empty body and idempotency key"
)
if self.terminal_proof_importer is None:
raise ForbiddenError("terminal proof importer is not configured")
return 200, self.terminal_proof_importer.reconcile(
path.split("/")[2], key, actor_identity=self.identity,
)
raise ForbiddenError("operation outside mission ingress scope")
def load_registered_verifier(path: Path):
metadata = path.lstat()
if (not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != 0
or metadata.st_mode & 0o022):
raise ValueError("Owner verifier must be a root-owned immutable regular file")
spec = importlib.util.spec_from_file_location("aios_mission_owner_verifier", path)
if spec is None or spec.loader is None:
raise ValueError("Owner verifier module unavailable")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.GrantRootOwnerApprovalVerifier()
def make_handler(ingress: SignedMissionIngress):
class Handler(BaseHTTPRequestHandler):
def log_message(self, *_args):
pass # Do not log signed payloads or transport credentials.
def respond(self, status, value):
raw = canonical_bytes(value)
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Cache-Control", "no-store")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_POST(self):
try:
if self.path != "/v1/signed-request":
raise ForbiddenError("unsupported mission ingress route")
if self.headers.get("Transfer-Encoding"):
raise ValidationError("transfer encoding is unsupported")
lengths = self.headers.get_all("Content-Length", [])
if len(lengths) != 1 or not lengths[0].isdigit():
raise ValidationError("one content length required")
length = int(lengths[0])
if not 0 < length <= MAX_BODY:
raise ValidationError("mission request too large or empty")
self.connection.settimeout(10)
raw = self.rfile.read(length)
if len(raw) != length:
raise ValidationError("truncated mission request")
envelope = json.loads(raw, object_pairs_hook=strict_object)
status, result = ingress.handle(envelope)
self.respond(status, result)
except TaskPlaneHTTPError as exc:
self.respond(exc.status_code, exc.to_payload())
except (ValueError, UnicodeError, RecursionError):
self.respond(400, {"error": "invalid_json"})
except Exception:
self.respond(500, {"error": "mission_authority_unavailable"})
return Handler
def main() -> None:
from .task_plane.api import TaskPlaneService
from .task_plane.mission import MissionAuthority, MissionPrincipalBinding
from .task_plane.policy import IdentityVerifier, PolicyEngine
from .task_plane.store import TaskPlaneStore
parser = argparse.ArgumentParser()
parser.add_argument("--db", type=Path, required=True)
parser.add_argument("--owner-verifier", type=Path, required=True)
parser.add_argument("--port", type=int, required=True)
args = parser.parse_args()
if not 1024 <= args.port <= 65535:
parser.error("unprivileged loopback port required")
verifier = load_registered_verifier(args.owner_verifier)
identity = verifier.signer_id
store = TaskPlaneStore(str(args.db), high_assurance=True)
authority = MissionAuthority(store, principal_bindings=[MissionPrincipalBinding(
authenticated_identity=identity, owner_principal_id=identity,
acceptance_verifier_id="identity-lineage-live-closure-v1",
allowed_task_capabilities=(
"code.read", "code.write", "test.execute",
"governed.write.file", "governed.write.branch",
),
max_risk="medium",
)])
service = TaskPlaneService(store, policy=PolicyEngine(
identity_verifier=IdentityVerifier({identity}), execution_enabled=False,
identity_capabilities={identity: {"task.creator", "task.read", "task.delegate"}},
), mission_authority=authority)
ingress = SignedMissionIngress(service, verifier)
# SQLite's existing connection and transaction lock share one request thread.
HTTPServer(("127.0.0.1", args.port), make_handler(ingress)).serve_forever()
if __name__ == "__main__":
main()
@@ -1,317 +0,0 @@
"""Narrow, terminal-only parameterized recovery over the formal Task store.
This adapter deliberately does *not* create a generic recovery authority. It
can only record an Owner-signed, exact-CAS terminal NOOP. A non-terminal
recovery remains the existing formal lineage/supersession operation.
The receipt uses the existing TaskStore ``idempotency`` uniqueness constraint
in the same authoritative database. Two fixed owner-authority scopes bind a
decision id and a nonce atomically; this is not a new nonce database or table.
"""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
import hashlib
import json
import re
import sqlite3
from typing import Any, Callable, Mapping, Protocol
from aios_agent_task_protocol import TaskStore, canonical_json
from aios_task_lineage_supersession import (
OWNER_KEY_ID,
OWNER_NAMESPACE,
OWNER_PRINCIPAL,
REPOSITORY,
)
DECISION_SCHEMA = "aios.task-parameterized-recovery-decision.v1"
RECEIPT_SCHEMA = "aios.task-parameterized-recovery-receipt.v1"
ACTION_TERMINAL_NOOP = "TERMINAL_NOOP"
TERMINAL_STATUSES = frozenset({"COMPLETED", "CANCELLED", "FAILED_FINAL"})
MAX_TTL = timedelta(minutes=5)
NONCE_SCOPE = "aios.task-parameterized-recovery.v1:owner-nonce"
DECISION_SCOPE = "aios.task-parameterized-recovery.v1:owner-decision"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+:-]{2,511}$")
_BRANCH = re.compile(r"^agent/[A-Za-z0-9._/-]{1,220}$")
_FIELDS = frozenset(
{
"schema", "decision_id", "nonce", "action", "task_id",
"task_identity_sha256", "expected_status", "expected_attempt",
"expected_generation", "expected_revision", "expected_claimed_by",
"expected_lease_token_hash", "expected_lease_expires_at", "repository",
"target_branch", "owner_identity", "signature_namespace",
"signature_principal", "key_id", "issued_at", "not_before",
"expires_at", "one_time_use", "signature",
}
)
class ParameterizedRecoveryError(RuntimeError):
def __init__(self, code: str, http_status: int = 409) -> None:
super().__init__(code)
self.code = code
self.http_status = int(http_status)
class SignatureVerifier(Protocol):
def verify(self, *, namespace: str, payload: bytes, signature: str, key_id: str) -> bool: ...
def _fail(code: str, status: int = 409) -> None:
raise ParameterizedRecoveryError(code, status)
def _utc(value: object, field: str) -> datetime:
if not isinstance(value, str) or not value or len(value) > 80:
_fail(f"{field.upper()}_INVALID", 400)
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise ParameterizedRecoveryError(f"{field.upper()}_INVALID", 400) from exc
if parsed.tzinfo is None:
_fail(f"{field.upper()}_INVALID", 400)
return parsed.astimezone(timezone.utc)
def _text(value: object, field: str, *, maximum: int = 1024) -> str:
if not isinstance(value, str) or not value or len(value) > maximum:
_fail(f"{field.upper()}_INVALID", 400)
return value
def canonical_decision_payload(raw: Mapping[str, Any]) -> dict[str, Any]:
return {key: raw[key] for key in raw if key != "signature"}
def _identity_digest(row: sqlite3.Row) -> str:
value = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"priority": row["priority"],
"max_attempts": row["max_attempts"],
}
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def validate_decision(raw: Mapping[str, Any], *, now: datetime) -> dict[str, Any]:
if not isinstance(raw, Mapping) or set(raw) != _FIELDS:
_fail("DECISION_FIELDS_INVALID", 400)
exact = {
"schema": DECISION_SCHEMA,
"action": ACTION_TERMINAL_NOOP,
"repository": REPOSITORY,
"owner_identity": OWNER_PRINCIPAL,
"signature_namespace": OWNER_NAMESPACE,
"signature_principal": OWNER_PRINCIPAL,
"key_id": OWNER_KEY_ID,
"one_time_use": True,
}
for field, expected in exact.items():
if raw[field] != expected:
_fail(f"{field.upper()}_MISMATCH", 403)
for field in ("task_id", "decision_id", "nonce"):
if _SAFE_ID.fullmatch(_text(raw[field], field, maximum=512)) is None:
_fail(f"{field.upper()}_INVALID", 400)
if not isinstance(raw["task_identity_sha256"], str) or not _HEX64.fullmatch(raw["task_identity_sha256"]):
_fail("TASK_IDENTITY_SHA256_INVALID", 400)
if not isinstance(raw["target_branch"], str) or not _BRANCH.fullmatch(raw["target_branch"]):
_fail("TARGET_BRANCH_INVALID", 400)
for field, minimum in (("expected_attempt", 0), ("expected_generation", 1), ("expected_revision", 1)):
if isinstance(raw[field], bool) or not isinstance(raw[field], int) or raw[field] < minimum:
_fail(f"{field.upper()}_INVALID", 400)
for field in ("expected_claimed_by", "expected_lease_token_hash", "expected_lease_expires_at"):
if raw[field] is not None:
_text(raw[field], field, maximum=512)
if raw["expected_status"] not in TERMINAL_STATUSES:
_fail("EXPECTED_STATUS_NOT_TERMINAL", 409)
_text(raw["signature"], "signature", maximum=32768)
issued_at = _utc(raw["issued_at"], "issued_at")
not_before = _utc(raw["not_before"], "not_before")
expires_at = _utc(raw["expires_at"], "expires_at")
if issued_at > not_before or not_before > expires_at:
_fail("DECISION_TIME_ORDER_INVALID", 403)
if expires_at <= issued_at or expires_at - issued_at > MAX_TTL:
_fail("DECISION_TTL_INVALID", 403)
if now < not_before or now >= expires_at or issued_at > now + timedelta(seconds=5) or now - issued_at > MAX_TTL:
_fail("DECISION_EXPIRED_OR_NOT_YET_VALID", 403)
return dict(raw)
class ParameterizedRecoveryController:
"""Consumes a validated terminal-NOOP decision once, in the TaskStore DB."""
def __init__(
self,
store: TaskStore,
verifier: SignatureVerifier,
*,
clock: Callable[[], datetime] | None = None,
identity_digest: Callable[[sqlite3.Row], str] = _identity_digest,
) -> None:
if not isinstance(store, TaskStore) or verifier is None:
_fail("FORMAL_TASK_AUTHORITY_REQUIRED", 503)
self.store = store
self.verifier = verifier
self.clock = clock or (lambda: datetime.now(timezone.utc))
self.identity_digest = identity_digest
def _now(self) -> datetime:
value = self.clock()
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value.astimezone(timezone.utc)
@staticmethod
def _assert_schema(connection: sqlite3.Connection) -> None:
tables = {str(row[0]) for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
if not {"tasks", "events", "idempotency", "task_lineage_supersession_receipts"}.issubset(tables):
_fail("FORMAL_LINEAGE_SCHEMA_UNAVAILABLE", 503)
columns = {str(row[1]) for row in connection.execute("PRAGMA table_info(idempotency)")}
if not {"scope", "key", "payload_digest", "response_json", "created_at"}.issubset(columns):
_fail("FORMAL_IDEMPOTENCY_SCHEMA_UNAVAILABLE", 503)
def _verify_task(self, row: sqlite3.Row, decision: Mapping[str, Any]) -> None:
if self.identity_digest(row) != decision["task_identity_sha256"]:
_fail("TASK_IDENTITY_DIGEST_MISMATCH")
if (
row["task_id"] != decision["task_id"]
or row["status"] != decision["expected_status"]
or int(row["revision"]) != decision["expected_revision"]
or int(row["attempt"]) != decision["expected_attempt"]
or row["claimed_by"] != decision["expected_claimed_by"]
or row["lease_token_hash"] != decision["expected_lease_token_hash"]
or row["lease_expires_at"] != decision["expected_lease_expires_at"]
):
_fail("TASK_EXACT_CAS_MISMATCH")
try:
metadata = json.loads(row["metadata_json"])
constraints = json.loads(row["constraints_json"])
except (TypeError, ValueError) as exc:
raise ParameterizedRecoveryError("TASK_BINDING_JSON_INVALID") from exc
execution = metadata.get("execution") if isinstance(metadata, Mapping) else None
if not isinstance(execution, Mapping) or constraints.get("generation") != decision["expected_generation"]:
_fail("TASK_EXECUTION_BINDING_INVALID")
if (
execution.get("task_id") != decision["task_id"]
or execution.get("repository") != decision["repository"]
or execution.get("target_branch") != decision["target_branch"]
):
_fail("TASK_EXECUTION_BINDING_MISMATCH")
@staticmethod
def _receipt(decision: Mapping[str, Any], decision_sha: str, row: sqlite3.Row, now: datetime) -> dict[str, Any]:
return {
"schema": RECEIPT_SCHEMA,
"state": "NOOP",
"noop_reason": "TERMINAL_STATUS",
"decision_id": decision["decision_id"],
"nonce": decision["nonce"],
"task_id": decision["task_id"],
"status": row["status"],
"revision": int(row["revision"]),
"owner_decision_sha256": decision_sha,
"owner_signature_namespace": OWNER_NAMESPACE,
"owner_signature_principal": OWNER_PRINCIPAL,
"owner_signature_key_id": OWNER_KEY_ID,
"owner_signature_sha256": hashlib.sha256(decision["signature"].encode()).hexdigest(),
"exact_identity_cas": True,
"one_time_use": True,
"observed_at": now.isoformat(),
}
@staticmethod
def _read_exact_replay(
nonce_row: sqlite3.Row | None, decision_row: sqlite3.Row | None, decision: Mapping[str, Any], decision_sha: str
) -> dict[str, Any] | None:
if nonce_row is None and decision_row is None:
return None
if nonce_row is None or decision_row is None:
present = nonce_row if nonce_row is not None else decision_row
if present["payload_digest"] != decision_sha:
_fail("DECISION_REPLAY_MISMATCH")
_fail("RECOVERY_RECEIPT_INCOMPLETE", 500)
if nonce_row["payload_digest"] != decision_sha or decision_row["payload_digest"] != decision_sha:
_fail("DECISION_REPLAY_MISMATCH")
try:
nonce_receipt = json.loads(nonce_row["response_json"])
decision_receipt = json.loads(decision_row["response_json"])
except (TypeError, ValueError) as exc:
raise ParameterizedRecoveryError("RECOVERY_RECEIPT_INVALID", 500) from exc
if nonce_receipt != decision_receipt or (
nonce_receipt.get("schema") != RECEIPT_SCHEMA
or nonce_receipt.get("decision_id") != decision["decision_id"]
or nonce_receipt.get("nonce") != decision["nonce"]
or nonce_receipt.get("task_id") != decision["task_id"]
or nonce_receipt.get("owner_decision_sha256") != decision_sha
):
_fail("DECISION_REPLAY_MISMATCH")
return dict(nonce_receipt)
def recover_terminal_noop(self, raw: Mapping[str, Any]) -> dict[str, Any]:
now = self._now()
decision = validate_decision(raw, now=now)
payload = canonical_decision_payload(raw)
payload_bytes = canonical_json(payload).encode("utf-8")
if not self.verifier.verify(
namespace=OWNER_NAMESPACE,
payload=payload_bytes,
signature=decision["signature"],
key_id=OWNER_KEY_ID,
):
_fail("DECISION_SIGNATURE_INVALID", 403)
decision_sha = hashlib.sha256(payload_bytes).hexdigest()
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
try:
self._assert_schema(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (decision["task_id"],)).fetchone()
if row is None:
_fail("TASK_NOT_FOUND", 404)
self._verify_task(row, decision)
nonce_row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(NONCE_SCOPE, decision["nonce"]),
).fetchone()
decision_row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(DECISION_SCOPE, decision["decision_id"]),
).fetchone()
lineage_row = connection.execute(
"SELECT 1 FROM task_lineage_supersession_receipts WHERE decision_id=? OR nonce=?",
(decision["decision_id"], decision["nonce"]),
).fetchone()
if lineage_row is not None:
_fail("DECISION_REPLAY_MISMATCH")
replay = self._read_exact_replay(nonce_row, decision_row, decision, decision_sha)
if replay is not None:
connection.commit()
return {**replay, "idempotent": True}
receipt = self._receipt(decision, decision_sha, row, now)
receipt_json = canonical_json(receipt)
for scope, key in ((NONCE_SCOPE, decision["nonce"]), (DECISION_SCOPE, decision["decision_id"])):
connection.execute(
"INSERT INTO idempotency(scope,key,payload_digest,response_json,created_at) VALUES(?,?,?,?,?)",
(scope, key, decision_sha, receipt_json, now.isoformat()),
)
connection.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) VALUES(?,?,?,?,?)",
(decision["task_id"], None, "TASK_PARAMETERIZED_RECOVERY_NOOP", receipt_json, now.isoformat()),
)
connection.commit()
return {**receipt, "idempotent": False}
except Exception:
connection.rollback()
raise
# There is intentionally no generic resume/reclaim path. The existing
# lineage controller remains the only predecessor/successor authority.
recover = recover_terminal_noop
@@ -1,167 +0,0 @@
"""Read-only V3 issuer scope over the admitted producer's durable receipt.
The V1/V2 provider remains authoritative for those schemas. V3 validation
and Job readback are delegated to the existing admission producer contract;
this module never reserves a Job or changes the canonical plan digest.
"""
from __future__ import annotations
from pathlib import Path
import stat
from typing import Any, Callable, Mapping
from aios_governed_task_bootstrap import GovernedTaskBootstrap
from aios_task_admission_bootstrap import TaskAdmissionError
from aios_task_admission_live_adapters import (
ProducerReceiptError,
_FLProducerReceiptReserver,
load_v3_plan,
validate_v3_plan,
)
from governed_runtime_scope import (
GovernedScopeError,
RuntimeScope,
_branch,
_generation,
_hex64,
_job_id,
_normalize_caps,
_normalize_files,
_oid40,
_task_id,
_trust_digest,
)
def _deny_mutation(*_args: Any, **_kwargs: Any) -> None:
raise GovernedScopeError("ISSUER_PRODUCER_MUTATION_FORBIDDEN")
def _read_only_bootstrap(root: Path) -> GovernedTaskBootstrap:
"""Construct only the read-only binding view; __init__ creates directories."""
root = Path(root)
for path in (root, root / "task-bindings", root / "jobs"):
metadata = path.lstat()
if (stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode)
or stat.S_IMODE(metadata.st_mode) & 0o022):
raise GovernedScopeError("PRODUCER_STATE_ROOT_INVALID")
view = object.__new__(GovernedTaskBootstrap)
view.root = root
view.task_meta_root = root / "task-bindings"
view.job_root = root / "jobs"
view.create = _deny_mutation
view._store = _deny_mutation
view._write = _deny_mutation
return view
class IssuerV3ReceiptScopeProvider:
"""Delegate legacy plans, and resolve V3 with an existing Job only."""
def __init__(
self, *, legacy: Any, producer_state_root: Path, plan_root: Path,
grant_reader: Callable[..., Mapping[str, Any]],
plan_loader: Callable[[Path, Mapping[str, Any]], Mapping[str, Any]] = load_v3_plan,
) -> None:
self.legacy = legacy
self.producer_state_root = Path(producer_state_root)
self.plan_root = Path(plan_root)
self.grant_reader = grant_reader
self.plan_loader = plan_loader
def inspect_v3_binding(
self, *, grant_raw: Mapping[str, Any], plan: Mapping[str, Any],
) -> tuple[str, Mapping[str, Any]]:
"""Validate original bytes and durable receipt without Task mutation.
This method can inspect an expired historical fixture. ``resolve``
separately enforces live Task and grant state before issuing scope.
"""
try:
original = self.plan_loader(self.plan_root, grant_raw)
if dict(original) != dict(plan):
raise GovernedScopeError("EXECUTION_PLAN_READBACK_CONFLICT")
# Keep the main V3 validator in the issuer contract even when a
# test supplies an isolated loader. It binds the original plan,
# including risk, hashes, capabilities and execution digest.
from aios_task_admission_live_adapters import canonical_json_bytes
validate_v3_plan(grant=grant_raw, raw=canonical_json_bytes(original))
bootstrap = _read_only_bootstrap(self.producer_state_root)
runtime = type("ReadOnlyIssuerRuntime", (), {
"bootstrap": bootstrap, "bindings": {},
})()
reserver = _FLProducerReceiptReserver(runtime)
job_id = reserver.existing_v3_job(grant_raw, original)
if job_id is None:
raise GovernedScopeError("PRODUCER_RECEIPT_MISSING")
readback = reserver.readbacks.get(str(grant_raw["task_id"]))
proof = readback.get("reservation_readback") if isinstance(readback, Mapping) else None
if not isinstance(proof, Mapping) or proof.get("job_id") != job_id:
raise GovernedScopeError("PRODUCER_RECEIPT_BINDING_INVALID")
return job_id, dict(proof)
except (TaskAdmissionError, ProducerReceiptError, OSError, KeyError, TypeError, ValueError) as exc:
raise GovernedScopeError(str(exc) or "PRODUCER_RECEIPT_INVALID") from exc
def resolve(
self, *, task: Any, execution: Any, grant: Any,
plan: Mapping[str, Any], trust: Any, now: int,
) -> RuntimeScope:
if plan.get("schema") != "aios.task-admission-execution-plan.v3":
return self.legacy.resolve(
task=task, execution=execution, grant=grant,
plan=plan, trust=trust, now=now,
)
task_id = _task_id(task.task_id)
exec_job = _job_id(execution.external_job_id)
exec_digest = _hex64(execution.execution_digest, "execution_digest")
exec_branch = _branch(execution.target_branch)
exec_generation = _generation(execution.generation)
grant_raw = self.grant_reader(
task_id=task_id, execution_digest=exec_digest,
repository=execution.repository, target_branch=exec_branch,
generation=exec_generation, now=int(now),
)
producer_job, _proof = self.inspect_v3_binding(
grant_raw=grant_raw, plan=plan,
)
if exec_job != producer_job:
raise GovernedScopeError("EXECUTION_PRODUCER_JOB_MISMATCH")
if str(task.status).upper() not in {"CLAIMED", "RUNNING"}:
raise GovernedScopeError("TASK_NOT_CLAIMED")
if grant.status != "ADMITTED" or int(grant.expires_at) <= int(now):
raise GovernedScopeError("GRANT_EXPIRED_OR_NOT_ADMITTED")
expected = {
"task_id": task_id,
"repository": execution.repository,
"target_branch": exec_branch,
"generation": exec_generation,
"execution_digest": exec_digest,
"authority_base_oid": _oid40(grant.authority_base_oid, "authority_base_oid"),
"grant_id": grant.grant_id,
}
if (execution.repository != "aios/aios"
or execution.task_id != task_id
or any(grant_raw.get(key) != value for key, value in expected.items())):
raise GovernedScopeError("V3_GRANT_EXECUTION_BINDING_MISMATCH")
caps = _normalize_caps(grant.requested_capabilities)
files = _normalize_files(grant.file_allowlist)
if (caps != _normalize_caps(grant_raw["capabilities"])
or files != _normalize_files(grant_raw["file_allowlist"])):
raise GovernedScopeError("V3_GRANT_SCOPE_MISMATCH")
if (not trust.issuer_id or not trust.key_id or not trust.public_key
or "TASK_PRINCIPAL" not in str(trust.purpose).upper().replace("-", "_")
or not str(trust.bridge_id).strip()):
raise GovernedScopeError("TRUST_IDENTITY_INVALID")
trust_digest = _trust_digest(trust)
if (trust.scope_manifest_sha256 is not None
and _hex64(trust.scope_manifest_sha256, "scope_manifest_sha256") != trust_digest):
raise GovernedScopeError("TRUST_MANIFEST_DIGEST_MISMATCH")
return RuntimeScope(
scope_id=f"governed-runtime:{task_id}:g{exec_generation}:{exec_digest[:16]}",
task_id=task_id, external_job_id=producer_job,
repository=execution.repository, target_branch=exec_branch,
generation=exec_generation, execution_digest=exec_digest,
capabilities=caps, trust_manifest_sha256=trust_digest,
grant_id=grant.grant_id, grant_expires_at=int(grant.expires_at),
)
@@ -1,511 +0,0 @@
"""Authenticated, trigger-only import of authoritative terminal proof.
The caller authorizes reconciliation but supplies no proof fields. Proof is
read by a configured server-side provider and is accepted only after the
provider validates an authenticated authority document and its immutable
cross-system tuple.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from hashlib import sha256
import json
import re
from typing import Any, Callable, Mapping, Protocol
from .aios_task_lineage_integration import LINEAGE_METADATA_KEY
from .task_plane.errors import ConflictError, ForbiddenError, ValidationError
from .task_plane.models import ResultEnvelope, TaskRecord
from .task_plane.policy import PolicyDecision
PROOF_SCHEMA = "aios.task-terminal-authority-proof.v1"
RECEIPT_SCHEMA = "aios.task-terminal-import-receipt.v1"
REQUIRED_SOURCES = frozenset(
{
"agent_task",
"admission",
"job_producer",
"execution_alias",
"governed_job",
"bridge",
"authority_git",
}
)
DOCUMENT_FIELDS = frozenset(
{
"schema",
"task_id",
"mission_id",
"root_task_id",
"parent_task_id",
"acceptance_condition",
"agent_task_status",
"admission_status",
"execution_outcome",
"external_job_id",
"internal_job_id",
"repository",
"base_revision",
"target_branch",
"generation",
"execution_plan_digest",
"task_principal_ref",
"task_lease_ref",
"writer_lease_ref",
"governed_commit_oid",
"authority_ref_oid",
"postimage_hashes",
"source_receipt_digests",
"attestation_ref",
"started_at",
"completed_at",
}
)
_HEX64 = re.compile(r"[0-9a-f]{64}")
_GIT_OID = re.compile(r"[0-9a-f]{40}(?:[0-9a-f]{24})?")
_IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/-]{2,255}")
def _canonical_bytes(value: Mapping[str, object]) -> bytes:
return json.dumps(
dict(value), ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
def _nonempty_string(value: object, field: str) -> str:
if not isinstance(value, str) or not value or len(value) > 1024:
raise ForbiddenError(f"authoritative proof {field} invalid")
return value
def _identifier(value: object, field: str) -> str:
text = _nonempty_string(value, field)
if _IDENTIFIER.fullmatch(text) is None:
raise ForbiddenError(f"authoritative proof {field} invalid")
return text
def _utc_time(value: object, field: str) -> str:
text = _nonempty_string(value, field)
try:
parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
except ValueError as exc:
raise ForbiddenError(f"authoritative proof {field} invalid") from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise ForbiddenError(f"authoritative proof {field} lacks timezone")
return parsed.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
@dataclass(frozen=True)
class VerifiedTerminalProof:
document: dict[str, object]
@property
def task_id(self) -> str:
return str(self.document["task_id"])
@property
def attestation_ref(self) -> str:
return str(self.document["attestation_ref"])
@property
def started_at(self) -> str:
return str(self.document["started_at"])
@property
def completed_at(self) -> str:
return str(self.document["completed_at"])
def evidence(self) -> dict[str, object]:
return {
"kind": "acceptance-proof",
"proof_schema": PROOF_SCHEMA,
"authority_document_digest": sha256(
_canonical_bytes(self.document)
).hexdigest(),
"task_id": self.task_id,
"mission_id": self.document["mission_id"],
"root_task_id": self.document["root_task_id"],
"parent_task_id": self.document["parent_task_id"],
"acceptance_condition": self.document["acceptance_condition"],
"external_job_id": self.document["external_job_id"],
"internal_job_id": self.document["internal_job_id"],
"repository": self.document["repository"],
"base_revision": self.document["base_revision"],
"target_branch": self.document["target_branch"],
"generation": self.document["generation"],
"execution_plan_digest": self.document["execution_plan_digest"],
"task_principal_ref": self.document["task_principal_ref"],
"task_lease_ref": self.document["task_lease_ref"],
"writer_lease_ref": self.document["writer_lease_ref"],
"governed_commit_oid": self.document["governed_commit_oid"],
"authority_ref_oid": self.document["authority_ref_oid"],
"postimage_hashes": self.document["postimage_hashes"],
"source_receipt_digests": self.document["source_receipt_digests"],
"attestation_ref": self.attestation_ref,
}
def outputs(self) -> list[dict[str, object]]:
return [
{
"kind": "governed-commit-readback",
"repository": self.document["repository"],
"target_branch": self.document["target_branch"],
"governed_commit_oid": self.document["governed_commit_oid"],
"postimage_hashes": self.document["postimage_hashes"],
}
]
class AuthorityDocumentReader(Protocol):
def __call__(self, task_id: str) -> Mapping[str, object]: ...
class AuthorityDocumentVerifier(Protocol):
def __call__(self, document: Mapping[str, object]) -> bool: ...
class AuthenticatedTerminalProofProvider:
"""Read and validate one aggregate document backed by existing authorities.
``reader`` and ``verifier`` are mandatory runtime bindings. The verifier
must authenticate the referenced AgentTask/admission/Job/Bridge/Git
receipts using already configured trust roots. This class does not confer
trust on an arbitrary mapping.
"""
def __init__(
self,
*,
reader: AuthorityDocumentReader,
verifier: AuthorityDocumentVerifier,
task_reader: Callable[[str], TaskRecord],
worker_identity: str,
) -> None:
if not callable(reader) or not callable(verifier) or not callable(task_reader):
raise ValueError("authenticated proof reader, verifier and task reader required")
self._reader = reader
self._verifier = verifier
self._task_reader = task_reader
self.worker_identity = _identifier(worker_identity, "worker_identity")
@staticmethod
def _parse(raw: Mapping[str, object]) -> dict[str, object]:
if not isinstance(raw, Mapping) or set(raw) != DOCUMENT_FIELDS:
raise ForbiddenError("authoritative proof document shape invalid")
data = dict(raw)
if data["schema"] != PROOF_SCHEMA:
raise ForbiddenError("authoritative proof schema invalid")
for field in (
"task_id", "mission_id", "root_task_id", "parent_task_id",
"external_job_id", "internal_job_id", "task_principal_ref", "task_lease_ref",
"writer_lease_ref",
):
data[field] = _identifier(data[field], field)
for field in ("acceptance_condition", "repository", "base_revision", "target_branch"):
data[field] = _nonempty_string(data[field], field)
if data["agent_task_status"] != "SUCCEEDED":
raise ForbiddenError("AgentTask is not authoritatively successful")
if data["admission_status"] != "ADMITTED":
raise ForbiddenError("task admission is not authoritative")
if data["execution_outcome"] != "APPLIED_VERIFIED":
raise ForbiddenError("governed execution outcome is not verified")
if type(data["generation"]) is not int or int(data["generation"]) < 1:
raise ForbiddenError("authoritative proof generation invalid")
if not isinstance(data["execution_plan_digest"], str) or _HEX64.fullmatch(
data["execution_plan_digest"]
) is None:
raise ForbiddenError("authoritative execution plan digest invalid")
for field in ("governed_commit_oid", "authority_ref_oid"):
if not isinstance(data[field], str) or _GIT_OID.fullmatch(data[field]) is None:
raise ForbiddenError(f"authoritative proof {field} invalid")
if data["governed_commit_oid"] != data["authority_ref_oid"]:
raise ForbiddenError("authority ref does not read back governed commit")
images = data["postimage_hashes"]
if (
not isinstance(images, Mapping)
or not images
or len(images) > 256
or any(
not isinstance(path, str)
or not path
or path.startswith(("/", "\\"))
or ".." in path.replace("\\", "/").split("/")
or not isinstance(digest, str)
or _HEX64.fullmatch(digest) is None
for path, digest in images.items()
)
):
raise ForbiddenError("authoritative postimage hashes invalid")
data["postimage_hashes"] = dict(sorted(images.items()))
sources = data["source_receipt_digests"]
if (
not isinstance(sources, Mapping)
or set(sources) != REQUIRED_SOURCES
or any(
not isinstance(digest, str) or _HEX64.fullmatch(digest) is None
for digest in sources.values()
)
):
raise ForbiddenError("authoritative source receipt set invalid")
data["source_receipt_digests"] = dict(sorted(sources.items()))
data["attestation_ref"] = _nonempty_string(data["attestation_ref"], "attestation_ref")
data["started_at"] = _utc_time(data["started_at"], "started_at")
data["completed_at"] = _utc_time(data["completed_at"], "completed_at")
if datetime.fromisoformat(data["completed_at"].replace("Z", "+00:00")) < datetime.fromisoformat(
data["started_at"].replace("Z", "+00:00")
):
raise ForbiddenError("authoritative proof time order invalid")
return data
@staticmethod
def _assert_task_binding(task: TaskRecord, data: Mapping[str, object]) -> None:
lineage = task.task_plan.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(lineage, Mapping) or lineage.get("relation_type") == "ROOT":
raise ForbiddenError("terminal proof requires a canonical lineage child")
expected = {
"task_id": task.task_id,
"mission_id": task.mission_id,
"root_task_id": task.root_task_id,
"parent_task_id": task.task_plan.parent_task_id,
"acceptance_condition": task.task_plan.metadata.get("acceptance_condition"),
}
if any(data.get(field) != value or value in (None, "") for field, value in expected.items()):
raise ForbiddenError("authoritative proof TaskPlane binding mismatch")
if any(lineage.get(field) != expected[field] for field in expected if field != "acceptance_condition"):
raise ForbiddenError("authoritative proof lineage binding mismatch")
def collect(self, task: TaskRecord) -> VerifiedTerminalProof:
raw = self._reader(task.task_id)
if not isinstance(raw, Mapping) or self._verifier(raw) is not True:
raise ForbiddenError("authoritative terminal proof authentication failed")
data = self._parse(raw)
self._assert_task_binding(task, data)
return VerifiedTerminalProof(data)
def verify_evidence(self, task: TaskRecord, evidences: list[dict]) -> bool:
proof = self.collect(task)
return evidences == [proof.evidence()]
def verify_result(self, result: ResultEnvelope) -> bool:
if result.worker_identity != self.worker_identity or result.status != "COMPLETED":
return False
try:
task = self._task_reader(result.task_id)
proof = self.collect(task)
except Exception:
return False
return bool(
result.attestation_ref == proof.attestation_ref
and result.evidence == [proof.evidence()]
and result.outputs == proof.outputs()
and result.started_at == proof.started_at
and result.completed_at == proof.completed_at
and result.operation_digest == task.task_plan.operation_digest()
)
class TerminalProofEvidenceVerifier:
"""PolicyEngine evidence verifier backed by authenticated authority reads."""
def __init__(self, provider: AuthenticatedTerminalProofProvider) -> None:
self.provider = provider
def verify(self, task: TaskRecord, evidences: list[dict]) -> PolicyDecision:
try:
allowed = self.provider.verify_evidence(task, evidences)
except Exception:
allowed = False
return PolicyDecision(allowed, "" if allowed else "terminal proof evidence invalid")
class TerminalProofImporter:
"""Reconcile a server-verified proof through TaskPlane claim/result paths."""
def __init__(
self,
service: Any,
*,
provider: AuthenticatedTerminalProofProvider | None,
worker_id: str | None,
worker_identity: str | None,
lease_seconds: int = 300,
) -> None:
self.service = service
self.provider = provider
self.worker_id = worker_id
self.worker_identity = worker_identity
if provider is not None:
if not worker_id or not worker_identity:
raise ValueError("terminal proof worker binding required")
_identifier(worker_id, "worker_id")
_identifier(worker_identity, "worker_identity")
if provider.worker_identity != worker_identity:
raise ValueError("terminal proof provider identity mismatch")
if not 30 <= int(lease_seconds) <= 3600:
raise ValueError("terminal proof lease out of range")
self.lease_seconds = int(lease_seconds)
@staticmethod
def expected_idempotency_key(task: TaskRecord) -> str:
return f"terminal-proof:{task.mission_id}:{task.task_id}"
def _authorized_task(self, task_id: str, actor_identity: str) -> TaskRecord:
self.service.get_task(task_id, actor_identity)
task = self.service.store.get_task(task_id)
lineage = task.task_plan.metadata.get(LINEAGE_METADATA_KEY)
if (
not isinstance(lineage, Mapping)
or lineage.get("relation_type") == "ROOT"
or lineage.get("task_id") != task.task_id
or lineage.get("mission_id") != task.mission_id
or lineage.get("root_task_id") != task.root_task_id
or lineage.get("parent_task_id") != task.task_plan.parent_task_id
):
raise ForbiddenError("terminal proof target is not a canonical child")
return task
def _existing_receipt(
self, task: TaskRecord, actor_identity: str
) -> dict[str, object] | None:
if self.service.lineage is None:
raise ForbiddenError("TaskPlane lineage is not configured")
snapshot = self.service.lineage.binding.get_task_snapshot(task.task_id)
if not snapshot.terminal_evidence_proven:
return None
if snapshot.status != "COMPLETED":
raise ConflictError("terminal proof target did not complete successfully")
review = self.service.lineage.binding.get_root_review_for_trigger(task.task_id)
if review is None:
raise ConflictError("terminal proof lacks durable root review")
evidence_rows = self.service.store.list_evidence(task.task_id)
by_id = {str(row.get("evidence_id")): row for row in evidence_rows}
if not snapshot.evidence_refs or any(ref not in by_id for ref in snapshot.evidence_refs):
raise ConflictError("terminal proof evidence readback mismatch")
envelopes = []
for ref in snapshot.evidence_refs:
try:
envelope = json.loads(str(by_id[ref]["envelope_json"]))
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
raise ConflictError("terminal proof evidence readback invalid") from exc
envelopes.append(envelope)
claim_ids = {item.get("claim_id") for item in envelopes}
evidence_payloads = [item.get("payload") for item in envelopes]
if (
len(claim_ids) != 1
or len(evidence_payloads) != 1
or any(item.get("worker_identity") != self.worker_identity for item in envelopes)
or envelopes[0].get("kind") != "acceptance-proof"
or not isinstance(evidence_payloads[0], dict)
or not self.provider.verify_evidence(task, evidence_payloads)
or not self.service.store.verify_evidence_integrity(task.task_id)
or not self.service.store.verify_event_chain(task.task_id)
):
raise ConflictError("terminal proof receipt integrity failed")
claim = self.service.store.get_claim(str(next(iter(claim_ids))))
if (
claim.task_id != task.task_id
or claim.worker_id != self.worker_id
or claim.worker_identity != self.worker_identity
or claim.operation_digest != task.task_plan.operation_digest()
or claim.status != "COMPLETED"
):
raise ConflictError("terminal proof claim binding mismatch")
dispatch = self.service.lineage.binding.get_root_action_dispatch(str(review["review_id"]))
if dispatch is None:
raise ConflictError("terminal proof lacks durable root action dispatch")
body: dict[str, object] = {
"schema": RECEIPT_SCHEMA,
"task_id": task.task_id,
"mission_id": task.mission_id,
"root_task_id": task.root_task_id,
"claim_id": next(iter(claim_ids)),
"task_status": snapshot.status,
"task_revision": snapshot.revision,
"evidence_refs": list(snapshot.evidence_refs),
"review_id": review["review_id"],
"root_revision_after": review["root_revision_after"],
"next_legal_action": review["next_legal_action"],
"dispatch_status": dispatch.status,
"dispatch_idempotency_key": dispatch.idempotency_key,
}
body["receipt_digest"] = sha256(_canonical_bytes(body)).hexdigest()
return body
def _exact_active_claim(self, task: TaskRecord):
active = self.service.store.active_claim(task.task_id)
if active is None:
return None
if (
active.worker_id != self.worker_id
or active.worker_identity != self.worker_identity
or active.operation_digest != task.task_plan.operation_digest()
):
raise ConflictError("active claim is owned by another execution binding")
return active
def reconcile(
self, task_id: str, idempotency_key: str | None, *, actor_identity: str
) -> dict[str, object]:
if self.provider is None:
raise ForbiddenError("terminal proof provider is not configured")
task = self._authorized_task(task_id, actor_identity)
if idempotency_key != self.expected_idempotency_key(task):
raise ValidationError("terminal proof idempotency key mismatch")
existing = self._existing_receipt(task, actor_identity)
if existing is not None:
return existing
proof = self.provider.collect(task)
claim = self._exact_active_claim(task)
if claim is None:
try:
claimed = self.service.claim(
task.task_id,
{
"worker_id": self.worker_id,
"lease_seconds": self.lease_seconds,
"actor_identity": self.worker_identity,
},
)
claim_id = str(claimed["claim_id"])
except Exception:
refreshed = self._authorized_task(task_id, actor_identity)
replay = self._existing_receipt(refreshed, actor_identity)
if replay is not None:
return replay
claim = self._exact_active_claim(refreshed)
if claim is None:
raise
claim_id = claim.claim_id
else:
claim_id = claim.claim_id
try:
self.service.complete(
task.task_id,
{
"claim_id": claim_id,
"actor_identity": self.worker_identity,
"status": "COMPLETED",
"outputs": proof.outputs(),
"evidence": [proof.evidence()],
"started_at": proof.started_at,
"completed_at": proof.completed_at,
"attestation_ref": proof.attestation_ref,
},
)
except Exception:
refreshed = self._authorized_task(task_id, actor_identity)
replay = self._existing_receipt(refreshed, actor_identity)
if replay is None:
raise
return replay
refreshed = self._authorized_task(task_id, actor_identity)
receipt = self._existing_receipt(refreshed, actor_identity)
if receipt is None:
raise ConflictError("terminal proof completion lacks authoritative readback")
return receipt
@@ -24,19 +24,6 @@ class BuilderError(RuntimeError):
"""A stable, non-sensitive error code returned by the builder boundary."""
C5_INTEGRATION_CONTRACT_FIELDS = frozenset({
"integration_source_commit", "integration_tree_oid",
"publication_source_commit", "publication_tree_oid",
"publication_plan_sha256", "repair_source_commit", "repair_tree_oid",
})
C5_INTEGRATION_FILES = {
"active-runtime/app/aios_capability_contract.py": ("publication_source_commit", "67d8a3a296110e20a4ea59863cd7982b6bdada71abd457c0f7cec480b06d278a"),
"tests/test_v2r2c5_capability_contract.py": ("publication_source_commit", "f91af641528ea9a625ca45a46b6c26f5dba568e6b69cba819ca33ab5cba28de3"),
"active-runtime/app/aios_baseline/service.py": ("repair_source_commit", "3c3918aec5503947b48ef4c73e34cac8f0d33fdfb99cfcd5ca8af699b663da72"),
"tests/baseline_v1_1/test_loop.py": ("repair_source_commit", "f867e92b9ec9b4dff248e6ca38280848458a535c610dc6d9fea2b270c29b664a"),
}
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
@@ -148,7 +135,6 @@ class TrustedCandidateBuilderConfig:
policy_sha256: str
source_admission_resolver: Callable[[str, str | None, str], Mapping[str, str]] | None = None
migration_plan_resolver: Callable[[str, str, str], Any] | None = None
c5_contract: Mapping[str, str] | None = None
@classmethod
def from_policy(cls, policy: Mapping[str, Any]) -> "TrustedCandidateBuilderConfig":
@@ -159,52 +145,16 @@ class TrustedCandidateBuilderConfig:
"allowed_peer_runtime_prefix", "allowed_peer_runtime_suffix",
"required_checks", "evidence_layout", "recipes", "governed_recipes",
}
is_integration = policy.get("schema") == "aios.trusted-candidate-builder-policy.c5-integration.v1"
is_c5 = is_integration or policy.get("schema") == "aios.trusted-candidate-builder-policy.c5.v1"
if set(policy) != (required | {"c5_contract"} if is_c5 else required) or policy.get("schema") not in {"aios.trusted-candidate-builder-policy.v1", "aios.trusted-candidate-builder-policy.c5.v1", "aios.trusted-candidate-builder-policy.c5-integration.v1"}:
if set(policy) != required or policy.get("schema") != "aios.trusted-candidate-builder-policy.v1":
raise BuilderError("POLICY_SCHEMA_INVALID")
recipes = policy.get("recipes")
expected_recipes = {"V2R2C2", "V2R2C3", "V2R2C5"} if is_c5 else {"V2R2C2", "V2R2C3"}
if not isinstance(recipes, Mapping) or set(recipes) != expected_recipes:
if not isinstance(recipes, Mapping) or set(recipes) != {"V2R2C2", "V2R2C3"}:
raise BuilderError("POLICY_RECIPES_INVALID")
c2, c3 = recipes["V2R2C2"], recipes["V2R2C3"]
if not isinstance(c2, Mapping) or set(c2) != {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose"} or c2.get("mode") != "fixed":
raise BuilderError("POLICY_V2R2C2_INVALID")
if not isinstance(c3, Mapping) or set(c3) != {"mode", "derives_from", "version", "purpose"} or c3.get("mode") != "governed-derived-only" or c3.get("derives_from") != "V2R2C2":
raise BuilderError("POLICY_V2R2C3_INVALID")
c5_contract = None
if is_c5:
c5 = recipes["V2R2C5"]
expected_mode = "governed-source-integration-only" if is_integration else "governed-source-admission-only"
if not isinstance(c5, Mapping) or set(c5) != {"mode", "version", "purpose"} or c5.get("mode") != expected_mode or c5.get("version") != "V2R2C5" or c5.get("purpose") != "AIOS_TRUSTED_RELEASE_BUILDER":
raise BuilderError("POLICY_V2R2C5_INVALID")
required_c5 = {"task_id", "authority_base_oid", "accepted_base_oid", "rollback_version", "rollback_target_oid", "rollback_artifact_sha256"}
if is_integration:
required_c5 |= C5_INTEGRATION_CONTRACT_FIELDS
value = policy.get("c5_contract")
if not isinstance(value, Mapping) or set(value) != required_c5:
raise BuilderError("POLICY_V2R2C5_CONTRACT_INVALID")
if any(not isinstance(v, str) for v in value.values()):
raise BuilderError("POLICY_V2R2C5_CONTRACT_INVALID")
c5_contract = dict(value)
oid_fields = {"authority_base_oid", "accepted_base_oid", "rollback_target_oid"}
if is_integration:
oid_fields |= {
"integration_source_commit", "integration_tree_oid",
"publication_source_commit", "publication_tree_oid",
"repair_source_commit", "repair_tree_oid",
}
sha_fields = {"rollback_artifact_sha256"}
if is_integration:
sha_fields.add("publication_plan_sha256")
if (
not c5_contract["task_id"]
or c5_contract["rollback_version"] != "V2R2C4"
or c5_contract["accepted_base_oid"] != c5_contract["rollback_target_oid"]
or any(len(c5_contract[name]) != 40 or any(ch not in "0123456789abcdef" for ch in c5_contract[name]) for name in oid_fields)
or any(len(c5_contract[name]) != 64 or any(ch not in "0123456789abcdef" for ch in c5_contract[name]) for name in sha_fields)
):
raise BuilderError("POLICY_V2R2C5_CONTRACT_INVALID")
if (
policy["repository"] != "aios/aios"
or policy["operation"] != "BuildTrustedCandidateV1"
@@ -260,7 +210,6 @@ class TrustedCandidateBuilderConfig:
recipes={name: dict(value) for name, value in recipes.items()},
governed_recipes={name: dict(value) for name, value in policy["governed_recipes"].items()} if isinstance(policy["governed_recipes"], Mapping) else _invalid_governed_recipes(),
policy_sha256=hashlib.sha256(_canonical_json(dict(policy))).hexdigest(),
c5_contract=c5_contract,
)
def fixed_recipe(self, name: str) -> Mapping[str, str]:
@@ -282,36 +231,6 @@ class TrustedCandidateBuilderConfig:
def resolve_recipe(self, version: str, candidate_id: str | None, source_commit: str) -> Mapping[str, str]:
recipe = self.recipes.get(version)
if recipe is not None:
if recipe.get("mode") in {"governed-source-admission-only", "governed-source-integration-only"}:
if version != "V2R2C5" or self.c5_contract is None:
raise BuilderError("SOURCE_ADMISSION_REQUIRED")
resolver = self.source_admission_resolver
if resolver is None:
raise BuilderError("SOURCE_ADMISSION_REQUIRED")
try:
admitted = dict(resolver(version, candidate_id, source_commit))
except Exception as exc:
raise BuilderError("SOURCE_ADMISSION_REQUIRED") from exc
required = {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "tree_oid"}
if set(admitted) != required or admitted.get("mode") != "governed-source-admission" or admitted.get("version") != version or admitted.get("source_commit") != source_commit or admitted.get("purpose") != "AIOS_TRUSTED_RELEASE_BUILDER":
raise BuilderError("SOURCE_ADMISSION_INVALID")
if (not isinstance(admitted.get("candidate_id"), str) or not admitted.get("candidate_id")
or admitted.get("task_id") != self.c5_contract["task_id"]
or admitted.get("base_oid") != self.c5_contract["authority_base_oid"]):
raise BuilderError("SOURCE_ADMISSION_BINDING_INVALID")
effective = str(candidate_id or admitted["candidate_id"])
if effective != admitted["candidate_id"]:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
for name in ("base_oid", "source_commit", "tree_oid"):
value = admitted.get(name)
if not isinstance(value, str) or len(value) != 40 or any(char not in "0123456789abcdef" for char in value.lower()):
raise BuilderError("SOURCE_ADMISSION_INVALID")
if recipe.get("mode") == "governed-source-integration-only" and (
source_commit != self.c5_contract["integration_source_commit"]
or admitted["tree_oid"] != self.c5_contract["integration_tree_oid"]
):
raise BuilderError("SOURCE_INTEGRATION_BINDING_INVALID")
return {**admitted, **self.c5_contract}
resolved = self.fixed_recipe(version)
# Fixed/governed-derived policy recipes remain immutable. A
# source admission can never override one of these identities.
@@ -328,8 +247,6 @@ class TrustedCandidateBuilderConfig:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
return resolved
if self.c5_contract is not None:
raise BuilderError("RECIPE_UNKNOWN")
resolver = self.source_admission_resolver
if resolver is None:
raise BuilderError("RECIPE_UNKNOWN")
@@ -346,9 +263,8 @@ class TrustedCandidateBuilderConfig:
"version",
"purpose",
}
allowed_fields = (required, required | {"tree_oid"}) if version == "V2R2C5" else (required,)
if (
set(admitted) not in allowed_fields
set(admitted) != required
or admitted.get("mode") != "governed-source-admission"
or admitted.get("version") != version
or admitted.get("source_commit") != source_commit
@@ -359,7 +275,7 @@ class TrustedCandidateBuilderConfig:
effective_candidate_id = str(candidate_id or admitted["candidate_id"])
if effective_candidate_id != admitted["candidate_id"]:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
for name in ("base_oid", "source_commit", *(["tree_oid"] if "tree_oid" in admitted else [])):
for name in ("base_oid", "source_commit"):
value = admitted.get(name)
if (
not isinstance(value, str)
@@ -451,15 +367,15 @@ class ServerOwnedEvidenceSource:
raise BuilderError(code) from exc
if not stat.S_ISDIR(metadata.st_mode) or path.is_symlink():
raise BuilderError(code)
if path == self._evidence_root:
if os.name != "nt" and metadata.st_mode & 0o022:
raise BuilderError("COLLECTOR_EVIDENCE_ROOT_MODE_DENIED")
if (
os.name != "nt"
and self._expected_owner_uid is not None
and metadata.st_uid != self._expected_owner_uid
):
raise BuilderError("COLLECTOR_EVIDENCE_ROOT_OWNER_DENIED")
if os.name != "nt" and metadata.st_mode & 0o022:
raise BuilderError("COLLECTOR_EVIDENCE_ROOT_MODE_DENIED")
if (
path == self._evidence_root
and os.name != "nt"
and self._expected_owner_uid is not None
and metadata.st_uid != self._expected_owner_uid
):
raise BuilderError("COLLECTOR_EVIDENCE_ROOT_OWNER_DENIED")
def _digest(self, path: Path) -> str:
digest = hashlib.sha256()
@@ -479,44 +395,7 @@ class ServerOwnedEvidenceSource:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
return value
def _validate_integration_source(self, recipe: Mapping[str, str]) -> None:
if "integration_source_commit" not in recipe:
return
def read(*arguments: str, binary: bool = False):
try:
result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", *arguments],
check=True, capture_output=True, text=not binary, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
return result.stdout if binary else result.stdout.strip()
except (OSError, subprocess.SubprocessError) as exc:
raise BuilderError("COLLECTOR_INTEGRATION_UNAVAILABLE") from exc
source = recipe["source_commit"]
if source != recipe["integration_source_commit"]:
raise BuilderError("COLLECTOR_INTEGRATION_MISMATCH")
for commit_field, tree_field in (
("integration_source_commit", "integration_tree_oid"),
("publication_source_commit", "publication_tree_oid"),
("repair_source_commit", "repair_tree_oid"),
):
if read("rev-parse", f"{recipe[commit_field]}^{{tree}}") != recipe[tree_field]:
raise BuilderError("COLLECTOR_INTEGRATION_MISMATCH")
if read("show", "-s", "--format=%P", source) != recipe["authority_base_oid"]:
raise BuilderError("COLLECTOR_INTEGRATION_MISMATCH")
changed = read("diff", "--name-only", recipe["authority_base_oid"], source).splitlines()
if sorted(changed) != sorted(C5_INTEGRATION_FILES):
raise BuilderError("COLLECTOR_INTEGRATION_FILE_SET_MISMATCH")
for path, (component, digest) in C5_INTEGRATION_FILES.items():
integrated = read("show", f"{source}:{path}", binary=True)
original = read("show", f"{recipe[component]}:{path}", binary=True)
if integrated != original or hashlib.sha256(integrated).hexdigest() != digest:
raise BuilderError("COLLECTOR_INTEGRATION_BYTES_MISMATCH")
def __call__(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
self._validate_integration_source(recipe)
try:
tree_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "rev-parse", f"{recipe['source_commit']}^{{tree}}"],
@@ -533,16 +412,6 @@ class ServerOwnedEvidenceSource:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if getattr(ancestry_result, "returncode", 1) != 0:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
if recipe.get("tree_oid") is not None and recipe.get("tree_oid") != tree_oid:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
if recipe.get("accepted_base_oid") is not None:
accepted_ancestry = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "merge-base", "--is-ancestor", recipe["accepted_base_oid"], recipe["source_commit"]],
check=False, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
if getattr(accepted_ancestry, "returncode", 1) != 0:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
receipt = self._json(self._regular_path(f"{recipe_name}/receipt.json"))
if set(receipt) != {"schema", "version", "candidate", "release_plan"}:
@@ -619,13 +488,6 @@ class ServerOwnedEvidenceSource:
or rollback.get("rollback_sha256") != deployment.get("rollback_sha256")
):
raise BuilderError("COLLECTOR_ROLLBACK_MISMATCH")
if recipe.get("rollback_version") is not None:
if deployment.get("rollback_target") != {
"version": recipe["rollback_version"],
"target_oid": recipe["rollback_target_oid"],
"artifact_sha256": recipe["rollback_artifact_sha256"],
}:
raise BuilderError("COLLECTOR_ROLLBACK_MISMATCH")
if (
digests["gitea"] != hashlib.sha256(_canonical_json(dict(gitea))).hexdigest()
or digests["rollback"] != hashlib.sha256(_canonical_json(dict(rollback))).hexdigest()
+8 -40
View File
@@ -4,7 +4,6 @@ import hashlib, html, json, os, sqlite3, time, uuid, urllib.parse
from http import cookies
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import hmac # SEC-P0-BOOTSTRAP-CREDENTIAL
ROOT = Path(os.getenv('AIOS_WEBOPS_ROOT', Path(__file__).resolve().parents[1]))
DB_PATH = Path(os.getenv('AIOS_WEBOPS_DB', ROOT / 'data' / 'webops.sqlite3'))
@@ -14,11 +13,7 @@ PUBLIC_DOMAIN = os.getenv('AIOS_PUBLIC_DOMAIN','https://cnnetmkt.top')
WEBOPS_DOMAIN = os.getenv('AIOS_WEBOPS_DOMAIN','https://ops.cnnetmkt.top')
API_DOMAIN = os.getenv('AIOS_API_DOMAIN','https://api.cnnetmkt.top')
WECHAT_DOMAIN = os.getenv('AIOS_WECHAT_CALLBACK_DOMAIN','https://wechat.cnnetmkt.top')
SECRET = os.getenv('AIOS_WEBOPS_SECRET') or ''
# SEC-P0: fail closed — never boot with a missing/insecure default secret.
if SECRET in ('', 'change-me-before-production'):
raise SystemExit('AIOS_WEBOPS_SECRET not set (or insecure default). Refusing to start: fail closed.')
SESSION_TTL = int(os.getenv('AIOS_SESSION_TTL','3600'))
SECRET = os.getenv('AIOS_WEBOPS_SECRET','change-me-before-production')
ROLES = ['owner','admin','user']
NODES = ['server-hw','server-local']
PAGES = [('/dashboard','Dashboard'),('/nodes','Nodes'),('/roles','Roles'),('/routing','Routing'),('/failover','Failover'),('/approvals','Approvals'),('/operations','Operations'),('/users','Users'),('/quota','Quota'),('/models','Models'),('/memory','Memory'),('/channels/wechat','WeChat'),('/life','Life Core'),('/patches','Patch Studio'),('/optimization','Optimization'),('/simulation','Simulation'),('/logs','Logs'),('/config','Config'),('/uat','UAT'),('/versions','Versions'),('/evidence','Evidence')]
@@ -48,46 +43,19 @@ def audit(actor, action, target='', payload=None):
exec_sql('INSERT INTO audit_logs(id,actor,action,target,payload,created_at) VALUES(?,?,?,?,?,?)',(uid('audit'),actor,action,target,jdump(payload or {}),now()))
def notify(event_type,payload):
exec_sql('INSERT INTO owner_notifications(id,event_type,payload,status,created_at) VALUES(?,?,?,?,?)',(uid('notify'),event_type,jdump(payload),'queued',now()))
_PBKDF2_ITERS = int(os.getenv('AIOS_PBKDF2_ITERS','260000'))
def _is_pbkdf2(stored): return isinstance(stored,str) and stored.startswith('pbkdf2$') and stored.count('$')==2
def hash_pw(p):
salt=os.urandom(16).hex()
dk=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return f'pbkdf2${salt}${dk}'
def check_pw(stored,p):
if not stored or not isinstance(stored,str) or not stored: return False
if _is_pbkdf2(stored):
_,salt,dk=stored.split('$')
calc=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return hmac.compare_digest(dk,calc)
# Legacy record path: NEVER accept the plaintext form as a credential.
# A legacy (unsalted) sha256 is honored ONLY as a one-time owner migration
# credential and never for the known bootstrap default; anything else is
# rejected so the account must be re-seeded via a secure channel (fail closed).
if stored == p: return False
try:
old=hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
except Exception:
return False
return (hmac.compare_digest(old,stored)) and (p not in {'bootstrap-change-me'})
def hash_pw(p): return hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
def check_pw(stored,p): return stored in (p, hash_pw(p))
def token(username,role):
exp=int(time.time())+SESSION_TTL
data=f'{username}|{role}|{exp}'
sig=hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest()
return data+'|'+sig
data=f'{username}|{role}|{int(time.time())}'; sig=hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]; return data+'|'+sig
def parse_session(header):
if not header: return None
c=cookies.SimpleCookie(); c.load(header)
if 'aios_session' not in c: return None
parts=c['aios_session'].value.split('|')
if len(parts)!=4: return None
username,role,exp,sig=parts
data=f'{username}|{role}|{exp}'
if not hmac.compare_digest(hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest(), sig): return None
try:
if int(exp) <= int(time.time()): return None
except Exception: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(username,role,'approved'))
data='|'.join(parts[:3]); sig=parts[3]
if hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]!=sig: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(parts[0],parts[1],'approved'))
def require(user, roles): return bool(user and user['role'] in roles)
def table(data):
if not data: return '<p class="muted">No records.</p>'
@@ -247,7 +215,7 @@ class H(BaseHTTPRequestHandler):
if path=='/login':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
u=one('SELECT * FROM users WHERE username=? AND status=?',(data.get('username',[''])[0],'approved'))
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Strict"]); return
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Lax"]); return
self.send_body(403,layout('Login Failed','<p>登录失败。</p>',None)); return
if path=='/register':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
+8 -40
View File
@@ -4,7 +4,6 @@ import hashlib, html, json, os, sqlite3, time, uuid, urllib.parse
from http import cookies
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import hmac # SEC-P0-BOOTSTRAP-CREDENTIAL
ROOT = Path(os.getenv('AIOS_WEBOPS_ROOT', Path(__file__).resolve().parents[1]))
DB_PATH = Path(os.getenv('AIOS_WEBOPS_DB', ROOT / 'data' / 'webops.sqlite3'))
@@ -14,11 +13,7 @@ PUBLIC_DOMAIN = os.getenv('AIOS_PUBLIC_DOMAIN','https://cnnetmkt.top')
WEBOPS_DOMAIN = os.getenv('AIOS_WEBOPS_DOMAIN','https://ops.cnnetmkt.top')
API_DOMAIN = os.getenv('AIOS_API_DOMAIN','https://api.cnnetmkt.top')
WECHAT_DOMAIN = os.getenv('AIOS_WECHAT_CALLBACK_DOMAIN','https://wechat.cnnetmkt.top')
SECRET = os.getenv('AIOS_WEBOPS_SECRET') or ''
# SEC-P0: fail closed — never boot with a missing/insecure default secret.
if SECRET in ('', 'change-me-before-production'):
raise SystemExit('AIOS_WEBOPS_SECRET not set (or insecure default). Refusing to start: fail closed.')
SESSION_TTL = int(os.getenv('AIOS_SESSION_TTL','3600'))
SECRET = os.getenv('AIOS_WEBOPS_SECRET','change-me-before-production')
ROLES = ['owner','admin','user']
NODES = ['server-hw','server-local']
PAGES = [('/dashboard','Dashboard'),('/nodes','Nodes'),('/roles','Roles'),('/routing','Routing'),('/failover','Failover'),('/approvals','Approvals'),('/operations','Operations'),('/users','Users'),('/quota','Quota'),('/models','Models'),('/memory','Memory'),('/channels/wechat','WeChat'),('/life','Life Core'),('/patches','Patch Studio'),('/optimization','Optimization'),('/simulation','Simulation'),('/logs','Logs'),('/config','Config'),('/uat','UAT'),('/versions','Versions'),('/evidence','Evidence')]
@@ -48,46 +43,19 @@ def audit(actor, action, target='', payload=None):
exec_sql('INSERT INTO audit_logs(id,actor,action,target,payload,created_at) VALUES(?,?,?,?,?,?)',(uid('audit'),actor,action,target,jdump(payload or {}),now()))
def notify(event_type,payload):
exec_sql('INSERT INTO owner_notifications(id,event_type,payload,status,created_at) VALUES(?,?,?,?,?)',(uid('notify'),event_type,jdump(payload),'queued',now()))
_PBKDF2_ITERS = int(os.getenv('AIOS_PBKDF2_ITERS','260000'))
def _is_pbkdf2(stored): return isinstance(stored,str) and stored.startswith('pbkdf2$') and stored.count('$')==2
def hash_pw(p):
salt=os.urandom(16).hex()
dk=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return f'pbkdf2${salt}${dk}'
def check_pw(stored,p):
if not stored or not isinstance(stored,str) or not stored: return False
if _is_pbkdf2(stored):
_,salt,dk=stored.split('$')
calc=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return hmac.compare_digest(dk,calc)
# Legacy record path: NEVER accept the plaintext form as a credential.
# A legacy (unsalted) sha256 is honored ONLY as a one-time owner migration
# credential and never for the known bootstrap default; anything else is
# rejected so the account must be re-seeded via a secure channel (fail closed).
if stored == p: return False
try:
old=hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
except Exception:
return False
return (hmac.compare_digest(old,stored)) and (p not in {'bootstrap-change-me'})
def hash_pw(p): return hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
def check_pw(stored,p): return stored in (p, hash_pw(p))
def token(username,role):
exp=int(time.time())+SESSION_TTL
data=f'{username}|{role}|{exp}'
sig=hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest()
return data+'|'+sig
data=f'{username}|{role}|{int(time.time())}'; sig=hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]; return data+'|'+sig
def parse_session(header):
if not header: return None
c=cookies.SimpleCookie(); c.load(header)
if 'aios_session' not in c: return None
parts=c['aios_session'].value.split('|')
if len(parts)!=4: return None
username,role,exp,sig=parts
data=f'{username}|{role}|{exp}'
if not hmac.compare_digest(hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest(), sig): return None
try:
if int(exp) <= int(time.time()): return None
except Exception: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(username,role,'approved'))
data='|'.join(parts[:3]); sig=parts[3]
if hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]!=sig: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(parts[0],parts[1],'approved'))
def require(user, roles): return bool(user and user['role'] in roles)
def table(data):
if not data: return '<p class="muted">No records.</p>'
@@ -247,7 +215,7 @@ class H(BaseHTTPRequestHandler):
if path=='/login':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
u=one('SELECT * FROM users WHERE username=? AND status=?',(data.get('username',[''])[0],'approved'))
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Strict"]); return
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Lax"]); return
self.send_body(403,layout('Login Failed','<p>登录失败。</p>',None)); return
if path=='/register':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
@@ -18,10 +18,7 @@ from __future__ import annotations
import argparse
import dataclasses
try:
import fcntl
except ModuleNotFoundError: # pragma: no cover - production host is POSIX
fcntl = None
import fcntl
import hashlib
import json
import os
@@ -379,8 +376,6 @@ class FileLock:
self.handle = None
def __enter__(self) -> "FileLock":
if fcntl is None:
raise ReleaseBlocked("production executor file locking requires POSIX")
self.path.parent.mkdir(parents=True, exist_ok=True)
self.handle = self.path.open("a+", encoding="utf-8")
os.chmod(self.path, 0o600)
@@ -393,7 +388,7 @@ class FileLock:
return self
def __exit__(self, exc_type, exc, tb) -> None:
if self.handle is not None and fcntl is not None:
if self.handle is not None:
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
self.handle.close()
-74
View File
@@ -1,74 +0,0 @@
"""Fixed registered composition of the existing shadow API and renewal store."""
import base64
import importlib.util
import json
from pathlib import Path
import runpy
import sys
ORIGINAL_SHA = 'f6393fe614239036a1d8235c98c1f455dc32b700107d88f48190c4f61dae617e'
def service_type(types, renewal, public_key):
class RegisteredService(types.SilentReleaseActionService):
def __init__(self, root, **kwargs):
if 'completion_approvals' in kwargs:
raise renewal.RenewalRejected('FIXED_COMPLETION_DEPENDENCY_REQUIRED')
original = types.SignedApprovalStore(Path(root) / 'approvals')
completion = renewal.RegisteredCompletionApprovalStore(
original, public_key, types.SignedReleasePlanApproval,
)
super().__init__(root, completion_approvals=completion, **kwargs)
return RegisteredService
def main():
# Original Round3 startup and verifier remain loaded before the new root
# is added. The C5 types are a separate registered module as in R2.
import silent_release_hardening as original
import hashlib
if hashlib.sha256(Path(original.__file__).read_bytes()).hexdigest() != ORIGINAL_SHA:
raise RuntimeError('ROUND3_HARDENING_DRIFT')
if not getattr(original.BuilderAttestationVerifier.verify,
'__aios_round3_rollback_contract_patch__', False):
raise RuntimeError('ROUND3_VERIFIER_NOT_ACTIVE')
import c5_completion_renewal as renewal
renewal.configure_registered_bindings()
root = renewal.AUTHORITY_ROOT
sys.path.insert(0, str(root))
if Path(__file__) != root / 'c5_completion_api.py':
raise renewal.RenewalRejected('REGISTERED_API_WRAPPER_REQUIRED')
policy = renewal.RegisteredCompletionAuthority()()
parent = json.loads(renewal.secure_bytes(Path(policy['predecessor_snapshot'])))
keyring = json.loads(renewal.secure_bytes(root / 'approval-public-keyring.json'))
if keyring.get('algorithm') != 'Ed25519' or parent['key_id'] in keyring.get('revoked', []):
raise renewal.RenewalRejected('REGISTERED_APPROVAL_KEY_REQUIRED')
public_key = base64.b64decode(keyring['keys'][parent['key_id']], validate=True)
renewal.verify_signature(parent, public_key)
spec = importlib.util.spec_from_file_location('_registered_c5_completion_types',
root / 'silent_release_hardening.py')
types = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = types
spec.loader.exec_module(types)
loaded = runpy.run_path(str(root / 'original_api.py'), run_name='_registered_original_api')
g = loaded['main'].__globals__
if g['SILENT_RELEASE_EXECUTION_ENABLED'] is not False or Path(g['SILENT_RELEASE_STATE_ROOT']) != renewal.STATE_ROOT:
raise renewal.RenewalRejected('EXISTING_SHADOW_PLANE_REQUIRED')
g['ReleasePlan'] = types.ReleasePlan
g['TrustedCandidateBundle'] = types.TrustedCandidateBundle
g['SilentReleaseActionService'] = service_type(types, renewal, public_key)
g['TRUSTED_RELEASE_POLICY'] = renewal.TRUSTED_RELEASE_POLICY_PATH
service = g['_build_silent_release_service']()
if service.execution_enabled or service.policy_sha256 != renewal.TRUSTED_RELEASE_POLICY_SHA256:
raise renewal.RenewalRejected('EXACT_API_POLICY_REQUIRED')
# Preserve the exact candidate boundary from the existing registered R2.
def reject_candidate_creation(payload):
raise original.CapabilityAuthorizationError('completion mode cannot create or replace candidates')
service.create_release_candidate = reject_candidate_creation
g['_SILENT_RELEASE_SERVICE'] = service
g['main']()
return 0
if __name__ == '__main__':
raise SystemExit(main())
-100
View File
@@ -1,100 +0,0 @@
"""Completion-only fixed tag and metadata adapter launcher, no runtime effects."""
import argparse
import base64
import io
import json
import os
from pathlib import Path
import runpy
import sys
import urllib.request
UNIT = 'aios-c5-exact-production-host-r1.service'
def main():
parser = argparse.ArgumentParser()
parser.add_argument('adapter')
parser.add_argument('mode', choices=('tag', 'registry', 'baseline'))
args = parser.parse_args()
import c5_completion_renewal as renewal
renewal.configure_registered_bindings()
root = renewal.AUTHORITY_ROOT
sys.path.insert(0, str(root))
from silent_release_hardening import SignedApprovalStore, SignedReleasePlanApproval
if Path(__file__) != root / 'c5_completion_phase.py' or os.geteuid() != 0:
raise renewal.RenewalRejected('REGISTERED_COMPLETION_PHASE_REQUIRED')
policy = renewal.RegisteredCompletionAuthority()()
credentials = Path(os.environ.get('CREDENTIALS_DIRECTORY', ''))
if credentials != Path('/run/credentials') / UNIT:
raise renewal.RenewalRejected('FIXED_COMPLETION_PURPOSE_UNIT_REQUIRED')
if not any(line.split(':', 2)[-1].rstrip('/').endswith('/' + UNIT)
for line in Path('/proc/self/cgroup').read_text().splitlines()):
raise renewal.RenewalRejected('FIXED_COMPLETION_CGROUP_REQUIRED')
module = 'governed_tag_adapter.py' if args.mode == 'tag' else 'release_metadata_adapter.py'
if Path(args.adapter) != ROOT / module:
raise renewal.RenewalRejected('FIXED_COMPLETION_ADAPTER_REQUIRED')
value = json.load(sys.stdin)
fixed = {'schema': 'aios.production-executor-request.v1', 'version': 'V2R2C5',
'target_oid': renewal.SOURCE, 'evidence_closure_oid': renewal.SOURCE,
'artifact_sha256': renewal.ARTIFACT,
'approval_plan_id': 'sha256:d8f64b8b92f9fdb17f5c6053b76a68ee5bcbc2f266d91438c4db7893463079f8',
'repository_identity': 'aios/aios', 'production_service': 'aios-v2r2c2-production.service',
'production_port': 18928}
if args.mode != 'tag':
fixed['tag_oid'] = renewal.TAG_OID
if (set(value) != set(fixed) | {'operation'} or value['operation'] not in {'probe', 'apply'}
or any(value[k] != v for k, v in fixed.items())):
raise renewal.RenewalRejected('EXACT_COMPLETION_REQUEST_REQUIRED')
parent = json.loads(renewal.secure_bytes(Path(policy['predecessor_snapshot'])))
ring = json.loads(renewal.secure_bytes(root / 'approval-public-keyring.json'))
if parent['key_id'] in ring.get('revoked', []):
raise renewal.RenewalRejected('APPROVAL_KEY_REVOKED')
public_key = base64.b64decode(ring['keys'][parent['key_id']], validate=True)
approval = renewal.RegisteredCompletionApprovalStore(
SignedApprovalStore(renewal.STATE_ROOT / 'approvals'), public_key, SignedReleasePlanApproval,
).load(renewal.CID)
if approval.fingerprint == renewal.OLD_FP:
raise renewal.RenewalRejected('FRESH_COMPLETION_SUCCESSOR_REQUIRED')
# Loading the store verifies both actual signatures, registered fixed nonce,
# append-only chain and current successor lifetime. The historical proof
# intentionally remains bound to the original approval, never rewritten.
runtime = json.load(urllib.request.urlopen('http://127.0.0.1:18928/health', timeout=10))
if any(runtime.get(k) != v for k, v in {
'version': 'V2R2C5', 'commit_oid': renewal.SOURCE, 'artifact_sha256': renewal.ARTIFACT,
'deployment_state': 'ACTIVE', 'production_deployed': True}.items()):
raise renewal.RenewalRejected('CURRENT_C5_RUNTIME_REQUIRED')
# Exact signed copies of these historical helpers are read-only here.
# Never call their activation, run(), or verification-phase effect methods.
import production_verification_phase_r1 as business
import public_business_acceptance_probe_r1 as public
business.RegisteredVerificationPhase.business_proof(None, True)
public.reconcile()
os.environ.clear()
os.environ.update(PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
PYTHONDONTWRITEBYTECODE='1', AIOS_GITOPS_WRITES_ENABLED='false',
AIOS_GITOPS_LEGACY_GLOBAL_WRITES_ENABLED='false',
AIOS_GITOPS_CONTROLLER_WORKSPACE=str(renewal.CONTROLLER_WORKSPACE),
AIOS_GITOPS_AUTHORITATIVE_GIT_DIR=str(renewal.AUTHORITATIVE_GIT_DIR),
AIOS_GITOPS_REPOSITORY_IDENTITY='aios/aios',
AIOS_RUNTIME_REGISTRY_ROOT=str(renewal.RUNTIME_REGISTRY_ROOT),
AIOS_RUNTIME_BASELINE_PATH=str(renewal.RUNTIME_BASELINE_PATH))
if args.mode == 'tag':
os.environ.update(GIT_COMMITTER_NAME='owner', GIT_COMMITTER_EMAIL='owner@cnnetmkt.top',
GIT_COMMITTER_DATE='1790826063 +0000', GIT_TERMINAL_PROMPT='0',
GIT_ASKPASS=str(root / 'publisher-askpass.py'))
sys.argv = [module, '--workspace', str(renewal.CONTROLLER_WORKSPACE),
'--repository', str(renewal.AUTHORITATIVE_GIT_DIR), '--remote', 'gitea-http',
'--gitea-api', 'http://127.0.0.1:3000/api/v1', '--gitea-repo', 'aios/aios',
'--gitea-token-file', str(credentials / 'gitea-release-publisher'),
'--state-root', str(renewal.HISTORICAL_ROOT / 'tag-publication')]
else:
sys.argv = [module, '--mode', args.mode, '--expected-previous-baseline', 'V2R2C4',
'--expected-previous-baseline-sha256', 'de0c7008facc44c5061239ee490135b58e521989952918f84a57cecb3358483c',
'--health-url', 'http://127.0.0.1:18928/health']
sys.stdin = io.StringIO(json.dumps(value))
runpy.run_path(str(root / module), run_name='__main__')
if __name__ == '__main__':
main()
-630
View File
@@ -1,630 +0,0 @@
"""Append-only C5 completion renewal boundary; activation requires registration.
The exact Owner-registered API, signer and completion host must call this
boundary. This module does not grant authority or perform deployment effects.
"""
import base64
import hashlib
import json
import math
import os
import stat
import time
from datetime import datetime, timezone
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
CID = 'rc-v2r2c5-5f65e458957d'
# Immutable release content remains compiled into the validator. Renewable
# authority identities and filesystem locations are installed only from the
# digest-bound, root-owned registration file (see configure_registered_bindings).
OLD_FP = '0' * 64
SOURCE = '5f65e458957dd51c9973cfa956a624f3cbf54990'
TREE = 'ddd239d43d63a7fbd3cff46119c71466c9443737'
ARTIFACT = '7f9d8409f07a5e0d36f5db7c55eb82ea70935fa731e057998f29cd1a5ca0a9ad'
PLAN = '370521afcf9b2288f6ad3803ca3554a46ee05d2042d53b19b4f4cb80e37a9684'
MUTABLE = {'issued_at', 'not_before', 'expires_at', 'nonce', 'fingerprint', 'signature'}
CAPABILITIES = ['baseline_registry_finalize', 'runtime_switch', 'tag_publish']
AUTHORITY_ROOT = Path('/unconfigured/c5-completion-authority')
STATE_ROOT = Path('/unconfigured/c5-completion-state')
OWNER_KEY = 'UNCONFIGURED'
TAG_OID = '6d1745ff496103db7e721a2b2bc624fda52c5dc8'
SAGA_HEAD = '0' * 64
HISTORICAL_ROOT = Path('/unconfigured/c5-completion-history')
CONTROLLER_WORKSPACE = Path('/unconfigured/c5-controller-workspace')
AUTHORITATIVE_GIT_DIR = Path('/unconfigured/c5-authoritative-git')
RUNTIME_REGISTRY_ROOT = Path('/unconfigured/c5-runtime-registry')
RUNTIME_BASELINE_PATH = Path('/unconfigured/c5-runtime-baseline.json')
TRUSTED_RELEASE_POLICY_PATH = Path('/unconfigured/c5-trusted-release-policy.json')
TRUSTED_RELEASE_POLICY_SHA256 = '0' * 64
BINDINGS_PATH_ENV = 'AIOS_C5_COMPLETION_BINDINGS_PATH'
BINDINGS_SHA256_ENV = 'AIOS_C5_COMPLETION_BINDINGS_SHA256'
_BINDINGS_DIGEST = None
RENEWAL_SOURCE_FILES = ('c5_completion_renewal.py', 'c5_release_execution_host.py',
'silent_release_hardening.py', 'c5_completion_signer.py',
'c5_completion_api.py', 'c5_completion_phase.py') # gitleaks:allow -- filenames only
PHASE_DEPENDENCIES = ('governed_tag_adapter.py', 'release_metadata_adapter.py',
'production_verification_phase_r1.py', 'public_business_acceptance_probe_r1.py',
'publisher-askpass.py', 'c5_staging_installation.py',
'aios_single_host_authority.py', 'registered_owner_signer_adapter.py')
class RenewalRejected(ValueError):
pass
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False).encode()
def digest(raw):
return hashlib.sha256(raw).hexdigest()
def validate_completion_lineage(gate):
"""Bind a current completion-only journal suffix to the frozen Saga15 head.
The suffix is authenticated by the enclosing registered Owner contract;
these checks preserve its origin and exclude deployment/source transitions.
"""
revision = gate.get('saga_revision')
suffix = gate.get('completion_journal_suffix', [])
observed = gate.get('observed_at')
if (type(revision) is not int or revision < 15 or not isinstance(suffix, list)
or type(observed) not in {int, float} or not math.isfinite(observed)):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
if revision == 15:
if gate.get('saga_head') != SAGA_HEAD or suffix:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
return
if not 0 < len(suffix) <= 64 or len(suffix) != revision - 15:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
allowed = {
'owner_registered_completion_successor_tag_reconciliation': ('TAG_PENDING', {'RECONCILIATION_REQUIRED'}),
'owner_registered_tag_capability_successor_verified': ('TAG_VERIFIED', {'RECONCILIATION_REQUIRED', 'TAG_PENDING'}),
'baseline_registry_finalization_pending': ('BASELINE_REGISTRY_PENDING', {'TAG_VERIFIED'}),
'owner_registered_completion_successor_metadata_reconciliation': ('BASELINE_REGISTRY_PENDING', {'RECONCILIATION_REQUIRED'}),
'release_reconciliation_required': ('RECONCILIATION_REQUIRED', {'TAG_PENDING', 'TAG_VERIFIED', 'BASELINE_REGISTRY_PENDING'}),
}
previous, state = SAGA_HEAD, 'RECONCILIATION_REQUIRED'
for offset, event in enumerate(suffix, 16):
if (not isinstance(event, dict)
or set(event) != {'schema_version', 'sequence', 'previous_hash', 'payload', 'event_hash'}
or type(event['schema_version']) is not int or event['schema_version'] != 1
or type(event['sequence']) is not int or event['sequence'] != offset + 1
or event['previous_hash'] != previous):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
payload = event['payload']
if (not isinstance(payload, dict)
or set(payload) != {'candidate_id', 'release_plan_id', 'revision', 'state', 'event', 'timestamp', 'details'}
or payload['candidate_id'] != CID
or payload['release_plan_id'] != 'pending-' + CID
or type(payload['revision']) is not int or payload['revision'] != offset
or not isinstance(payload['details'], dict)
or not isinstance(payload['event'], str) or payload['event'] not in allowed):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
target, origins = allowed[payload['event']]
if payload['state'] != target or state not in origins:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
if ('release_plan_sha256' in payload['details']
and payload['details']['release_plan_sha256'] != PLAN):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
try:
if timestamp(payload['timestamp']) > gate['observed_at']:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
actual = digest(canonical({key: event[key] for key in ('sequence', 'previous_hash', 'payload')}))
except (TypeError, ValueError, AttributeError, KeyError) as exc:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID') from exc
if event['event_hash'] != actual:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
previous, state = actual, target
if gate.get('saga_head') != previous:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
def timestamp(value):
parsed = datetime.fromisoformat(value.replace('Z', '+00:00'))
if parsed.tzinfo is None or parsed.utcoffset().total_seconds() != 0:
raise RenewalRejected('UTC_REQUIRED')
return parsed.timestamp()
def verify_signature(approval, public_key):
payload = {k: v for k, v in approval.items() if k not in {'signature', 'fingerprint'}}
raw = canonical(payload)
if approval.get('fingerprint') != digest(raw):
raise RenewalRejected('APPROVAL_DIGEST_INVALID')
Ed25519PublicKey.from_public_bytes(public_key).verify(
base64.b64decode(approval['signature'], validate=True), raw
)
def validate_successor(old, new, public_key, *, now):
"""Validate historical predecessor and fresh identical-binding successor.
Historical signature verification does not authorize an expired effect.
The caller must separately verify its fresh registered Owner capability.
"""
verify_signature(old, public_key)
verify_signature(new, public_key)
exact = {'candidate_id': CID, 'source_commit': SOURCE, 'tree_oid': TREE,
'artifact_sha256': ARTIFACT, 'release_plan_sha256': PLAN,
'fingerprint': OLD_FP, 'capabilities': CAPABILITIES}
if any(old.get(k) != v for k, v in exact.items()):
raise RenewalRejected('EXACT_PREDECESSOR_REQUIRED')
if set(old) != set(new) or any(new[k] != old[k] for k in set(old) - MUTABLE):
raise RenewalRejected('IMMUTABLE_APPROVAL_BINDING_DRIFT')
if timestamp(old['expires_at']) > now:
raise RenewalRejected('PREDECESSOR_NOT_EXPIRED')
issued, not_before, expires = (timestamp(new[k]) for k in ['issued_at', 'not_before', 'expires_at'])
if not issued <= not_before <= now < expires or not 0 < expires - issued <= 900:
raise RenewalRejected('FRESH_BOUNDED_APPROVAL_REQUIRED')
if new['nonce'] == old['nonce'] or new['fingerprint'] == old['fingerprint']:
raise RenewalRejected('FRESH_APPROVAL_IDENTITY_REQUIRED')
return new['fingerprint']
def secure_bytes(path, owner_uid=0):
path = Path(path)
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
with os.fdopen(descriptor, 'rb') as stream:
metadata = os.fstat(stream.fileno())
if not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != owner_uid or metadata.st_mode & 0o022:
raise RenewalRejected('UNSAFE_RENEWAL_FILE')
return stream.read()
def _safe_parents(path):
"""Reject writable or symlinked ancestors before opening authority files."""
path = Path(path)
if not path.is_absolute() or '..' in path.parts:
raise RenewalRejected('ABSOLUTE_CANONICAL_PATH_REQUIRED')
for parent in path.parents:
info = parent.lstat()
if not stat.S_ISDIR(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022:
raise RenewalRejected('UNSAFE_AUTHORITY_ANCESTOR')
def _sha(value):
return isinstance(value, str) and len(value) == 64 and all(c in '0123456789abcdef' for c in value)
def _oid(value):
return isinstance(value, str) and len(value) == 40 and all(c in '0123456789abcdef' for c in value)
def configure_registered_bindings(path=None, expected_sha256=None, *, owner_uid=0):
"""Install renewable completion bindings from one authenticated file.
No historical approval fingerprint, Saga head, Owner key, or authority/state
path is a runtime default. The service registration must provide both the
absolute file path and its SHA-256 digest.
"""
global _BINDINGS_DIGEST
path = Path(path or os.environ.get(BINDINGS_PATH_ENV, ''))
expected_sha256 = expected_sha256 or os.environ.get(BINDINGS_SHA256_ENV, '')
if not path.is_absolute() or not _sha(expected_sha256):
raise RenewalRejected('REGISTERED_COMPLETION_BINDINGS_REQUIRED')
_safe_parents(path)
raw = secure_bytes(path, owner_uid=owner_uid)
if digest(raw) != expected_sha256:
raise RenewalRejected('COMPLETION_BINDINGS_DIGEST_MISMATCH')
if _BINDINGS_DIGEST is not None and _BINDINGS_DIGEST != expected_sha256:
raise RenewalRejected('COMPLETION_BINDINGS_REBIND_FORBIDDEN')
try:
value = json.loads(raw)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RenewalRejected('COMPLETION_BINDINGS_INVALID') from exc
fields = {
'schema', 'authority_root', 'state_root', 'owner_key_id',
'predecessor_fingerprint', 'saga_head', 'historical_root',
'controller_workspace', 'authoritative_git_dir',
'runtime_registry_root', 'runtime_baseline_path',
'trusted_release_policy_path', 'trusted_release_policy_sha256',
}
if not isinstance(value, dict) or set(value) != fields or value.get('schema') != 'aios.c5.completion-bindings.v1':
raise RenewalRejected('COMPLETION_BINDINGS_SCHEMA_INVALID')
authority_root = Path(value['authority_root'])
state_root = Path(value['state_root'])
governed_paths = {
name: Path(value[name]) for name in (
'historical_root', 'controller_workspace', 'authoritative_git_dir',
'runtime_registry_root', 'runtime_baseline_path', 'trusted_release_policy_path',
)
}
owner_key_id = value['owner_key_id']
try:
owner_key_valid = (isinstance(owner_key_id, str)
and owner_key_id.startswith('SHA256:')
and len(base64.b64decode(owner_key_id[7:], validate=True)) == 32)
except (ValueError, TypeError):
owner_key_valid = False
all_paths = (authority_root, state_root, *governed_paths.values())
if (any(not item.is_absolute() or '..' in item.parts for item in all_paths)
or not _sha(value['predecessor_fingerprint']) or not _sha(value['saga_head'])
or not _sha(value['trusted_release_policy_sha256'])
or not owner_key_valid):
raise RenewalRejected('COMPLETION_BINDINGS_VALUE_INVALID')
if path.parent != authority_root:
raise RenewalRejected('COMPLETION_BINDINGS_ROOT_MISMATCH')
global AUTHORITY_ROOT, STATE_ROOT, OWNER_KEY, OLD_FP, SAGA_HEAD
global HISTORICAL_ROOT, CONTROLLER_WORKSPACE, AUTHORITATIVE_GIT_DIR
global RUNTIME_REGISTRY_ROOT, RUNTIME_BASELINE_PATH
global TRUSTED_RELEASE_POLICY_PATH, TRUSTED_RELEASE_POLICY_SHA256
AUTHORITY_ROOT = authority_root
STATE_ROOT = state_root
OWNER_KEY = value['owner_key_id']
OLD_FP = value['predecessor_fingerprint']
SAGA_HEAD = value['saga_head']
HISTORICAL_ROOT = governed_paths['historical_root']
CONTROLLER_WORKSPACE = governed_paths['controller_workspace']
AUTHORITATIVE_GIT_DIR = governed_paths['authoritative_git_dir']
RUNTIME_REGISTRY_ROOT = governed_paths['runtime_registry_root']
RUNTIME_BASELINE_PATH = governed_paths['runtime_baseline_path']
TRUSTED_RELEASE_POLICY_PATH = governed_paths['trusted_release_policy_path']
TRUSTED_RELEASE_POLICY_SHA256 = value['trusted_release_policy_sha256']
_BINDINGS_DIGEST = expected_sha256
return dict(value)
class RegisteredCompletionAuthority:
"""Fixed root registration, Owner signature and immutable evidence verifier.
The completed production proofs are historical facts. This fresh authority
permits only approval renewal and remaining metadata effects; it cannot
authorize another runtime switch or public acceptance Task.
"""
@property
def root(self):
return AUTHORITY_ROOT
@property
def state_root(self):
return STATE_ROOT
def __call__(self):
from c5_staging_installation import RegistryOwnerVerifier
root = AUTHORITY_ROOT
_safe_parents(root / 'contract.json')
envelope = json.loads(secure_bytes(root / 'contract.json'))
if set(envelope) != {'payload', 'signature'}:
raise RenewalRejected('EXACT_AUTHORITY_ENVELOPE_REQUIRED')
p = envelope['payload']
fields = {'schema', 'host', 'repository', 'candidate_id', 'source_commit', 'tree_oid',
'artifact_sha256', 'release_plan_sha256', 'state_root', 'predecessor_fingerprint',
'predecessor_sha256', 'predecessor_uid', 'predecessor_snapshot', 'approval_nonce',
'issued_at', 'expires_at', 'owner_key_id', 'owner_namespace', 'allowed_effects',
'runtime_effects_allowed', 'files', 'gate_path', 'gate_observed_at',
'governed_source_commit', 'governed_source_tree'}
if not isinstance(p, dict) or set(p) != fields:
raise RenewalRejected('EXACT_AUTHORITY_SCHEMA_REQUIRED')
exact = {'schema': 'aios.c5.registered-completion-authority.v1', 'host': 'cn-network-mkt',
'repository': 'aios/aios', 'candidate_id': CID, 'source_commit': SOURCE,
'tree_oid': TREE, 'artifact_sha256': ARTIFACT, 'release_plan_sha256': PLAN,
'state_root': str(STATE_ROOT), 'predecessor_fingerprint': OLD_FP,
'owner_key_id': OWNER_KEY, 'owner_namespace': 'aios-owner-approval-v1',
'allowed_effects': ['approval_successor_append', 'baseline_finalize', 'registry_finalize', 'tag_publish'],
'runtime_effects_allowed': False}
if any(p[k] != v for k, v in exact.items()) or type(p['runtime_effects_allowed']) is not bool:
raise RenewalRejected('EXACT_COMPLETION_AUTHORITY_REQUIRED')
now = time.time()
if (type(p['issued_at']) is not int or type(p['expires_at']) is not int
or not p['issued_at'] <= now < p['expires_at']
or not 0 < p['expires_at'] - p['issued_at'] <= 1800
or type(p['gate_observed_at']) not in {int, float}
or not math.isfinite(p['gate_observed_at'])
or not p['issued_at'] - 60 <= p['gate_observed_at'] <= p['issued_at']):
raise RenewalRejected('FRESH_OWNER_AUTHORITY_REQUIRED')
if (not _sha(p['approval_nonce']) or not _sha(p['predecessor_sha256'])
or type(p['predecessor_uid']) is not int or p['predecessor_uid'] not in {0, 1000}
or any(not isinstance(p[k], str) or len(p[k]) != 40
or any(c not in '0123456789abcdef' for c in p[k])
for k in ('governed_source_commit', 'governed_source_tree'))):
raise RenewalRejected('AUTHORITY_IDENTITIES_INVALID')
verifier = RegistryOwnerVerifier(Path('/etc/aios/single-host-signer-registry.json'),
owner_signer_id='local-owner-authority')
if not verifier.verify(namespace=p['owner_namespace'], payload=canonical(p),
signature=envelope['signature'], key_id=p['owner_key_id']):
raise RenewalRejected('OWNER_SIGNATURE_REJECTED')
payload_sha = digest(canonical(p))
registered = json.loads(secure_bytes(root / 'registration.json'))
if (set(registered) != {'schema', 'status', 'payload_sha256', 'approval_nonce'}
or registered != {'schema': 'aios.c5.completion-registration.v1', 'status': 'REGISTERED',
'payload_sha256': payload_sha, 'approval_nonce': p['approval_nonce']}):
raise RenewalRejected('ROOT_REGISTRATION_REQUIRED')
if (root / 'rollback.json').exists():
raise RenewalRejected('REGISTRATION_ROLLED_BACK')
pins = p['files']
if not isinstance(pins, list) or not pins:
raise RenewalRejected('SIGNED_FILE_CLOSURE_REQUIRED')
checked = {}
for item in pins:
if (not isinstance(item, dict) or set(item) != {'path', 'sha256', 'uid'}
or not _sha(item['sha256']) or type(item['uid']) is not int or item['uid'] != 0):
raise RenewalRejected('SIGNED_FILE_PIN_INVALID')
path = Path(item['path'])
if path.parent != root or str(path) in checked:
raise RenewalRejected('SIGNED_FILE_PATH_INVALID')
_safe_parents(path)
raw = secure_bytes(path)
if digest(raw) != item['sha256']:
raise RenewalRejected('SIGNED_FILE_BYTES_DRIFT')
checked[str(path)] = raw
required = {str(root / name) for name in (*RENEWAL_SOURCE_FILES, *PHASE_DEPENDENCIES, 'source-material.json',
'historical-production-proof.json', 'predecessor-approval.json', 'current-gate.json',
'approval-public-keyring.json', 'original_api.py')}
if not required <= set(checked):
raise RenewalRejected('INCOMPLETE_REGISTERED_CLOSURE')
if Path(__file__) != root / 'c5_completion_renewal.py':
raise RenewalRejected('UNREGISTERED_AUTHORITY_MODULE')
if (p['predecessor_snapshot'] != str(root / 'predecessor-approval.json')
or p['gate_path'] != str(root / 'current-gate.json')
or digest(checked[p['predecessor_snapshot']]) != p['predecessor_sha256']):
raise RenewalRejected('REGISTERED_SNAPSHOT_DRIFT')
material = json.loads(checked[str(root / 'source-material.json')])
if (material.get('status') != 'MATERIALIZED' or material.get('task_status') != 'SUCCEEDED'
or material.get('remote_verified') is not True
or material.get('source_commit') != p['governed_source_commit']
or material.get('source_tree') != p['governed_source_tree']
or material.get('file_sha256') != {
name: digest(checked[str(root / name)])
for name in RENEWAL_SOURCE_FILES}):
raise RenewalRejected('GOVERNED_CODE_PROVENANCE_REQUIRED')
historical = json.loads(checked[str(root / 'historical-production-proof.json')])
if (historical.get('candidate_id') != CID or historical.get('approval_fingerprint') != OLD_FP
or historical.get('release_plan_sha256') != PLAN
or historical.get('status') != 'COMPLETED'
or any(historical.get(k) is not True for k in (
'public_business_verified', 'real_c4_rollback_verified', 'c5_restore_verified'))):
raise RenewalRejected('COMPLETED_PRODUCTION_PROOF_REQUIRED')
gate = json.loads(checked[p['gate_path']])
expected_gate = {'candidate_id': CID, 'source_commit': SOURCE, 'artifact_sha256': ARTIFACT,
'release_plan_sha256': PLAN,
'local_tag_oid': TAG_OID, 'runtime_verified': True, 'business_verified': True,
'global_writes_enabled': False, 'observed_at': p['gate_observed_at']}
if (any(gate.get(k) != v for k, v in expected_gate.items())
or any(type(gate.get(k)) is not bool for k in (
'runtime_verified', 'business_verified', 'global_writes_enabled'))
or gate.get('remote_tag_oid') not in {None, TAG_OID}):
raise RenewalRejected('EXACT_POSTVERIFICATION_GATE_REQUIRED')
validate_completion_lineage(gate)
return {**p, 'payload_sha256': payload_sha}
def validate_host(self, *, state_root, candidate_id, source_commit, tree_oid,
artifact_sha256, release_plan_sha256):
self()
if (Path(state_root), candidate_id, source_commit, tree_oid, artifact_sha256, release_plan_sha256) != (
STATE_ROOT, CID, SOURCE, TREE, ARTIFACT, PLAN):
raise RenewalRejected('COMPLETION_HOST_BINDING_DRIFT')
def deny_runtime_effect(self, **kwargs):
self()
raise RenewalRejected('COMPLETION_RUNTIME_EFFECT_FORBIDDEN')
class CompletionRuntimeGuard:
def __init__(self, authority):
if type(authority) is not RegisteredCompletionAuthority:
raise RenewalRejected('REGISTERED_COMPLETION_AUTHORITY_REQUIRED')
self.authority = authority
def execute(self, **kwargs):
return self.authority.deny_runtime_effect(**kwargs)
def append_file(path, raw):
"""An existing record must be identical; it is never replaced."""
path = Path(path)
try:
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
except FileExistsError:
if secure_bytes(path, owner_uid=os.geteuid()) != raw:
raise RenewalRejected('APPEND_ONLY_RECORD_CONFLICT')
return False
with os.fdopen(fd, 'wb') as stream:
stream.write(raw)
stream.flush()
os.fsync(stream.fileno())
fd = os.open(path.parent, os.O_DIRECTORY)
try:
os.fsync(fd)
finally:
os.close(fd)
return True
def publish_successor(directory, old, new, public_key, *, now, registration_digest, predecessor_bytes):
"""Append records inside the SAME plane under a verified caller lock.
Registration validation, current Saga CAS, production/business readbacks,
and permission to call this function remain mandatory caller gates.
"""
fp = validate_successor(old, new, public_key, now=now)
if json.loads(predecessor_bytes) != old:
raise RenewalRejected('PREDECESSOR_BYTES_DRIFT')
predecessor_bytes_digest = digest(predecessor_bytes)
if len(registration_digest) != 64 or any(c not in '0123456789abcdef' for c in registration_digest):
raise RenewalRejected('REGISTERED_DIGEST_REQUIRED')
directory = Path(directory)
metadata = directory.lstat()
if not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.geteuid() or metadata.st_mode & 0o022:
raise RenewalRejected('UNSAFE_APPEND_DIRECTORY')
receipt = {'schema': 'aios.c5.completion-approval-successor.v1', 'candidate_id': CID,
'predecessor_fingerprint': OLD_FP, 'predecessor_bytes_sha256': predecessor_bytes_digest,
'successor_fingerprint': fp, 'successor_sha256': digest(canonical(new)),
'owner_registration_payload_sha256': registration_digest,
'completion_only': True, 'runtime_effects_allowed': False}
# A crash after the first append is unresolved until its exact receipt is
# reconciled. A loader must require BOTH files; no mutable current pointer.
append_file(directory / (fp + '.approval.json'), canonical(new))
append_file(directory / (fp + '.receipt.json'), canonical(receipt))
return receipt
class CompletionApprovalStore:
"""Explicit store dependency for a registered completion host/API.
The original store and original candidate approval remain untouched.
No global class replacement or expired approval verification is used.
An authenticated registration fixes ONE successor nonce and immutable
receipt path before signing. The host must separately enforce TAG_PENDING
and forbid runtime effects; this store does not execute a Saga.
"""
def __init__(self, original, directory, public_key, authority, approval_type):
self.original = original
self.directory = Path(directory)
self.public_key = public_key
self.authority = authority
self.approval_type = approval_type
def _policy(self):
# authority must be the fixed registered verifier, not a caller-supplied
# HTTP callback. Wiring it is outside this unregistered draft.
policy = self.authority()
if policy.get('candidate_id') != CID or policy.get('predecessor_fingerprint') != OLD_FP:
raise RenewalRejected('REGISTERED_EXACT_POLICY_REQUIRED')
nonce = policy.get('approval_nonce', '')
if not isinstance(nonce, str) or len(nonce) != 64 or any(c not in '0123456789abcdef' for c in nonce):
raise RenewalRejected('REGISTERED_NONCE_REQUIRED')
return policy, self.directory / (nonce + '.activation.json')
def _parent(self):
old = self.original.load(CID)
policy, _ = self._policy()
owner = policy.get('predecessor_uid')
if type(owner) is not int or owner not in {0, 1000}:
raise RenewalRejected('REGISTERED_PREDECESSOR_OWNER_REQUIRED')
raw = secure_bytes(self.original.root / (CID + '.json'), owner_uid=owner)
if json.loads(raw) != old.to_dict() or digest(raw) != policy.get('predecessor_sha256'):
raise RenewalRejected('PREDECESSOR_FILE_DRIFT')
return old.to_dict(), raw
def load(self, candidate_id):
if candidate_id != CID:
return self.original.load(candidate_id)
policy, activation = self._policy()
if not activation.exists():
return self.original.load(candidate_id)
receipt = json.loads(secure_bytes(activation, owner_uid=policy['predecessor_uid']))
expected = {'schema', 'candidate_id', 'predecessor_fingerprint', 'predecessor_bytes_sha256',
'successor_fingerprint', 'successor_sha256', 'owner_registration_payload_sha256',
'completion_only', 'runtime_effects_allowed'}
if set(receipt) != expected or receipt.get('owner_registration_payload_sha256') != policy['payload_sha256']:
raise RenewalRejected('ACTIVATION_RECEIPT_INVALID')
fp = receipt.get('successor_fingerprint', '')
if len(fp) != 64 or any(c not in '0123456789abcdef' for c in fp):
raise RenewalRejected('SUCCESSOR_FINGERPRINT_INVALID')
raw = secure_bytes(self.directory / (fp + '.approval.json'), owner_uid=policy['predecessor_uid'])
new = json.loads(raw)
parent, parent_raw = self._parent()
valid = validate_successor(parent, new, self.public_key, now=datetime.now(timezone.utc).timestamp())
exact = {'candidate_id': CID, 'predecessor_fingerprint': OLD_FP,
'predecessor_bytes_sha256': digest(parent_raw), 'successor_fingerprint': valid,
'successor_sha256': digest(raw), 'completion_only': True, 'runtime_effects_allowed': False}
if any(receipt.get(k) != v for k, v in exact.items()) or new['nonce'] != policy['approval_nonce']:
raise RenewalRejected('SUCCESSOR_ACTIVATION_DRIFT')
durable = json.loads(secure_bytes(self.directory / (fp + '.receipt.json'), owner_uid=policy['predecessor_uid']))
if durable != receipt:
raise RenewalRejected('DURABLE_RECEIPT_DRIFT')
return self.approval_type.from_dict(new)
def save(self, candidate_id, approval):
if candidate_id != CID:
return self.original.save(candidate_id, approval)
policy, activation = self._policy()
parent, raw = self._parent()
if approval.fingerprint == parent['fingerprint']:
return self.original.save(candidate_id, approval)
new = approval.to_dict()
if new.get('nonce') != policy['approval_nonce']:
raise RenewalRejected('REGISTERED_NONCE_MISMATCH')
# The caller supplies its authenticated registered effect lock and
# invokes the existing ApprovalVerifier before this store boundary.
receipt = publish_successor(self.directory, parent, new, self.public_key,
now=datetime.now(timezone.utc).timestamp(),
registration_digest=policy['payload_sha256'], predecessor_bytes=raw)
created = append_file(activation, canonical(receipt))
if self.load(candidate_id).fingerprint != approval.fingerprint:
raise RenewalRejected('SUCCESSOR_READBACK_FAILED')
return activation, created
class RegisteredCompletionApprovalStore(CompletionApprovalStore):
"""Production dependency; no request-selected root or authority callback."""
def __init__(self, original, public_key, approval_type):
if Path(original.root) != STATE_ROOT / 'approvals':
raise RenewalRejected('SAME_APPROVAL_PLANE_REQUIRED')
super().__init__(original, STATE_ROOT / 'approvals' / 'c5-completion-successors',
public_key, RegisteredCompletionAuthority(), approval_type)
class CompletionSignerMixin:
"""Narrow additional method of the EXISTING credential-bound signer.
Activation must register this exact mixin, verifier and namespace snapshot
pins. The encrypted private key stays in the existing signer's unit. This
draft supplies no activation, credential access or HTTP route.
"""
def sign_exact_completion_successor(self, request):
expected = {'schema', 'operation', 'candidate_id', 'predecessor_fingerprint'}
if (set(request) != expected or request.get('operation') != 'renew_exact_completion_approval'
or request.get('candidate_id') != CID or request.get('predecessor_fingerprint') != OLD_FP
or request.get('schema') != 'aios.exact-release-signer.request.v1'):
raise RenewalRejected('EXACT_SIGNER_RENEWAL_REQUEST_REQUIRED')
policy = self.completion_authority()
now = datetime.now(timezone.utc).timestamp()
if (policy.get('candidate_id') != CID or policy.get('predecessor_fingerprint') != OLD_FP
or not policy['issued_at'] <= now < policy['expires_at']
or not 0 <= now - policy['gate_observed_at'] <= 60):
raise RenewalRejected('FRESH_REGISTERED_COMPLETION_GATE_REQUIRED')
old_raw = secure_bytes(policy['predecessor_snapshot'], owner_uid=0)
if digest(old_raw) != policy['predecessor_sha256']:
raise RenewalRejected('REGISTERED_SIGNER_PREIMAGE_DRIFT')
old = json.loads(old_raw)
public_key = self.private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
verify_signature(old, public_key)
exact = {'candidate_id': CID, 'source_commit': SOURCE, 'tree_oid': TREE,
'artifact_sha256': ARTIFACT, 'release_plan_sha256': PLAN,
'fingerprint': OLD_FP, 'capabilities': CAPABILITIES,
'key_id': self.policy.key_id, 'owner_identity': self.policy.owner_identity,
'trust_domain': self.policy.trust_domain,
'owner_approval_record_oid': self.authorization_oid}
if any(old.get(k) != v for k, v in exact.items()) or timestamp(old['expires_at']) > now:
raise RenewalRejected('EXACT_EXPIRED_SIGNER_PREDECESSOR_REQUIRED')
nonce = policy['approval_nonce']
if len(nonce) != 64 or any(c not in '0123456789abcdef' for c in nonce) or nonce == old['nonce']:
raise RenewalRejected('FRESH_REGISTERED_SIGNER_NONCE_REQUIRED')
output = self.state_root / 'completion-renewals' / (nonce + '.json')
if output.exists():
result = json.loads(secure_bytes(output, owner_uid=0))
if (result.get('registration_payload_sha256') != policy['payload_sha256']
or result['approval']['nonce'] != nonce):
raise RenewalRejected('SIGNER_REPLAY_CONFLICT')
validate_successor(old, result['approval'], public_key, now=now)
return {**result, 'status': 'SIGNED_IDEMPOTENT'}
expiry = min(now + 600, policy['expires_at'] - 5)
if expiry - now < 120:
raise RenewalRejected('INSUFFICIENT_SIGNER_WINDOW')
def utc(value):
return datetime.fromtimestamp(value, timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z')
payload = {k:v for k,v in old.items() if k not in {'fingerprint', 'signature'}}
payload.update(issued_at=utc(now), not_before=utc(now), expires_at=utc(expiry), nonce=nonce)
raw = canonical(payload)
# All candidate, plan, historical signature and registered freshness
# checks precede this only signing operation.
approval = {**payload, 'fingerprint': digest(raw),
'signature': base64.b64encode(self.private_key.sign(raw)).decode()}
validate_successor(old, approval, public_key, now=now)
result = {'schema': 'aios.exact-release-signer.response.v1', 'status': 'SIGNED',
'approval': approval, 'registration_payload_sha256': policy['payload_sha256'],
'predecessor_fingerprint': OLD_FP, 'completion_only': True, 'arbitrary_signing': False}
# This registered directory must be created by the activation consumer
# inside the existing signer state root, never a request path.
append_file(output, canonical(result))
return result
@@ -1,58 +0,0 @@
"""Registered fixed extension of the existing credential-bound signer unit."""
import importlib.util
import os
from pathlib import Path
import sys
from c5_completion_renewal import (
CompletionSignerMixin, RegisteredCompletionAuthority,
RenewalRejected, secure_bytes, digest,
)
ORIGINAL = Path('/usr/local/libexec/aios_exact_release_signer.py')
ORIGINAL_SHA = '6e2a5a9df0edfe566175e5b556d15901b2c8fe4484388ad74af2ac64f810c930'
def signer_class(original):
class CompletionSigner(CompletionSignerMixin, original.ExactReleaseSigner):
def completion_authority(self):
return RegisteredCompletionAuthority()()
def handle(self, request):
if request.get('operation') == 'renew_exact_completion_approval':
return self.sign_exact_completion_successor(request)
return super().handle(request)
return CompletionSigner
def main():
import c5_completion_renewal as renewal
renewal.configure_registered_bindings()
if Path(__file__) != renewal.AUTHORITY_ROOT / 'c5_completion_signer.py':
raise RenewalRejected('REGISTERED_SIGNER_WRAPPER_REQUIRED')
RegisteredCompletionAuthority()()
if digest(secure_bytes(ORIGINAL)) != ORIGINAL_SHA:
raise RenewalRejected('ORIGINAL_SIGNER_PREIMAGE_DRIFT')
spec = importlib.util.spec_from_file_location('c5_original_exact_release_signer', ORIGINAL)
original = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = original
spec.loader.exec_module(original)
credentials = Path(os.environ.get('CREDENTIALS_DIRECTORY', ''))
if not credentials.is_absolute() or credentials.parent != Path('/run/credentials'):
raise RenewalRejected('EXISTING_SIGNER_PURPOSE_CREDENTIAL_REQUIRED')
if credentials.name != 'aios-exact-release-signer.service':
raise RenewalRejected('EXISTING_SIGNER_UNIT_REQUIRED')
cgroup = Path('/proc/self/cgroup').read_text()
if not any(line.split(':', 2)[-1].rstrip('/').endswith('/aios-exact-release-signer.service')
for line in cgroup.splitlines()):
raise RenewalRejected('EXISTING_SIGNER_CGROUP_REQUIRED')
signer = signer_class(original)(
Path('/etc/aios-gitops/exact-release-signer-policy.json'),
credentials / 'owner_release_private_key',
)
original.serve(Path('/run/aios-exact-release-signer/signer.sock'), signer)
return 0
if __name__ == '__main__':
raise SystemExit(main())
@@ -1,503 +0,0 @@
"""Exact C5 runtime-switch adapter for the silent-release Saga.
The adapter owns only the production deployment phase. Tag publication and
Registry/Baseline finalization remain separate ``ReleaseOrchestrator`` Saga
steps. A host must register immutable executable bytes and provide the real
HMAC-authenticated source-admission store plus the signed Owner approval
verifier before this adapter can be constructed.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import re
import stat
import subprocess
from typing import Any, Mapping
from release_source_admission import ReleaseSourceAdmissionStore
from silent_release_controller import ReleaseCandidate
from silent_release_hardening import (
CapabilityAuthorizationError,
CapabilityLedger,
ClaimProbeResult,
ReleasePlan,
RuntimeSwitchController,
SignedApprovalVerifier,
SignedReleasePlanApproval,
_canonical_json,
)
C5_VERSION = "V2R2C5"
C5_PREPUBLICATION_SOURCE_COMMIT = "396ea9811124736aa140d2810ab53eaefb6b5c1c"
C4_ROLLBACK_TARGET = {
"version": "V2R2C4",
"target_oid": "e5f8209f57b43fc39e55c4555776e6362ce326eb",
"artifact_sha256": "0dfc0461f7f9aa25cfbfc37008dcbd2d48d5341383574163b7bba102852062c4",
}
C4_ROLLBACK_ARTIFACT_PATH = Path(
"/var/lib/aios-gitops/release-only/"
"v2r2c4-e5f8209f57b4/artifact.tar.gz"
)
C5_PRODUCTION_ENVIRONMENT_FILE = Path("/etc/aios/v2r2c2.env")
C5_PRODUCTION_SERVICE = "aios-v2r2c2-production.service"
C5_PRODUCTION_PORT = 18928
REQUEST_SCHEMA = "aios.c5.exact-production-deployment-request.v1"
HEX64 = re.compile(r"^[0-9a-f]{64}$")
class C5DeploymentBindingError(CapabilityAuthorizationError):
"""C5 source, plan, approval, executor, or receipt binding is invalid."""
class C5DeploymentOutcomeUnknown(RuntimeError):
"""The deployment effect must be reconciled before any retry."""
class C5DeploymentConflict(RuntimeError):
"""The observed runtime is bound to a different release."""
def _sha256_file(path: Path) -> str:
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 _run_registered_adapter(
command: tuple[str, ...], request: Mapping[str, Any]
) -> dict[str, Any]:
"""Invoke only the code-pinned process and accept one JSON object."""
try:
completed = subprocess.run(
list(command),
input=_canonical_json(dict(request)),
check=False,
capture_output=True,
timeout=180,
env={"PATH": os.environ.get("PATH", "")},
)
except (OSError, subprocess.SubprocessError) as exc:
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_UNAVAILABLE"
) from exc
if completed.returncode != 0:
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_FAILED"
)
try:
lines = [
line
for line in completed.stdout.decode("utf-8").splitlines()
if line.strip()
]
value = json.loads(lines[-1]) if lines else None
except (UnicodeError, ValueError) as exc:
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_RESPONSE_INVALID"
) from exc
if not isinstance(value, dict):
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_RESPONSE_INVALID"
)
return value
def _secure_registered_file(
path: Path,
expected_sha256: str,
*,
require_root_owner: bool,
) -> None:
path = Path(path)
if (
not path.is_absolute()
or path.is_symlink()
or path.resolve(strict=False) != path
):
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_INVALID")
try:
info = path.stat()
except OSError as exc:
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_INVALID") from exc
if not stat.S_ISREG(info.st_mode):
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_INVALID")
if os.name != "nt" and stat.S_IMODE(info.st_mode) & 0o022:
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_WRITABLE")
if require_root_owner:
if not hasattr(os, "geteuid") or info.st_uid != 0:
raise C5DeploymentBindingError("C5_EXECUTOR_OWNER_INVALID")
parent = path.parent
while True:
parent_info = parent.stat()
if (
parent.is_symlink()
or parent_info.st_uid != 0
or stat.S_IMODE(parent_info.st_mode) & 0o022
):
raise C5DeploymentBindingError(
"C5_EXECUTOR_PARENT_SECURITY_INVALID"
)
if parent == parent.parent:
break
parent = parent.parent
if _sha256_file(path) != expected_sha256:
raise C5DeploymentBindingError("C5_EXECUTOR_CODE_MISMATCH")
@dataclass(frozen=True, slots=True)
class RegisteredC5DeploymentExecutor:
"""Pinned host configuration for one concrete process adapter.
``adapter_path`` is optional for a native executable. When present the
command is exactly ``executable adapter_path``; arbitrary argv and Python
callables are intentionally unsupported.
"""
executor_id: str
executable: Path
executable_sha256: str
artifact_path: Path
rollback_artifact_path: Path
production_environment_file: Path
adapter_path: Path | None = None
adapter_sha256: str | None = None
require_root_owner: bool = True
execution_scope: str = "PRODUCTION"
isolation_root: Path | None = None
rollback_target: Mapping[str, str] | None = None
def __post_init__(self) -> None:
if not self.executor_id or len(self.executor_id) > 255:
raise C5DeploymentBindingError("C5_EXECUTOR_ID_INVALID")
if HEX64.fullmatch(self.executable_sha256) is None:
raise C5DeploymentBindingError("C5_EXECUTOR_DIGEST_INVALID")
if (self.adapter_path is None) != (self.adapter_sha256 is None):
raise C5DeploymentBindingError("C5_EXECUTOR_ADAPTER_BINDING_INVALID")
if (
self.adapter_sha256 is not None
and HEX64.fullmatch(self.adapter_sha256) is None
):
raise C5DeploymentBindingError("C5_EXECUTOR_DIGEST_INVALID")
if not Path(self.artifact_path).is_absolute():
raise C5DeploymentBindingError("C5_ARTIFACT_PATH_INVALID")
if self.execution_scope not in {"PRODUCTION", "ISOLATED_REHEARSAL"}:
raise C5DeploymentBindingError("C5_EXECUTION_SCOPE_INVALID")
target = dict(self.rollback_target or C4_ROLLBACK_TARGET)
if target.keys() != C4_ROLLBACK_TARGET.keys() or target["version"] != C4_ROLLBACK_TARGET["version"] or target["target_oid"] != C4_ROLLBACK_TARGET["target_oid"] or HEX64.fullmatch(str(target["artifact_sha256"])) is None:
raise C5DeploymentBindingError("C5_ROLLBACK_TARGET_INVALID")
if self.execution_scope == "PRODUCTION":
if target != C4_ROLLBACK_TARGET:
raise C5DeploymentBindingError("C5_ROLLBACK_TARGET_INVALID")
if Path(self.rollback_artifact_path) != C4_ROLLBACK_ARTIFACT_PATH:
raise C5DeploymentBindingError("C5_ROLLBACK_ARTIFACT_PATH_INVALID")
if Path(self.production_environment_file) != C5_PRODUCTION_ENVIRONMENT_FILE:
raise C5DeploymentBindingError("C5_PRODUCTION_ENVIRONMENT_FILE_INVALID")
if self.isolation_root is not None:
raise C5DeploymentBindingError("C5_ISOLATION_ROOT_INVALID")
else:
if self.isolation_root is None or not Path(self.isolation_root).is_absolute():
raise C5DeploymentBindingError("C5_ISOLATION_ROOT_INVALID")
root = Path(self.isolation_root).resolve(strict=False)
for bound_path in (self.rollback_artifact_path, self.production_environment_file):
path = Path(bound_path)
if not path.is_absolute() or path.resolve(strict=False) == root:
raise C5DeploymentBindingError("C5_REHEARSAL_BINDING_INVALID")
try:
path.resolve(strict=False).relative_to(root)
except ValueError as exc:
raise C5DeploymentBindingError("C5_REHEARSAL_BINDING_INVALID") from exc
_secure_registered_file(
Path(self.rollback_artifact_path),
target["artifact_sha256"],
require_root_owner=self.require_root_owner,
)
object.__setattr__(self, "rollback_target", target)
self.verify_code()
def verify_code(self) -> None:
_secure_registered_file(
Path(self.executable),
self.executable_sha256,
require_root_owner=self.require_root_owner,
)
if self.adapter_path is not None:
_secure_registered_file(
Path(self.adapter_path),
str(self.adapter_sha256),
require_root_owner=self.require_root_owner,
)
@property
def command(self) -> tuple[str, ...]:
values = [str(Path(self.executable))]
if self.adapter_path is not None:
values.append(str(Path(self.adapter_path)))
return tuple(values)
class C5ExactDeploymentAdapter:
"""Callable runtime-switch phase with exact C5 admission and approval."""
def __init__(
self,
*,
candidate: ReleaseCandidate,
release_plan: ReleasePlan,
approval: SignedReleasePlanApproval,
approval_verifier: SignedApprovalVerifier,
source_admissions: ReleaseSourceAdmissionStore,
executor: RegisteredC5DeploymentExecutor,
) -> None:
if not isinstance(approval_verifier, SignedApprovalVerifier):
raise C5DeploymentBindingError("C5_APPROVAL_VERIFIER_REQUIRED")
if not isinstance(source_admissions, ReleaseSourceAdmissionStore):
raise C5DeploymentBindingError("C5_SOURCE_ADMISSION_STORE_REQUIRED")
self.candidate = candidate
self.release_plan = release_plan
self.approval = approval
self.approval_verifier = approval_verifier
self.source_admissions = source_admissions
self.executor = executor
self._binding = self._verify_binding(candidate, release_plan)
@staticmethod
def _global_writes_disabled() -> bool:
return os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() == "false"
def _verify_binding(
self,
candidate: ReleaseCandidate,
plan: ReleasePlan,
) -> dict[str, Any]:
if candidate.fingerprint != self.candidate.fingerprint or plan.sha256 != self.release_plan.sha256:
raise C5DeploymentBindingError("C5_RUNTIME_INPUT_BINDING_MISMATCH")
plan.validate()
if plan.tag.version != C5_VERSION or plan.finalization.version != C5_VERSION:
raise C5DeploymentBindingError("C5_VERSION_BINDING_INVALID")
if candidate.source_commit == C5_PREPUBLICATION_SOURCE_COMMIT:
raise C5DeploymentBindingError("C5_PREPUBLICATION_SOURCE_FORBIDDEN")
rollback_target = dict(self.executor.rollback_target or C4_ROLLBACK_TARGET)
if plan.deployment.rollback_target != rollback_target:
raise C5DeploymentBindingError("C5_ROLLBACK_TARGET_INVALID")
if plan.deployment.service != C5_PRODUCTION_SERVICE:
raise C5DeploymentBindingError("C5_PRODUCTION_SERVICE_INVALID")
if plan.deployment.writer_expected is not False:
raise C5DeploymentBindingError("C5_LEGACY_WRITES_MUST_REMAIN_DISABLED")
if not self._global_writes_disabled():
raise C5DeploymentBindingError("C5_LEGACY_WRITES_MUST_REMAIN_DISABLED")
admitted = self.source_admissions.resolve_recipe(
C5_VERSION,
candidate.candidate_id,
candidate.source_commit,
)
if (
admitted.get("source_commit") != candidate.source_commit
or admitted.get("tree_oid") != candidate.tree_oid
):
raise C5DeploymentBindingError("C5_ADMITTED_SOURCE_BINDING_INVALID")
if (
plan.deployment.source_commit != candidate.source_commit
or plan.deployment.tree_oid != candidate.tree_oid
or plan.deployment.artifact_sha256 != candidate.artifact_sha256
or plan.deployment.manifest_sha256 != candidate.manifest_sha256
):
raise C5DeploymentBindingError("C5_CANDIDATE_PLAN_BINDING_INVALID")
self.approval_verifier.verify_release_plan(
self.approval,
candidate,
plan,
plan.policy_sha256,
)
artifact = Path(self.executor.artifact_path)
_secure_registered_file(
artifact,
candidate.artifact_sha256,
require_root_owner=self.executor.require_root_owner,
)
artifact_size = artifact.stat().st_size
if artifact_size <= 0:
raise C5DeploymentBindingError("C5_ARTIFACT_BYTES_MISMATCH")
self.executor.verify_code()
return {
"version": C5_VERSION,
"candidate_id": candidate.candidate_id,
"source_commit": candidate.source_commit,
"target_oid": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"artifact_path": str(artifact),
"artifact_size": artifact_size,
"manifest_sha256": candidate.manifest_sha256,
"deployment_plan_sha256": plan.deployment.sha256,
"release_plan_sha256": plan.sha256,
"policy_sha256": plan.policy_sha256,
"approval_plan_id": self.approval.approval_plan_id,
"approval_fingerprint": self.approval.fingerprint,
"owner_approval_record_oid": self.approval.owner_approval_record_oid,
"evidence_closure_oid": self.approval.evidence_closure_oid,
"repository_identity": plan.deployment.repository_identity,
"production_service": plan.deployment.service,
"production_port": C5_PRODUCTION_PORT,
"systemd_unit_sha256": plan.deployment.systemd_unit_sha256,
"dropin_sha256": plan.deployment.dropin_sha256,
"rollback_target": rollback_target,
"rollback_artifact_path": str(
self.executor.rollback_artifact_path
),
"production_environment_file": str(
self.executor.production_environment_file
),
"writer_expected": False,
"global_legacy_writes_enabled": False,
"registered_executor_id": self.executor.executor_id,
"executor_executable_sha256": self.executor.executable_sha256,
"executor_adapter_sha256": self.executor.adapter_sha256,
}
def _request(self, operation: str) -> dict[str, Any]:
return {
"schema": REQUEST_SCHEMA,
"operation": operation,
**self._binding,
}
def _invoke(self, operation: str) -> dict[str, Any]:
if not self._global_writes_disabled():
raise C5DeploymentBindingError("C5_LEGACY_WRITES_MUST_REMAIN_DISABLED")
self.executor.verify_code()
value = _run_registered_adapter(
self.executor.command, self._request(operation)
)
for key, expected in self._binding.items():
if value.get(key) != expected:
raise C5DeploymentBindingError(f"C5_DEPLOYMENT_RECEIPT_MISMATCH:{key}")
if value.get("writer_disabled") is not True:
raise C5DeploymentBindingError("C5_DEPLOYMENT_WRITER_STATE_INVALID")
receipt = value.get("deployment_receipt")
if not isinstance(receipt, Mapping) or not receipt:
raise C5DeploymentBindingError("C5_DEPLOYMENT_RECEIPT_REQUIRED")
return value
@staticmethod
def _classify(value: Mapping[str, Any]) -> ClaimProbeResult:
status = value.get("status")
if (
status == "ACTIVE_VERIFIED"
and value.get("verified") is True
and value.get("deployment_state") == "ACTIVE"
and value.get("production_deployed") is True
and value.get("writer_disabled") is True
and value.get("global_legacy_writes_enabled") is False
):
return ClaimProbeResult.APPLIED_VERIFIED
if status == "NOT_APPLIED":
return ClaimProbeResult.NOT_APPLIED
if status == "CONFLICT":
return ClaimProbeResult.CONFLICT
return ClaimProbeResult.INDETERMINATE
def recovery_probe(self) -> ClaimProbeResult:
"""Read-only exact probe used by ``CapabilityLedger`` after UNKNOWN."""
try:
self._verify_binding(self.candidate, self.release_plan)
return self._classify(self._invoke("probe"))
except C5DeploymentConflict:
return ClaimProbeResult.CONFLICT
except Exception:
return ClaimProbeResult.INDETERMINATE
def rollback(
self,
*,
candidate: ReleaseCandidate,
plan: ReleasePlan,
) -> dict[str, Any]:
"""Execute only the explicit Saga rollback phase and verify exact C4."""
self._verify_binding(candidate, plan)
value = self._invoke("rollback")
if (
value.get("status") != "ROLLED_BACK_VERIFIED"
or value.get("verified") is not True
or value.get("rollback_verified") is not True
or value.get("rollback_version") != self.executor.rollback_target["version"]
or value.get("rollback_target_oid")
!= self.executor.rollback_target["target_oid"]
or value.get("rollback_artifact_sha256")
!= self.executor.rollback_target["artifact_sha256"]
):
raise C5DeploymentOutcomeUnknown("C5_ROLLBACK_OUTCOME_UNKNOWN")
return {
"verified": True,
"rollback_verified": True,
"rollback_version": self.executor.rollback_target["version"],
"rollback_target_oid": self.executor.rollback_target["target_oid"],
"rollback_artifact_sha256": self.executor.rollback_target["artifact_sha256"],
"writer_disabled": True,
}
def __call__(
self,
*,
candidate: ReleaseCandidate,
plan: ReleasePlan,
) -> dict[str, Any]:
self._verify_binding(candidate, plan)
probe = self._invoke("probe")
classification = self._classify(probe)
if classification == ClaimProbeResult.CONFLICT:
raise C5DeploymentConflict("C5_RUNTIME_BINDING_CONFLICT")
if classification == ClaimProbeResult.INDETERMINATE:
raise C5DeploymentOutcomeUnknown("C5_DEPLOYMENT_OUTCOME_UNKNOWN")
if classification == ClaimProbeResult.NOT_APPLIED:
applied = self._invoke("apply")
if applied.get("status") == "CONFLICT":
raise C5DeploymentConflict("C5_RUNTIME_BINDING_CONFLICT")
if applied.get("status") not in {"APPLIED", "ACTIVE_VERIFIED"}:
raise C5DeploymentOutcomeUnknown("C5_DEPLOYMENT_OUTCOME_UNKNOWN")
probe = self._invoke("probe")
classification = self._classify(probe)
if classification == ClaimProbeResult.CONFLICT:
raise C5DeploymentConflict("C5_RUNTIME_BINDING_CONFLICT")
if classification != ClaimProbeResult.APPLIED_VERIFIED:
raise C5DeploymentOutcomeUnknown("C5_DEPLOYMENT_OUTCOME_UNKNOWN")
return {
"verified": True,
"version": C5_VERSION,
"candidate_id": candidate.candidate_id,
"source_commit": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"release_plan_sha256": plan.sha256,
"approval_fingerprint": self.approval.fingerprint,
"rollback_target": dict(self.executor.rollback_target),
"writer_disabled": True,
"global_legacy_writes_enabled": False,
"registered_executor_id": self.executor.executor_id,
"deployment_receipt_sha256": hashlib.sha256(
_canonical_json(dict(probe))
).hexdigest(),
}
def controller(
self,
ledger: CapabilityLedger,
*,
lease_seconds: float = 300,
) -> RuntimeSwitchController:
"""Bind this exact adapter to the existing capability controller."""
return RuntimeSwitchController(
ledger,
self,
recovery_probe=self.recovery_probe,
executor_id=self.executor.executor_id,
lease_seconds=lease_seconds,
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,688 +0,0 @@
"""Owner-registered dispatch boundary for the fixed-scope C5 installer.
The registration authorizes one exact signed installation contract and one
exact set of installed executor bytes. It is not an execution receipt; the
wrapped :class:`Installer` remains the component that durably records and
reconciles installation state.
"""
from __future__ import annotations
from dataclasses import dataclass
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import socket
import stat
import sys
import time
from typing import Any, Callable, Mapping
import c5_staging_installation as _installer_module
from c5_staging_installation import (
BRIDGE_EFFECTS,
BRIDGE_TARGET,
HOST,
InstallationError,
InstallationOutcomeUnknown,
ISSUER_EFFECTS,
ISSUER_TARGET,
OWNER_NAMESPACE,
R14_SOURCE_COMMIT,
REPOSITORY,
SUCCESSOR_SCHEMA,
Installer,
RegistryOwnerVerifier,
canonical_bytes,
verify_signed_contract,
)
from registered_owner_signer_adapter import RegisteredOwnerSignerAdapter
SCHEMA = "aios.c5.registered-installer-executor.v1"
PROVENANCE_SCHEMA = "aios.c5.executor-provenance.v1"
EXECUTION_DIGEST = "0779fa615e8e7971a8dcfccf0f295b1323cd9b64787e174aea4fd2affc414339"
BRIDGE_PORT = 18892
ISSUER_PORT = 18946
CAPABILITIES = frozenset({"install", "rollback", "reconcile"})
TARGET_EFFECTS = {BRIDGE_TARGET: BRIDGE_EFFECTS, ISSUER_TARGET: ISSUER_EFFECTS}
MAX_TTL_SECONDS = 900
MAX_CODE_BYTES = 16 * 1024 * 1024
DEFAULT_REGISTRY = Path("/etc/aios/single-host-signer-registry.json")
DEFAULT_PROVENANCE_MANIFEST = Path("/etc/aios/c5-executor-provenance.json")
HEX40 = re.compile(r"^[0-9a-f]{40}$")
HEX64 = re.compile(r"^[0-9a-f]{64}$")
NONCE = re.compile(r"^[A-Za-z0-9._:\-]{16,128}$")
SCOPE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:\-]{2,127}$")
class ExecutorRegistrationError(RuntimeError):
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
def _fail(code: str) -> None:
raise ExecutorRegistrationError(code)
def _sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _require_digest(value: Any, pattern: re.Pattern[str], code: str) -> str:
if not isinstance(value, str) or pattern.fullmatch(value) is None:
_fail(code)
return value
def _strict_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
_fail("EXECUTOR_JSON_DUPLICATE_FIELD")
result[key] = value
return result
def _secure_bytes(
path: Path, *, require_root_owner: bool = True, maximum: int = MAX_CODE_BYTES,
) -> bytes:
path = Path(path)
if not path.is_absolute():
_fail("EXECUTOR_CODE_PATH_INVALID")
try:
if path.is_symlink() or path.resolve(strict=True) != path:
_fail("EXECUTOR_CODE_PATH_INVALID")
before = path.stat()
except OSError as exc:
raise ExecutorRegistrationError("EXECUTOR_CODE_PATH_INVALID") from exc
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
fd = os.open(path, flags)
except OSError as exc:
raise ExecutorRegistrationError("EXECUTOR_CODE_PATH_INVALID") from exc
try:
current = os.fstat(fd)
if (
not stat.S_ISREG(current.st_mode)
or current.st_dev != before.st_dev
or current.st_ino != before.st_ino
or current.st_size < 1
or current.st_size > maximum
):
_fail("EXECUTOR_CODE_FILE_INVALID")
if require_root_owner:
if os.name != "posix" or current.st_uid != 0:
_fail("EXECUTOR_CODE_PATH_OWNER_INVALID")
if stat.S_IMODE(current.st_mode) & 0o022:
_fail("EXECUTOR_CODE_PATH_WRITABLE")
parent = path.parent
while True:
parent_stat = parent.stat()
if parent.is_symlink() or parent_stat.st_uid != 0:
_fail("EXECUTOR_CODE_PATH_OWNER_INVALID")
if stat.S_IMODE(parent_stat.st_mode) & 0o022:
_fail("EXECUTOR_CODE_PATH_WRITABLE")
if parent == parent.parent:
break
parent = parent.parent
chunks: list[bytes] = []
remaining = current.st_size
while remaining:
chunk = os.read(fd, min(remaining, 1024 * 1024))
if not chunk:
_fail("EXECUTOR_CODE_FILE_INVALID")
chunks.append(chunk)
remaining -= len(chunk)
if os.read(fd, 1):
_fail("EXECUTOR_CODE_FILE_INVALID")
return b"".join(chunks)
finally:
os.close(fd)
@dataclass(frozen=True, slots=True)
class CodeProvenance:
environment: str
instance_id: str
integration_commit: str
integration_tree: str
installer_module_path: Path
installer_script_path: Path
executor_module_path: Path
installer_module_sha256: str
installer_script_sha256: str
executor_module_sha256: str
authority_manifest_sha256: str
def _actual_installer_module_path() -> Path:
value = getattr(_installer_module, "__file__", None)
if not isinstance(value, str):
_fail("EXECUTOR_LOADED_INSTALLER_PATH_INVALID")
return Path(value).resolve(strict=True)
def _actual_executor_module_path() -> Path:
return Path(__file__).resolve(strict=True)
def load_code_provenance(
manifest: Path, *, require_root_owner: bool = True,
) -> CodeProvenance:
"""Load identity from an authority manifest and actual loaded paths."""
manifest = Path(manifest)
raw = _secure_bytes(manifest, require_root_owner=require_root_owner)
try:
value = json.loads(raw, object_pairs_hook=_strict_object)
except (UnicodeError, json.JSONDecodeError) as exc:
raise ExecutorRegistrationError("EXECUTOR_PROVENANCE_INVALID") from exc
if (
not isinstance(value, dict)
or set(value) != {
"schema", "environment", "instance_id", "integration_commit",
"integration_tree", "entries",
}
or value["schema"] != PROVENANCE_SCHEMA
or not isinstance(value["entries"], dict)
or set(value["entries"]) != {
"installer_module", "installer_script", "executor_module",
}
):
_fail("EXECUTOR_PROVENANCE_INVALID")
expected_paths = {
"installer_module": _actual_installer_module_path(),
"executor_module": _actual_executor_module_path(),
}
resolved: dict[str, Path] = {}
hashes: dict[str, str] = {}
for label, entry in value["entries"].items():
if not isinstance(entry, dict) or set(entry) != {"path", "sha256"}:
_fail("EXECUTOR_PROVENANCE_INVALID")
if not isinstance(entry["path"], str):
_fail("EXECUTOR_PROVENANCE_INVALID")
path = Path(entry["path"])
if not path.is_absolute():
_fail("EXECUTOR_CODE_PATH_INVALID")
try:
canonical = path.resolve(strict=True)
except OSError as exc:
raise ExecutorRegistrationError("EXECUTOR_CODE_PATH_INVALID") from exc
if path != canonical:
_fail("EXECUTOR_CODE_PATH_INVALID")
if label in expected_paths and canonical != expected_paths[label]:
_fail("EXECUTOR_PROVENANCE_LOADED_PATH_MISMATCH")
if label == "installer_script" and canonical.name != "c5_staging_installation.py":
_fail("EXECUTOR_INSTALLER_SCRIPT_PATH_INVALID")
digest = _sha256_bytes(
_secure_bytes(canonical, require_root_owner=require_root_owner)
)
if entry["sha256"] != digest:
_fail("EXECUTOR_PROVENANCE_CODE_MISMATCH")
resolved[label] = canonical
hashes[label] = digest
if (
not isinstance(value["environment"], str)
or SCOPE_ID.fullmatch(value["environment"]) is None
or not isinstance(value["instance_id"], str)
or SCOPE_ID.fullmatch(value["instance_id"]) is None
):
_fail("EXECUTOR_PROVENANCE_SCOPE_INVALID")
return CodeProvenance(
environment=value["environment"],
instance_id=value["instance_id"],
integration_commit=_require_digest(
value["integration_commit"], HEX40,
"EXECUTOR_INTEGRATION_COMMIT_INVALID",
),
integration_tree=_require_digest(
value["integration_tree"], HEX40,
"EXECUTOR_INTEGRATION_TREE_INVALID",
),
installer_module_path=resolved["installer_module"],
installer_script_path=resolved["installer_script"],
executor_module_path=resolved["executor_module"],
installer_module_sha256=hashes["installer_module"],
installer_script_sha256=hashes["installer_script"],
executor_module_sha256=hashes["executor_module"],
authority_manifest_sha256=_sha256_bytes(raw),
)
def _contract_binding(payload: Mapping[str, Any], digest: str) -> dict[str, Any]:
return {
"target": payload["target"],
"contract_digest": digest,
"old_snapshot_sha256": _sha256_bytes(canonical_bytes(payload["existing"])),
"new_manifest_sha256": _sha256_bytes(canonical_bytes({
"release_root": payload["release_root"],
"files": payload["files"],
"readiness": payload["readiness"],
})),
"rollback_sha256": _sha256_bytes(canonical_bytes(payload["rollback"])),
"contract_issued_at": payload["issued_at"],
"contract_expires_at": payload["expires_at"],
"contract_nonce": payload["nonce"],
}
def _issuer_dependency(payload: Mapping[str, Any], digest: str) -> dict[str, Any]:
return {
"target": payload["target"],
"contract_digest": digest,
"source_commit": payload["source_commit"],
"support_commit": payload["support_commit"],
"owner_signer_id": payload["owner_signer_id"],
"owner_key_id": payload["owner_key_id"],
"contract_nonce": payload["nonce"],
"contract_issued_at": payload["issued_at"],
"contract_expires_at": payload["expires_at"],
"rollback_sha256": _sha256_bytes(canonical_bytes(payload["rollback"])),
}
def _registration_payload(
*, executor_id: Any, nonce: Any, issued_at: Any, expires_at: Any,
contract_payload: Mapping[str, Any], contract_digest: str,
provenance: CodeProvenance,
issuer_dependency: Mapping[str, Any] | None,
) -> dict[str, Any]:
return {
"schema": SCHEMA,
"executor_id": executor_id,
"owner_signer_id": contract_payload["owner_signer_id"],
"owner_key_id": contract_payload["owner_key_id"],
"host": HOST,
"repository": REPOSITORY,
"environment": provenance.environment,
"instance_id": provenance.instance_id,
"source_commit": R14_SOURCE_COMMIT,
"support_commit": contract_payload["support_commit"],
"integration_commit": provenance.integration_commit,
"integration_tree": provenance.integration_tree,
"installer_module_sha256": provenance.installer_module_sha256,
"installer_script_sha256": provenance.installer_script_sha256,
"executor_module_sha256": provenance.executor_module_sha256,
"authority_manifest_sha256": provenance.authority_manifest_sha256,
"execution_digest": EXECUTION_DIGEST,
"bridge_port": BRIDGE_PORT,
"issuer_port": ISSUER_PORT,
"capabilities": sorted(CAPABILITIES),
"effects": sorted(contract_payload["effects"]),
"issuer_dependency": dict(issuer_dependency) if issuer_dependency is not None else None,
"issued_at": issued_at,
"expires_at": expires_at,
"nonce": nonce,
**_contract_binding(contract_payload, contract_digest),
}
def _validate_issuer_dependency(
dependency: Any, contract_payload: Mapping[str, Any],
) -> None:
if contract_payload["target"] == ISSUER_TARGET:
if dependency is not None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
return
if (
not isinstance(dependency, Mapping)
or set(dependency) != {
"target", "contract_digest", "source_commit", "support_commit",
"owner_signer_id", "owner_key_id", "contract_nonce",
"contract_issued_at", "contract_expires_at", "rollback_sha256",
}
or dependency["target"] != ISSUER_TARGET
or dependency["source_commit"] != contract_payload["source_commit"]
or dependency["support_commit"] != contract_payload["support_commit"]
or dependency["owner_signer_id"] != contract_payload["owner_signer_id"]
or dependency["owner_key_id"] != contract_payload["owner_key_id"]
or type(dependency["contract_issued_at"]) is not int
or type(dependency["contract_expires_at"]) is not int
):
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
_require_digest(
dependency["contract_digest"], HEX64,
"EXECUTOR_ISSUER_DEPENDENCY_INVALID",
)
_require_digest(
dependency["rollback_sha256"], HEX64,
"EXECUTOR_ISSUER_DEPENDENCY_INVALID",
)
def _validate_payload(
payload: Mapping[str, Any], *, now: int, provenance: CodeProvenance,
contract_payload: Mapping[str, Any], contract_digest: str,
issuer_dependency: Mapping[str, Any] | None,
) -> None:
expected = _registration_payload(
executor_id=payload.get("executor_id"), nonce=payload.get("nonce"),
issued_at=payload.get("issued_at"), expires_at=payload.get("expires_at"),
contract_payload=contract_payload, contract_digest=contract_digest,
provenance=provenance, issuer_dependency=issuer_dependency,
)
if set(payload) != set(expected) or any(
payload.get(key) != value for key, value in expected.items()
):
_fail("EXECUTOR_REGISTRATION_BINDING_INVALID")
if (
not isinstance(payload["executor_id"], str)
or not payload["executor_id"]
or not isinstance(payload["nonce"], str)
or NONCE.fullmatch(payload["nonce"]) is None
):
_fail("EXECUTOR_REGISTRATION_IDENTITY_INVALID")
if (
type(payload["issued_at"]) is not int
or type(payload["expires_at"]) is not int
or payload["issued_at"] > now
or now >= payload["expires_at"]
or not 1 <= payload["expires_at"] - payload["issued_at"] <= MAX_TTL_SECONDS
or payload["issued_at"] < contract_payload["issued_at"]
or payload["expires_at"] > contract_payload["expires_at"]
):
_fail("EXECUTOR_REGISTRATION_EXPIRED")
if payload["support_commit"] != payload["integration_commit"]:
_fail("EXECUTOR_COMMIT_BINDING_INVALID")
if (
issuer_dependency is not None
and payload["expires_at"] > issuer_dependency["contract_expires_at"]
):
_fail("EXECUTOR_REGISTRATION_EXPIRED")
if (
not isinstance(payload["environment"], str)
or SCOPE_ID.fullmatch(payload["environment"]) is None
or not isinstance(payload["instance_id"], str)
or SCOPE_ID.fullmatch(payload["instance_id"]) is None
):
_fail("EXECUTOR_ENVIRONMENT_BINDING_INVALID")
if payload["source_commit"] != R14_SOURCE_COMMIT:
_fail("EXECUTOR_SOURCE_COMMIT_INVALID")
if (
contract_payload["schema"] != SUCCESSOR_SCHEMA
or contract_payload["source_commit"] != R14_SOURCE_COMMIT
):
_fail("EXECUTOR_CONTRACT_GENERATION_INVALID")
effects = TARGET_EFFECTS.get(payload["target"])
if (
effects is None
or not isinstance(payload["effects"], list)
or frozenset(payload["effects"]) != effects
or len(payload["effects"]) != len(effects)
):
_fail("EXECUTOR_EFFECTS_INVALID")
if (
not isinstance(payload["capabilities"], list)
or frozenset(payload["capabilities"]) != CAPABILITIES
or len(payload["capabilities"]) != len(CAPABILITIES)
):
_fail("EXECUTOR_CAPABILITIES_INVALID")
_validate_issuer_dependency(issuer_dependency, contract_payload)
for name in (
"installer_module_sha256", "installer_script_sha256",
"executor_module_sha256", "authority_manifest_sha256",
"execution_digest", "contract_digest", "old_snapshot_sha256",
"new_manifest_sha256", "rollback_sha256",
):
_require_digest(payload[name], HEX64, f"EXECUTOR_{name.upper()}_INVALID")
def _verified_contract(
contract: Path, installer: Installer, *, now: int,
) -> tuple[dict[str, Any], str]:
return verify_signed_contract(
contract, installer.registry_path, now=now,
require_root_owner=installer.require_root_owner,
_layout=installer.layout,
)
def _verified_issuer_dependency(
issuer_contract: Path | None, installer: Installer, *, now: int,
contract_payload: Mapping[str, Any],
) -> dict[str, Any] | None:
if contract_payload["target"] == ISSUER_TARGET:
if issuer_contract is not None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
return None
if issuer_contract is None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_REQUIRED")
payload, digest = _verified_contract(issuer_contract, installer, now=now)
if payload["schema"] != SUCCESSOR_SCHEMA or payload["source_commit"] != R14_SOURCE_COMMIT:
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
dependency = _issuer_dependency(payload, digest)
_validate_issuer_dependency(dependency, contract_payload)
return dependency
def issue_registration(
*, executor_id: str, nonce: str, ttl_seconds: int, contract: Path,
installer: Installer, provenance_manifest: Path,
registered_signer: RegisteredOwnerSignerAdapter,
clock: Callable[[], int], issuer_contract: Path | None = None,
require_root_owner: bool = True,
) -> dict[str, Any]:
"""Issue authorization after deriving every scope field from trusted input."""
if type(installer) is not Installer:
_fail("EXECUTOR_INSTALLER_TYPE_INVALID")
if not isinstance(registered_signer, RegisteredOwnerSignerAdapter):
_fail("REGISTERED_SIGNER_ADAPTER_REQUIRED")
if type(ttl_seconds) is not int or not 1 <= ttl_seconds <= MAX_TTL_SECONDS:
_fail("EXECUTOR_REGISTRATION_TTL_INVALID")
now = int(clock())
provenance = load_code_provenance(
provenance_manifest, require_root_owner=require_root_owner,
)
contract_payload, contract_digest = _verified_contract(contract, installer, now=now)
dependency = _verified_issuer_dependency(
issuer_contract, installer, now=now, contract_payload=contract_payload,
)
payload = _registration_payload(
executor_id=executor_id, nonce=nonce, issued_at=now,
expires_at=now + ttl_seconds, contract_payload=contract_payload,
contract_digest=contract_digest, provenance=provenance,
issuer_dependency=dependency,
)
_validate_payload(
payload, now=now, provenance=provenance,
contract_payload=contract_payload, contract_digest=contract_digest,
issuer_dependency=dependency,
)
if registered_signer.fingerprint != payload["owner_key_id"]:
_fail("EXECUTOR_SIGNER_BINDING_INVALID")
signature = registered_signer._sign_canonical_payload(
OWNER_NAMESPACE, canonical_bytes(payload),
)
return {"payload": payload, "signature": signature}
def load_registration(path: Path, *, require_root_owner: bool = True) -> dict[str, Any]:
raw = _secure_bytes(
Path(path), require_root_owner=require_root_owner, maximum=1024 * 1024,
)
try:
value = json.loads(raw, object_pairs_hook=_strict_object)
except (UnicodeError, json.JSONDecodeError) as exc:
raise ExecutorRegistrationError("EXECUTOR_REGISTRATION_INVALID") from exc
if not isinstance(value, dict) or set(value) != {"payload", "signature"}:
_fail("EXECUTOR_REGISTRATION_FIELDS_INVALID")
return value
class RegisteredInstallerExecutor:
def __init__(
self, *, owner_verifier: RegistryOwnerVerifier, installer: Installer,
provenance_manifest: Path, clock: Callable[[], int],
host: Callable[[], str] = socket.gethostname,
require_root_owner: bool = True,
) -> None:
if type(installer) is not Installer:
_fail("EXECUTOR_INSTALLER_TYPE_INVALID")
if not isinstance(owner_verifier, RegistryOwnerVerifier):
_fail("EXECUTOR_OWNER_VERIFIER_INVALID")
self.owner_verifier = owner_verifier
self.installer = installer
self.provenance_manifest = Path(provenance_manifest)
self.clock = clock
self.host = host
self.require_root_owner = require_root_owner
self.provenance = load_code_provenance(
self.provenance_manifest, require_root_owner=require_root_owner,
)
def _check_code_provenance(self) -> None:
current = load_code_provenance(
self.provenance_manifest, require_root_owner=self.require_root_owner,
)
if current != self.provenance:
_fail("EXECUTOR_CODE_PROVENANCE_CHANGED")
def _verify(
self, registration: Mapping[str, Any], contract: Path,
) -> tuple[dict[str, Any], str, dict[str, Any]]:
self._check_code_provenance()
if (
not isinstance(registration, Mapping)
or set(registration) != {"payload", "signature"}
or not isinstance(registration["payload"], Mapping)
):
_fail("EXECUTOR_REGISTRATION_FIELDS_INVALID")
if self.host() != HOST:
_fail("EXECUTOR_HOST_MISMATCH")
now = int(self.clock())
contract_payload, contract_digest = _verified_contract(
contract, self.installer, now=now,
)
payload = dict(registration["payload"])
dependency = payload.get("issuer_dependency")
_validate_payload(
payload, now=now, provenance=self.provenance,
contract_payload=contract_payload, contract_digest=contract_digest,
issuer_dependency=dependency,
)
if payload["owner_key_id"] != self.owner_verifier.record.fingerprint:
_fail("EXECUTOR_OWNER_KEY_BINDING_INVALID")
signature = registration["signature"]
if (
not isinstance(signature, str)
or not self.owner_verifier.verify(
namespace=OWNER_NAMESPACE, payload=canonical_bytes(payload),
signature=signature, key_id=payload["owner_key_id"],
)
):
_fail("EXECUTOR_OWNER_SIGNATURE_INVALID")
return payload, contract_digest, contract_payload
def dispatch(
self, registration: Mapping[str, Any], contract: Path,
artifact: Path, operation: str, *,
issuer_contract: Path | None = None,
issuer_artifact: Path | None = None,
) -> dict[str, Any]:
payload, contract_digest, contract_payload = self._verify(registration, contract)
capability = "install" if operation in {"preflight", "install"} else operation
if capability not in CAPABILITIES:
_fail("EXECUTOR_OPERATION_INVALID")
if capability not in payload["capabilities"]:
_fail("EXECUTOR_CAPABILITY_INVALID")
if operation == "rollback" and contract_payload["target"] == BRIDGE_TARGET:
if issuer_artifact is None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_REQUIRED")
actual_dependency = _verified_issuer_dependency(
issuer_contract, self.installer, now=int(self.clock()),
contract_payload=contract_payload,
)
if actual_dependency != payload["issuer_dependency"]:
_fail("EXECUTOR_ISSUER_DEPENDENCY_MISMATCH")
elif issuer_contract is not None or issuer_artifact is not None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_UNEXPECTED")
if operation == "preflight":
result = self.installer.preflight(contract, artifact)
elif operation == "install":
result = self.installer.apply(contract, artifact)
elif operation == "rollback":
result = self.installer.rollback_applied(
contract, artifact, issuer_contract=issuer_contract,
issuer_artifact=issuer_artifact,
)
else:
result = self.installer.reconcile_unknown(contract, artifact)
return {
**result,
"operation": operation,
"executor_id": payload["executor_id"],
"contract_digest": contract_digest,
"code_binding": {
"integration_commit": payload["integration_commit"],
"integration_tree": payload["integration_tree"],
"installer_module_sha256": payload["installer_module_sha256"],
"installer_script_sha256": payload["installer_script_sha256"],
"executor_module_sha256": payload["executor_module_sha256"],
},
"registration_policy_digest": _sha256_bytes(canonical_bytes(payload)),
}
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Run one Owner-registered fixed-scope C5 installer operation.",
)
parser.add_argument(
"operation", choices=("preflight", "install", "rollback", "reconcile"),
)
parser.add_argument("--registration", type=Path, required=True)
parser.add_argument("--contract", type=Path, required=True)
parser.add_argument("--artifact", type=Path, required=True)
parser.add_argument("--issuer-contract", type=Path)
parser.add_argument("--issuer-artifact", type=Path)
return parser
def main(argv: list[str] | None = None) -> int:
"""Production CLI with fixed registry and provenance authority paths."""
args = _parser().parse_args(argv)
try:
registration = load_registration(args.registration)
payload = registration.get("payload")
if not isinstance(payload, Mapping) or not isinstance(payload.get("owner_signer_id"), str):
_fail("EXECUTOR_REGISTRATION_FIELDS_INVALID")
installer = Installer(DEFAULT_REGISTRY)
verifier = RegistryOwnerVerifier(
DEFAULT_REGISTRY, owner_signer_id=payload["owner_signer_id"],
)
executor = RegisteredInstallerExecutor(
owner_verifier=verifier, installer=installer,
provenance_manifest=DEFAULT_PROVENANCE_MANIFEST,
clock=lambda: int(time.time()),
)
result = executor.dispatch(
registration, args.contract, args.artifact, args.operation,
issuer_contract=args.issuer_contract,
issuer_artifact=args.issuer_artifact,
)
print(json.dumps(result, sort_keys=True))
return 0
except InstallationOutcomeUnknown as exc:
print(
json.dumps({"status": "UNKNOWN", "error": exc.code}, sort_keys=True),
file=sys.stderr,
)
return 3
except (ExecutorRegistrationError, InstallationError, OSError, ValueError, TypeError) as exc:
print(
json.dumps(
{"status": "REJECTED", "error": getattr(exc, "code", str(exc))},
sort_keys=True,
),
file=sys.stderr,
)
return 2
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,977 +0,0 @@
#!/usr/bin/env python3
"""Root-owned one-shot assembly for the exact V2R2C5 release Saga.
This module only assembles existing hardened Saga phases. Each mutating phase
is a SHA-pinned process adapter; no caller-provided Python callable is accepted.
The staging HTTP service remains shadow-only and does not import this launcher.
"""
from __future__ import annotations
import argparse
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import stat
import subprocess
import time
from typing import Any, Mapping, Sequence
import approved_release_executor as approved_executor_module
from approved_release_executor import run_adapter
import c5_exact_deployment_adapter as exact_deployment_module
from c5_exact_deployment_adapter import (
C4_ROLLBACK_ARTIFACT_PATH,
C4_ROLLBACK_TARGET,
C5ExactDeploymentAdapter,
C5_PRODUCTION_ENVIRONMENT_FILE,
C5_PRODUCTION_PORT,
C5_PRODUCTION_SERVICE,
C5_VERSION,
RegisteredC5DeploymentExecutor,
)
from release_source_admission import open_source_admission_store
from silent_release_controller import ReleaseCandidate, ReleaseCandidateStore
from silent_release_hardening import (
ApprovalKeyRing,
ApprovalRevocationStore,
BaselineController,
CapabilityAuthorizationError,
CapabilityLedger,
ClaimProbeResult,
GovernedFinalizationCoordinator,
HardenedTagController,
PreSwitchValidator,
RegistryController,
ReleaseOrchestrator,
RecoverableReleaseError,
ReleasePlan,
ReleasePlanStore,
ReleaseSagaStore,
SignedApprovalStore,
SignedApprovalVerifier,
TagPlan,
TagPublicationConflict,
TagPublicationProbeEvidence,
TrustedReleasePolicy,
)
SCHEMA = "aios.c5.release-execution-host.v1"
PROVENANCE_SCHEMA = "aios.c5.release-execution-provenance.v1"
REQUEST_SCHEMA = "aios.production-executor-request.v1"
REPOSITORY = "aios/aios"
SCOPES = frozenset({"PRODUCTION", "ISOLATED_REHEARSAL"})
SHA256 = __import__("re").compile(r"^[0-9a-f]{64}$")
OID = __import__("re").compile(r"^[0-9a-f]{40}$")
class C5ExecutionHostBlocked(RuntimeError):
"""Exact host assembly or phase verification failed closed."""
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _secure_file(
path: Path,
expected_sha256: str,
*,
expected_owner_uid: int | None,
) -> Path:
path = Path(path)
if (
not path.is_absolute()
or path.is_symlink()
or path.resolve(strict=False) != path
):
raise C5ExecutionHostBlocked("HOST_FILE_PATH_INVALID")
try:
info = path.stat()
except OSError as exc:
raise C5ExecutionHostBlocked("HOST_FILE_UNAVAILABLE") from exc
if not stat.S_ISREG(info.st_mode) or (
os.name != "nt" and stat.S_IMODE(info.st_mode) & 0o022
):
raise C5ExecutionHostBlocked("HOST_FILE_SECURITY_INVALID")
if expected_owner_uid is not None and info.st_uid != expected_owner_uid:
raise C5ExecutionHostBlocked("HOST_FILE_OWNER_INVALID")
if expected_owner_uid is not None:
parent = path.parent
while True:
parent_info = parent.stat()
if (
parent.is_symlink()
or parent_info.st_uid != expected_owner_uid
or stat.S_IMODE(parent_info.st_mode) & 0o022
):
raise C5ExecutionHostBlocked("HOST_FILE_PARENT_SECURITY_INVALID")
if parent == parent.parent:
break
parent = parent.parent
if not SHA256.fullmatch(expected_sha256) or _sha256(path) != expected_sha256:
raise C5ExecutionHostBlocked("HOST_FILE_DIGEST_MISMATCH")
return path
def _absolute(value: Any, name: str) -> Path:
path = Path(str(value or ""))
if not path.is_absolute():
raise C5ExecutionHostBlocked(f"{name}_PATH_INVALID")
return path
@dataclass(frozen=True, slots=True)
class RegisteredPhaseCommand:
executable: Path
executable_sha256: str
adapter_path: Path
adapter_sha256: str
arguments: tuple[str, ...] = ()
@classmethod
def from_mapping(cls, raw: Any) -> "RegisteredPhaseCommand":
if not isinstance(raw, Mapping) or set(raw) != {
"executable",
"executable_sha256",
"adapter_path",
"adapter_sha256",
"arguments",
}:
raise C5ExecutionHostBlocked("PHASE_COMMAND_SCHEMA_INVALID")
arguments = raw["arguments"]
if not isinstance(arguments, list) or not all(
isinstance(item, str) and item and "\0" not in item
for item in arguments
):
raise C5ExecutionHostBlocked("PHASE_COMMAND_ARGUMENTS_INVALID")
return cls(
executable=_absolute(raw["executable"], "PHASE_EXECUTABLE"),
executable_sha256=str(raw["executable_sha256"]),
adapter_path=_absolute(raw["adapter_path"], "PHASE_ADAPTER"),
adapter_sha256=str(raw["adapter_sha256"]),
arguments=tuple(arguments),
)
def verify(self, *, expected_owner_uid: int | None) -> None:
_secure_file(
self.executable,
self.executable_sha256,
expected_owner_uid=expected_owner_uid,
)
_secure_file(
self.adapter_path,
self.adapter_sha256,
expected_owner_uid=expected_owner_uid,
)
@property
def command(self) -> tuple[str, ...]:
return (str(self.executable), str(self.adapter_path), *self.arguments)
@dataclass(frozen=True, slots=True)
class C5HostProvenance:
environment: str
instance_id: str
authority_commit: str
authority_tree: str
entries: Mapping[str, tuple[Path, str]]
manifest_sha256: str
@classmethod
def load(
cls,
path: Path,
*,
expected_sha256: str,
expected_owner_uid: int | None = 0,
) -> "C5HostProvenance":
path = _secure_file(
Path(path), expected_sha256,
expected_owner_uid=expected_owner_uid,
)
try:
raw = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INVALID") from exc
labels = {
"host", "exact_deployment", "deployment", "approved_executor",
"tag", "registry", "baseline",
}
if (
not isinstance(raw, Mapping)
or set(raw) != {
"schema", "environment", "instance_id",
"authority_commit", "authority_tree", "entries",
}
or raw["schema"] != PROVENANCE_SCHEMA
or raw["environment"] not in SCOPES
or not isinstance(raw["entries"], Mapping)
or set(raw["entries"]) != labels
):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INVALID")
instance_id = str(raw["instance_id"] or "")
if not instance_id or len(instance_id) > 128:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INSTANCE_INVALID")
authority_commit = str(raw["authority_commit"])
authority_tree = str(raw["authority_tree"])
if not OID.fullmatch(authority_commit) or not OID.fullmatch(authority_tree):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_AUTHORITY_INVALID")
actual_loaded = {
"host": Path(__file__).resolve(strict=True),
"exact_deployment": Path(exact_deployment_module.__file__).resolve(strict=True),
"approved_executor": Path(approved_executor_module.__file__).resolve(strict=True),
}
entries: dict[str, tuple[Path, str]] = {}
for label, item in raw["entries"].items():
if not isinstance(item, Mapping) or set(item) != {"path", "sha256"}:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_ENTRY_INVALID")
entry_path = _absolute(item["path"], "HOST_PROVENANCE_ENTRY")
digest = str(item["sha256"])
_secure_file(
entry_path, digest, expected_owner_uid=expected_owner_uid
)
if label in actual_loaded and entry_path != actual_loaded[label]:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_LOADED_PATH_MISMATCH")
entries[label] = (entry_path, digest)
return cls(
environment=str(raw["environment"]),
instance_id=instance_id,
authority_commit=authority_commit,
authority_tree=authority_tree,
entries=entries,
manifest_sha256=expected_sha256,
)
@dataclass(frozen=True, slots=True)
class C5ExecutionHostConfig:
host_provenance: C5HostProvenance
executor_authority_commit: str
executor_authority_tree: str
execution_scope: str
instance_id: str
provenance: Mapping[str, Any]
candidate_id: str
source_commit: str
tree_oid: str
artifact_sha256: str
release_plan_sha256: str
approval_fingerprint: str
state_root: Path
trusted_policy_path: Path
trusted_policy_sha256: str
approval_keyring_path: Path
approval_keyring_sha256: str
source_admission_root: Path
source_admission_policy_path: Path
source_admission_policy_sha256: str
source_admission_task_id: str
source_admission_hmac_path: Path
source_admission_hmac_sha256: str
deployment_executable: Path
deployment_executable_sha256: str
deployment_adapter_path: Path
deployment_adapter_sha256: str
artifact_path: Path
rollback_target: Mapping[str, str]
rollback_artifact_path: Path
production_environment_file: Path
authoritative_git_dir: Path
git_executable: Path
git_executable_sha256: str
tag_command: RegisteredPhaseCommand
registry_command: RegisteredPhaseCommand
baseline_command: RegisteredPhaseCommand
registry_record_path: Path
baseline_record_path: Path
@classmethod
def load(
cls,
path: Path,
*,
expected_sha256: str,
host_provenance: C5HostProvenance,
expected_owner_uid: int | None = 0,
) -> "C5ExecutionHostConfig":
path = _secure_file(
Path(path),
expected_sha256,
expected_owner_uid=expected_owner_uid,
)
try:
raw = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
raise C5ExecutionHostBlocked("HOST_CONFIG_INVALID") from exc
keys = {
"schema", "version", "execution_scope", "instance_id",
"executor_authority_commit", "executor_authority_tree",
"provenance", "candidate_id",
"source_commit", "tree_oid", "artifact_sha256",
"release_plan_sha256", "approval_fingerprint", "state_root",
"trusted_policy_path", "trusted_policy_sha256",
"approval_keyring_path", "approval_keyring_sha256",
"source_admission_root", "source_admission_policy_path",
"source_admission_policy_sha256", "source_admission_task_id", "source_admission_hmac_path",
"source_admission_hmac_sha256", "deployment_executable",
"deployment_executable_sha256", "deployment_adapter_path",
"deployment_adapter_sha256", "artifact_path",
"rollback_target", "rollback_artifact_path",
"production_environment_file", "production_service",
"production_port", "repository_identity", "global_legacy_writes_enabled",
"authoritative_git_dir", "git_executable", "git_executable_sha256",
"tag_command", "registry_command", "baseline_command",
"registry_record_path", "baseline_record_path",
}
if not isinstance(raw, Mapping) or set(raw) != keys:
raise C5ExecutionHostBlocked("HOST_CONFIG_SCHEMA_INVALID")
rollback_artifact_path = _absolute(raw["rollback_artifact_path"], "ROLLBACK_ARTIFACT")
production_environment_file = _absolute(raw["production_environment_file"], "PRODUCTION_ENVIRONMENT_FILE")
exact = (
raw["schema"] == SCHEMA
and raw["version"] == C5_VERSION
and raw["execution_scope"] in SCOPES
and isinstance(raw["rollback_target"], Mapping)
and raw["production_service"] == C5_PRODUCTION_SERVICE
and raw["production_port"] == C5_PRODUCTION_PORT
and raw["repository_identity"] == REPOSITORY
and raw["global_legacy_writes_enabled"] is False
)
if not exact:
raise C5ExecutionHostBlocked("HOST_CONFIG_BINDING_INVALID")
provenance = raw["provenance"]
expected_provenance = (
{
"environment": "ISOLATED_REHEARSAL",
"fixture": True,
"r14_source_receipt": False,
}
if raw["execution_scope"] == "ISOLATED_REHEARSAL"
else {
"environment": "PRODUCTION",
"fixture": False,
"r14_source_receipt": True,
}
)
if provenance != expected_provenance:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INVALID")
instance_id = str(raw["instance_id"] or "")
if not instance_id or len(instance_id) > 128 or any(
character not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-"
for character in instance_id
):
raise C5ExecutionHostBlocked("HOST_INSTANCE_ID_INVALID")
for name, pattern in (
("executor_authority_commit", OID),
("executor_authority_tree", OID),
("source_commit", OID), ("tree_oid", OID),
("artifact_sha256", SHA256),
("release_plan_sha256", SHA256),
("approval_fingerprint", SHA256),
):
if pattern.fullmatch(str(raw[name])) is None:
raise C5ExecutionHostBlocked(f"{name.upper()}_INVALID")
value = cls(
host_provenance=host_provenance,
executor_authority_commit=str(raw["executor_authority_commit"]),
executor_authority_tree=str(raw["executor_authority_tree"]),
execution_scope=str(raw["execution_scope"]),
instance_id=instance_id,
provenance=dict(provenance),
candidate_id=str(raw["candidate_id"]),
source_commit=str(raw["source_commit"]),
tree_oid=str(raw["tree_oid"]),
artifact_sha256=str(raw["artifact_sha256"]),
release_plan_sha256=str(raw["release_plan_sha256"]),
approval_fingerprint=str(raw["approval_fingerprint"]),
state_root=_absolute(raw["state_root"], "STATE_ROOT"),
trusted_policy_path=_absolute(raw["trusted_policy_path"], "TRUSTED_POLICY"),
trusted_policy_sha256=str(raw["trusted_policy_sha256"]),
approval_keyring_path=_absolute(raw["approval_keyring_path"], "APPROVAL_KEYRING"),
approval_keyring_sha256=str(raw["approval_keyring_sha256"]),
source_admission_root=_absolute(raw["source_admission_root"], "SOURCE_ADMISSION_ROOT"),
source_admission_policy_path=_absolute(raw["source_admission_policy_path"], "SOURCE_ADMISSION_POLICY"),
source_admission_policy_sha256=str(raw["source_admission_policy_sha256"]),
source_admission_task_id=str(raw["source_admission_task_id"]),
source_admission_hmac_path=_absolute(raw["source_admission_hmac_path"], "SOURCE_ADMISSION_HMAC"),
source_admission_hmac_sha256=str(raw["source_admission_hmac_sha256"]),
deployment_executable=_absolute(raw["deployment_executable"], "DEPLOYMENT_EXECUTABLE"),
deployment_executable_sha256=str(raw["deployment_executable_sha256"]),
deployment_adapter_path=_absolute(raw["deployment_adapter_path"], "DEPLOYMENT_ADAPTER"),
deployment_adapter_sha256=str(raw["deployment_adapter_sha256"]),
artifact_path=_absolute(raw["artifact_path"], "ARTIFACT"),
rollback_target=dict(raw["rollback_target"]),
rollback_artifact_path=rollback_artifact_path,
production_environment_file=production_environment_file,
authoritative_git_dir=_absolute(raw["authoritative_git_dir"], "AUTHORITATIVE_GIT_DIR"),
git_executable=_absolute(raw["git_executable"], "GIT_EXECUTABLE"),
git_executable_sha256=str(raw["git_executable_sha256"]),
tag_command=RegisteredPhaseCommand.from_mapping(raw["tag_command"]),
registry_command=RegisteredPhaseCommand.from_mapping(raw["registry_command"]),
baseline_command=RegisteredPhaseCommand.from_mapping(raw["baseline_command"]),
registry_record_path=_absolute(raw["registry_record_path"], "REGISTRY_RECORD"),
baseline_record_path=_absolute(raw["baseline_record_path"], "BASELINE_RECORD"),
)
if (not value.source_admission_task_id
or len(value.source_admission_task_id) > 160
or any(character not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:-"
for character in value.source_admission_task_id)):
raise C5ExecutionHostBlocked("SOURCE_ADMISSION_TASK_ID_INVALID")
if value.execution_scope == "ISOLATED_REHEARSAL":
rehearsal_root = value.state_root.resolve(strict=False)
lowered_parts = {part.lower() for part in rehearsal_root.parts}
if (
"isolated-rehearsal" not in lowered_parts
or value.instance_id not in rehearsal_root.parts
):
raise C5ExecutionHostBlocked("REHEARSAL_ROOT_NOT_ISOLATED")
for fixture_path in (
value.source_admission_root,
value.artifact_path,
value.rollback_artifact_path,
value.production_environment_file,
value.registry_record_path,
value.baseline_record_path,
):
try:
fixture_path.resolve(strict=False).relative_to(rehearsal_root)
except ValueError as exc:
raise C5ExecutionHostBlocked(
"REHEARSAL_FIXTURE_OUTSIDE_ISOLATION_ROOT"
) from exc
if value.execution_scope == "ISOLATED_REHEARSAL":
if set(value.rollback_target) != set(C4_ROLLBACK_TARGET) or value.rollback_target.get("version") != C4_ROLLBACK_TARGET["version"] or value.rollback_target.get("target_oid") != C4_ROLLBACK_TARGET["target_oid"]:
raise C5ExecutionHostBlocked("REHEARSAL_ROLLBACK_TARGET_INVALID")
if SHA256.fullmatch(str(value.rollback_target.get("artifact_sha256", ""))) is None:
raise C5ExecutionHostBlocked("REHEARSAL_ROLLBACK_TARGET_INVALID")
if (
host_provenance.environment != value.execution_scope
or host_provenance.instance_id != value.instance_id
or host_provenance.authority_commit
!= value.executor_authority_commit
or host_provenance.authority_tree != value.executor_authority_tree
):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_BINDING_MISMATCH")
if value.execution_scope == "PRODUCTION" and (
value.rollback_target != C4_ROLLBACK_TARGET
or value.rollback_artifact_path != C4_ROLLBACK_ARTIFACT_PATH
or value.production_environment_file != C5_PRODUCTION_ENVIRONMENT_FILE
):
raise C5ExecutionHostBlocked("HOST_CONFIG_BINDING_INVALID")
expected_adapter_paths = {
"deployment": value.deployment_adapter_path,
"tag": value.tag_command.adapter_path,
"registry": value.registry_command.adapter_path,
"baseline": value.baseline_command.adapter_path,
}
if any(
host_provenance.entries[label][0] != path
for label, path in expected_adapter_paths.items()
):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_ADAPTER_MISMATCH")
production_names = {
"deployment": "c5_production_deployment_adapter.py",
"tag": "governed_tag_adapter.py",
"registry": "release_metadata_adapter.py",
"baseline": "release_metadata_adapter.py",
}
observed_names = {
label: path.name for label, path in expected_adapter_paths.items()
}
if value.execution_scope == "PRODUCTION":
if observed_names != production_names:
raise C5ExecutionHostBlocked("PRODUCTION_ADAPTER_IDENTITY_INVALID")
elif any(
observed_names[label] == production_names[label]
for label in production_names
):
raise C5ExecutionHostBlocked("REHEARSAL_PRODUCTION_ADAPTER_FORBIDDEN")
value.verify_files(expected_owner_uid=expected_owner_uid)
return value
def verify_files(self, *, expected_owner_uid: int | None) -> None:
for path, digest in (
(self.trusted_policy_path, self.trusted_policy_sha256),
(self.approval_keyring_path, self.approval_keyring_sha256),
(self.source_admission_policy_path, self.source_admission_policy_sha256),
(self.source_admission_hmac_path, self.source_admission_hmac_sha256),
(self.git_executable, self.git_executable_sha256),
):
_secure_file(path, digest, expected_owner_uid=expected_owner_uid)
for command in (
self.tag_command, self.registry_command, self.baseline_command
):
command.verify(expected_owner_uid=expected_owner_uid)
def _load_policy(path: Path) -> TrustedReleasePolicy:
raw = json.loads(path.read_text(encoding="utf-8"))
required = {
"services", "release_roots", "cutover_links", "dropin_paths",
"authoritative_git_dirs", "repository_identities",
}
if not isinstance(raw, Mapping) or set(raw) != required:
raise C5ExecutionHostBlocked("TRUSTED_POLICY_SCHEMA_INVALID")
values: dict[str, frozenset[str]] = {}
for name in required:
items = raw[name]
if not isinstance(items, list) or not items or not all(
isinstance(item, str) and item for item in items
):
raise C5ExecutionHostBlocked("TRUSTED_POLICY_VALUES_INVALID")
values[name] = frozenset(items)
return TrustedReleasePolicy(**values)
def _phase_request(candidate: ReleaseCandidate, plan: ReleasePlan, approval, operation: str) -> dict[str, Any]:
return {
"schema": REQUEST_SCHEMA,
"operation": operation,
"version": C5_VERSION,
"target_oid": candidate.source_commit,
"evidence_closure_oid": approval.evidence_closure_oid,
"artifact_sha256": candidate.artifact_sha256,
"approval_plan_id": approval.approval_plan_id,
"repository_identity": plan.deployment.repository_identity,
"production_service": plan.deployment.service,
"production_port": C5_PRODUCTION_PORT,
}
def _checked_phase_response(
value: Mapping[str, Any], request: Mapping[str, Any]
) -> dict[str, Any]:
for name in (
"version", "target_oid", "evidence_closure_oid",
"artifact_sha256", "approval_plan_id",
):
if value.get(name) != request[name]:
raise C5ExecutionHostBlocked(f"PHASE_RECEIPT_MISMATCH:{name}")
return dict(value)
class _PreSwitchProbe:
def __init__(self, config: C5ExecutionHostConfig, source_admissions):
self.config = config
self.source_admissions = source_admissions
def _git(self, *args: str) -> str:
completed = subprocess.run(
[str(self.config.git_executable), f"--git-dir={self.config.authoritative_git_dir}", *args],
check=False,
capture_output=True,
text=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise C5ExecutionHostBlocked("PRE_SWITCH_GIT_UNAVAILABLE")
return completed.stdout.strip()
def collect(self, candidate: ReleaseCandidate, plan) -> Mapping[str, Any]:
admitted = self.source_admissions.resolve_recipe(
C5_VERSION, candidate.candidate_id, candidate.source_commit
)
tree = self._git("rev-parse", f"{candidate.source_commit}^{{tree}}")
artifact_ok = (
self.config.artifact_path.is_file()
and _sha256(self.config.artifact_path) == candidate.artifact_sha256
)
rollback_path = (
C4_ROLLBACK_ARTIFACT_PATH
if self.config.execution_scope == "PRODUCTION"
else self.config.rollback_artifact_path
)
rollback_target = (
C4_ROLLBACK_TARGET
if self.config.execution_scope == "PRODUCTION"
else self.config.rollback_target
)
rollback_ok = (
rollback_path.is_file()
and _sha256(rollback_path) == rollback_target["artifact_sha256"]
)
return {
"source_commit": candidate.source_commit,
"tree_oid": tree,
"artifact_sha256": candidate.artifact_sha256 if artifact_ok else "",
"manifest_sha256": candidate.manifest_sha256,
"deployment_plan_sha256": plan.sha256,
"policy_sha256": plan.policy_sha256,
"rollback_ready": rollback_ok,
"git_refs_consistent": (
admitted.get("source_commit") == candidate.source_commit
and admitted.get("tree_oid") == tree
),
"repository_identity": plan.repository_identity,
"writer_disabled": os.environ.get(
"AIOS_GITOPS_WRITES_ENABLED", "false"
).lower() == "false",
}
class _TagPhase:
def __init__(self, command: RegisteredPhaseCommand, candidate, plan, approval, owner_uid):
self.command_config = command
self.candidate = candidate
self.plan = plan
self.approval = approval
self.owner_uid = owner_uid
def _call(self, operation: str) -> dict[str, Any]:
self.command_config.verify(expected_owner_uid=self.owner_uid)
request = _phase_request(self.candidate, self.plan, self.approval, operation)
return _checked_phase_response(
run_adapter(self.command_config.command, request), request
)
def probe_evidence(self, tag_plan: TagPlan) -> TagPublicationProbeEvidence:
value = self._call("probe")
status = value.get("status")
listing = value.get("gitea_listing_visible")
if status == "NOT_APPLIED":
listing = False
return TagPublicationProbeEvidence(
repository_identity=tag_plan.repository_identity,
remote_reachable=True,
gitea_api_reachable=True,
gitea_listing_reachable=True,
remote_tag_oid=value.get("remote_tag_oid"),
annotated_tag_object_oid=value.get("tag_oid"),
remote_peeled_target_oid=value.get("remote_peeled_target_oid"),
gitea_api_tag_oid=value.get("gitea_tag_oid"),
gitea_api_peeled_target_oid=value.get("gitea_peeled_target_oid"),
gitea_listing_visible=listing,
gitea_listing_tag_oid=(value.get("gitea_tag_oid") if listing else None),
gitea_listing_peeled_target_oid=(value.get("gitea_peeled_target_oid") if listing else None),
observed_at=time.time(),
)
def __call__(self, *, tag_plan: TagPlan) -> Mapping[str, Any]:
value = self._call("probe")
if value.get("status") in {"NOT_APPLIED", "PARTIAL_LOCAL"}:
value = self._call("apply")
value = self._call("probe")
if value.get("status") == "CONFLICT":
raise TagPublicationConflict(
"tag publication conflicts with the exact C5 plan"
)
if value.get("status") != "APPLIED_VERIFIED":
raise RecoverableReleaseError(
"tag publication outcome is indeterminate"
)
proof = self.probe_evidence(tag_plan)
if proof.remote_tag_oid != tag_plan.expected_tag_oid:
raise C5ExecutionHostBlocked("TAG_OBJECT_BINDING_MISMATCH")
return {
"verified": True,
"evidence_closure_oid": self.approval.evidence_closure_oid,
"approval_plan_id": self.approval.approval_plan_id,
"repository_identity": tag_plan.repository_identity,
"remote_tag_oid": proof.remote_tag_oid,
"remote_peeled_target_oid": proof.remote_peeled_target_oid,
"gitea_api_tag_oid": proof.gitea_api_tag_oid,
"gitea_api_peeled_target_oid": proof.gitea_api_peeled_target_oid,
"gitea_listing_visible": proof.gitea_listing_visible,
}
class _MetadataPhase:
def __init__(self, *, mode: str, command: RegisteredPhaseCommand, record_path: Path,
candidate, plan, approval, tag_phase: _TagPhase, owner_uid):
self.mode = mode
self.command_config = command
self.record_path = record_path
self.candidate = candidate
self.plan = plan
self.approval = approval
self.tag_phase = tag_phase
self.owner_uid = owner_uid
def _call(self, operation: str) -> dict[str, Any]:
self.command_config.verify(expected_owner_uid=self.owner_uid)
request = _phase_request(self.candidate, self.plan, self.approval, operation)
request["tag_oid"] = self.plan.tag.expected_tag_oid
return _checked_phase_response(run_adapter(self.command_config.command, request), request)
def __call__(self, *, finalization_plan) -> Mapping[str, Any]:
value = self._call("probe")
if value.get("status") == "NOT_APPLIED":
self._call("apply")
value = self._call("probe")
if value.get("status") != "VERIFIED":
raise C5ExecutionHostBlocked(f"{self.mode.upper()}_FINALIZATION_NOT_VERIFIED")
try:
record = json.loads(self.record_path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
raise C5ExecutionHostBlocked(f"{self.mode.upper()}_RECORD_INVALID") from exc
expected_record = (
finalization_plan.registry_expected_record
if self.mode == "registry"
else finalization_plan.baseline_expected_record
)
if any(record.get(key) != item for key, item in expected_record.items()):
raise C5ExecutionHostBlocked(f"{self.mode.upper()}_RECORD_BINDING_MISMATCH")
tag = self.tag_phase.probe_evidence(self.plan.tag)
if (
tag.remote_tag_oid != self.plan.tag.expected_tag_oid
or tag.remote_peeled_target_oid != self.candidate.source_commit
or tag.gitea_api_peeled_target_oid != self.candidate.source_commit
or tag.gitea_listing_visible is not True
):
raise C5ExecutionHostBlocked("FINALIZATION_TAG_PROOF_INVALID")
proof = {
"verified": True,
"tag_verified": True,
"remote_tag_oid": tag.remote_tag_oid,
"remote_peeled_target_oid": tag.remote_peeled_target_oid,
"gitea_api_peeled_target_oid": tag.gitea_api_peeled_target_oid,
"gitea_listing_visible": True,
"evidence_closure_oid": self.approval.evidence_closure_oid,
"approval_plan_id": self.approval.approval_plan_id,
f"{self.mode}_target_oid": self.candidate.source_commit,
}
if self.mode == "baseline":
proof.update(
{
"registry_record": finalization_plan.registry_expected_record,
"baseline_record": finalization_plan.baseline_expected_record,
"baseline_written_last": True,
}
)
return proof
@dataclass(slots=True)
class C5ExecutionHost:
config: C5ExecutionHostConfig
orchestrator: ReleaseOrchestrator
def execute(self) -> dict[str, Any]:
result = dict(self.orchestrator.execute(self.config.candidate_id))
production_receipt = (
self.config.execution_scope == "PRODUCTION"
and result.get("state") == "COMPLETED"
)
return {
**result,
"execution_scope": self.config.execution_scope,
"instance_id": self.config.instance_id,
"provenance": dict(self.config.provenance),
"host_authority_commit": self.config.host_provenance.authority_commit,
"host_authority_tree": self.config.host_provenance.authority_tree,
"host_provenance_sha256": self.config.host_provenance.manifest_sha256,
"fixture_evidence": self.config.execution_scope == "ISOLATED_REHEARSAL",
"production_receipt": production_receipt,
"public_business_e2e_verified": False,
"production_acceptance": False,
}
def _validate_source_admission_task(config, candidate, admitted) -> None:
if admitted.get("task_id") != config.source_admission_task_id:
raise C5ExecutionHostBlocked("SOURCE_ADMISSION_TASK_BINDING_INVALID")
def build_execution_host(
config: C5ExecutionHostConfig,
*,
expected_owner_uid: int | None = 0,
completion_approvals: Any = None,
) -> C5ExecutionHost:
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
raise C5ExecutionHostBlocked("LEGACY_GLOBAL_WRITES_ENABLED")
config.verify_files(expected_owner_uid=expected_owner_uid)
candidates = ReleaseCandidateStore(config.state_root / "candidates")
plans = ReleasePlanStore(config.state_root / "release-plans")
approvals = SignedApprovalStore(config.state_root / "approvals")
completion_authority = None
if completion_approvals is not None:
from c5_completion_renewal import (
RegisteredCompletionApprovalStore, RegisteredCompletionAuthority,
CompletionRuntimeGuard,
)
if (type(completion_approvals) is not RegisteredCompletionApprovalStore
or type(completion_approvals.authority) is not RegisteredCompletionAuthority
or expected_owner_uid != 0 or config.execution_scope != "PRODUCTION"
or Path(__file__) != completion_approvals.authority.root / "c5_release_execution_host.py"
or Path(completion_approvals.original.root) != approvals.root):
raise C5ExecutionHostBlocked("REGISTERED_COMPLETION_DEPENDENCY_REQUIRED")
completion_authority = completion_approvals.authority
completion_authority.validate_host(
state_root=config.state_root, candidate_id=config.candidate_id,
source_commit=config.source_commit, tree_oid=config.tree_oid,
artifact_sha256=config.artifact_sha256,
release_plan_sha256=config.release_plan_sha256,
)
approvals = completion_approvals
sagas = ReleaseSagaStore(config.state_root / "sagas")
candidate = candidates.load(config.candidate_id)
plan = plans.load(config.candidate_id).validate()
approval = approvals.load(config.candidate_id)
observed = (
candidate.source_commit, candidate.tree_oid, candidate.artifact_sha256,
plan.sha256, approval.fingerprint,
)
expected = (
config.source_commit, config.tree_oid, config.artifact_sha256,
config.release_plan_sha256, config.approval_fingerprint,
)
if observed != expected or plan.tag.version != C5_VERSION:
raise C5ExecutionHostBlocked("HOST_DURABLE_BINDING_MISMATCH")
if (
plan.policy_sha256 != config.trusted_policy_sha256
or Path(plan.deployment.authoritative_git_dir)
!= config.authoritative_git_dir
):
raise C5ExecutionHostBlocked("HOST_ACTUAL_PLAN_BINDING_MISMATCH")
policy = _load_policy(config.trusted_policy_path)
policy.validate_deployment(plan.deployment)
verifier = SignedApprovalVerifier(
ApprovalKeyRing.from_file(config.approval_keyring_path),
ApprovalRevocationStore(config.state_root / "revocations"),
)
verifier.verify_release_plan(approval, candidate, plan, plan.policy_sha256)
hmac_key = config.source_admission_hmac_path.read_bytes().strip()
source_admissions = open_source_admission_store(
config.source_admission_root,
repository=REPOSITORY,
policy=config.source_admission_policy_path,
hmac_key=hmac_key,
expected_owner_uid=expected_owner_uid,
writable=False,
)
admitted = source_admissions.resolve_recipe(
C5_VERSION, candidate.candidate_id, candidate.source_commit
)
_validate_source_admission_task(config, candidate, admitted)
deployment_executor = RegisteredC5DeploymentExecutor(
executor_id="c5-production-runtime-switch-v1",
executable=config.deployment_executable,
executable_sha256=config.deployment_executable_sha256,
adapter_path=config.deployment_adapter_path,
adapter_sha256=config.deployment_adapter_sha256,
artifact_path=config.artifact_path,
rollback_artifact_path=config.rollback_artifact_path,
production_environment_file=config.production_environment_file,
execution_scope=config.execution_scope,
isolation_root=(config.state_root if config.execution_scope == "ISOLATED_REHEARSAL" else None),
rollback_target=config.rollback_target,
require_root_owner=expected_owner_uid == 0,
)
runtime = C5ExactDeploymentAdapter(
candidate=candidate,
release_plan=plan,
approval=approval,
approval_verifier=verifier,
source_admissions=source_admissions,
executor=deployment_executor,
)
ledger = CapabilityLedger(config.state_root / "capabilities")
tag_phase = _TagPhase(
config.tag_command, candidate, plan, approval, expected_owner_uid
)
tag = HardenedTagController(
ledger,
tag_phase,
expected_repository=REPOSITORY,
publication_probe=tag_phase.probe_evidence,
executor_id="c5-production-tag-v1",
)
registry_adapter = _MetadataPhase(
mode="registry", command=config.registry_command,
record_path=config.registry_record_path, candidate=candidate,
plan=plan, approval=approval, tag_phase=tag_phase,
owner_uid=expected_owner_uid,
)
baseline_adapter = _MetadataPhase(
mode="baseline", command=config.baseline_command,
record_path=config.baseline_record_path, candidate=candidate,
plan=plan, approval=approval, tag_phase=tag_phase,
owner_uid=expected_owner_uid,
)
finalizer = GovernedFinalizationCoordinator(
RegistryController(ledger, registry_adapter, executor_id="c5-production-registry-v1"),
BaselineController(ledger, baseline_adapter, executor_id="c5-production-baseline-v1"),
)
def actual_plan_factory(actual_candidate: ReleaseCandidate) -> ReleasePlan:
actual = plans.load(actual_candidate.candidate_id).validate()
runtime._verify_binding(actual_candidate, actual)
return actual
def runtime_verify(*, candidate: ReleaseCandidate, plan: ReleasePlan) -> Mapping[str, Any]:
runtime._verify_binding(candidate, plan)
result = runtime._invoke("probe")
if runtime._classify(result) != ClaimProbeResult.APPLIED_VERIFIED:
raise C5ExecutionHostBlocked("RUNTIME_VERIFY_INDETERMINATE")
return {
"verified": True,
"source_commit": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"manifest_sha256": candidate.manifest_sha256,
"repository_identity": plan.deployment.repository_identity,
"authoritative_git_dir": plan.deployment.authoritative_git_dir,
"writer_disabled": True,
}
orchestrator = ReleaseOrchestrator(
candidates=candidates,
plans=plans,
approvals=approvals,
sagas=sagas,
approval_verifier=verifier,
trusted_policy=policy,
actual_plan_factory=actual_plan_factory,
pre_switch_validator=PreSwitchValidator(
_PreSwitchProbe(config, source_admissions)
),
runtime_switch=(CompletionRuntimeGuard(completion_authority) if completion_authority
else runtime.controller(ledger)),
runtime_verify=runtime_verify,
runtime_rollback=(completion_authority.deny_runtime_effect if completion_authority
else runtime.rollback),
tag_publish=tag,
finalizer=finalizer,
)
return C5ExecutionHost(config=config, orchestrator=orchestrator)
def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--config-sha256", required=True)
parser.add_argument("--provenance", type=Path, required=True)
parser.add_argument("--provenance-sha256", required=True)
args = parser.parse_args(argv)
provenance = C5HostProvenance.load(
args.provenance,
expected_sha256=args.provenance_sha256,
expected_owner_uid=0,
)
config = C5ExecutionHostConfig.load(
args.config,
expected_sha256=args.config_sha256,
host_provenance=provenance,
expected_owner_uid=0,
)
result = build_execution_host(config, expected_owner_uid=0).execute()
print(json.dumps(result, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (C5ExecutionHostBlocked, CapabilityAuthorizationError) as exc:
print(json.dumps({"status": "FAIL_CLOSED", "error": type(exc).__name__}))
raise SystemExit(2)
File diff suppressed because it is too large Load Diff
@@ -1,326 +0,0 @@
"""Fail-closed component deployment identity projection.
This module deliberately does not discover a trust root or treat process
environment/health responses as runtime evidence. A host supplies a trusted
comparison callback after it has performed its own authenticated observation.
"""
from __future__ import annotations
import hashlib
import json
import math
import re
import time
from pathlib import Path
from typing import Any, Callable, Mapping
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_COMPONENT_SCHEMA = "aios.component-manifest.v1"
_RECEIPT_SCHEMA = "aios.component-artifact-receipt.v1"
class RuntimeIdentityError(ValueError):
"""A declared or observed identity has an invalid shape."""
def _strict_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise RuntimeIdentityError("DUPLICATE_FIELD")
result[key] = value
return result
def _load_json(path: Path) -> Mapping[str, Any]:
try:
metadata = path.lstat()
if not path.is_file() or path.is_symlink() or metadata.st_size > 2 * 1024 * 1024:
raise RuntimeIdentityError("IDENTITY_FILE_INVALID")
value = json.loads(path.read_text(encoding="utf-8"), object_pairs_hook=_strict_object)
except (OSError, UnicodeError, TypeError, json.JSONDecodeError) as exc:
raise RuntimeIdentityError("IDENTITY_FILE_INVALID") from exc
if not isinstance(value, Mapping):
raise RuntimeIdentityError("IDENTITY_OBJECT_REQUIRED")
return value
def _require_sha(value: object, field: str) -> str:
if not isinstance(value, str) or _SHA256.fullmatch(value) is None:
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return value
def _require_oid(value: object, field: str) -> str:
if not isinstance(value, str) or _OID.fullmatch(value) is None:
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return value
def _require_text(value: object, field: str) -> str:
if not isinstance(value, str) or not value.strip():
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return value
def _canonical(value: object) -> str:
try:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
except (TypeError, ValueError) as exc:
raise RuntimeIdentityError("IDENTITY_VALUE_INVALID") from exc
def _validate_modules(value: object) -> dict[str, str]:
if not isinstance(value, Mapping) or not value:
raise RuntimeIdentityError("MODULES_INVALID")
modules: dict[str, str] = {}
for path, digest in value.items():
name = _require_text(path, "module_path")
if name.startswith("/") or ".." in Path(name).parts:
raise RuntimeIdentityError("MODULE_PATH_INVALID")
modules[name] = _require_sha(digest, "module_sha256")
return dict(sorted(modules.items()))
def _validate_extra_files(value: object) -> dict[str, str]:
if not isinstance(value, Mapping):
raise RuntimeIdentityError("EXTRA_FILES_INVALID")
files: dict[str, str] = {}
for path, digest in value.items():
name = _require_text(path, "extra_file_path")
if name.startswith("/") or ".." in Path(name).parts:
raise RuntimeIdentityError("EXTRA_FILE_PATH_INVALID")
files[name] = _require_sha(digest, "extra_file_sha256")
return dict(sorted(files.items()))
def _relative_artifact_path(value: object, field: str, *, allow_current: bool = False) -> str:
name = _require_text(value, field)
path = Path(name)
if "\\" in name or path.is_absolute() or ".." in path.parts or (name == "." and not allow_current):
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return name
def _validate_start_identity(value: object) -> dict[str, Any]:
if not isinstance(value, Mapping) or set(value) != {"entrypoint", "config_path", "python_path"}:
raise RuntimeIdentityError("START_IDENTITY_INVALID")
entrypoint = _require_text(value.get("entrypoint"), "entrypoint")
config_path = _relative_artifact_path(value.get("config_path"), "config_path")
python_path = value.get("python_path")
if not isinstance(python_path, list) or not python_path:
raise RuntimeIdentityError("START_IDENTITY_INVALID")
result = {
"entrypoint": entrypoint,
"config_path": config_path,
"python_path": [
_relative_artifact_path(path, "python_path", allow_current=True)
for path in python_path
],
}
_canonical(result)
return result
def _validate_component(value: object) -> dict[str, Any]:
required = {
"component_id", "source_oid", "tree_oid", "modules", "extra_files", "config_sha256",
"start_identity", "dependency_contract_version", "rollback_artifact_sha256",
}
if not isinstance(value, Mapping) or set(value) != required:
raise RuntimeIdentityError("COMPONENT_MANIFEST_FIELDS_INVALID")
rollback = value.get("rollback_artifact_sha256")
if rollback is not None:
rollback = _require_sha(rollback, "rollback_artifact_sha256")
return {
"component_id": _require_text(value.get("component_id"), "component_id"),
"source_oid": _require_oid(value.get("source_oid"), "source_oid"),
"tree_oid": _require_oid(value.get("tree_oid"), "tree_oid"),
"modules": _validate_modules(value.get("modules")),
"extra_files": _validate_extra_files(value.get("extra_files")),
"config_sha256": _require_sha(value.get("config_sha256"), "config_sha256"),
"start_identity": _validate_start_identity(value.get("start_identity")),
"dependency_contract_version": _require_text(value.get("dependency_contract_version"), "dependency_contract_version"),
"rollback_artifact_sha256": rollback,
}
def validate_component_manifest(value: object) -> dict[str, Any]:
if not isinstance(value, Mapping) or set(value) != {"schema", "schema_version", "components"}:
raise RuntimeIdentityError("COMPONENT_MANIFEST_FIELDS_INVALID")
if value.get("schema") != _COMPONENT_SCHEMA or value.get("schema_version") != 1:
raise RuntimeIdentityError("COMPONENT_MANIFEST_SCHEMA_INVALID")
components = value.get("components")
if not isinstance(components, list) or not components:
raise RuntimeIdentityError("COMPONENT_MANIFEST_COMPONENTS_INVALID")
normalized = [_validate_component(component) for component in components]
ids = [component["component_id"] for component in normalized]
if len(set(ids)) != len(ids):
raise RuntimeIdentityError("COMPONENT_ID_DUPLICATE")
return {"schema": _COMPONENT_SCHEMA, "schema_version": 1, "components": normalized}
def load_component_manifest(path: Path) -> tuple[dict[str, Any], str]:
raw = path.read_bytes()
return validate_component_manifest(_load_json(path)), hashlib.sha256(raw).hexdigest()
def validate_artifact_receipt(value: object) -> dict[str, Any]:
required = {"schema", "schema_version", "artifact_sha256", "component_manifest_sha256", "manifest_sha256", "evidence_refs"}
optional = {"version", "source_commit", "tree_oid", "artifact_size", "production_deployed"}
if not isinstance(value, Mapping) or not required.issubset(value) or set(value) - required - optional:
raise RuntimeIdentityError("ARTIFACT_RECEIPT_FIELDS_INVALID")
if value.get("schema") != _RECEIPT_SCHEMA or value.get("schema_version") != 1:
raise RuntimeIdentityError("ARTIFACT_RECEIPT_SCHEMA_INVALID")
references = value.get("evidence_refs")
if not isinstance(references, list) or not references or any(not isinstance(ref, str) or not ref.strip() for ref in references):
raise RuntimeIdentityError("EVIDENCE_REFS_INVALID")
result = {
"schema": _RECEIPT_SCHEMA,
"schema_version": 1,
"artifact_sha256": _require_sha(value.get("artifact_sha256"), "artifact_sha256"),
"component_manifest_sha256": _require_sha(value.get("component_manifest_sha256"), "component_manifest_sha256"),
"manifest_sha256": _require_sha(value.get("manifest_sha256"), "manifest_sha256"),
"evidence_refs": list(references),
}
if "version" in value:
result["version"] = _require_text(value["version"], "version")
if "source_commit" in value:
result["source_commit"] = _require_oid(value["source_commit"], "source_commit")
if "tree_oid" in value:
result["tree_oid"] = _require_oid(value["tree_oid"], "tree_oid")
if "artifact_size" in value:
size = value["artifact_size"]
if type(size) is not int or size < 1:
raise RuntimeIdentityError("ARTIFACT_SIZE_INVALID")
result["artifact_size"] = size
if "production_deployed" in value:
deployed = value["production_deployed"]
if type(deployed) is not bool:
raise RuntimeIdentityError("PRODUCTION_DEPLOYED_INVALID")
result["production_deployed"] = deployed
return result
def load_artifact_receipt(path: Path) -> dict[str, Any]:
return validate_artifact_receipt(_load_json(path))
def _identity_result(
status: str,
*,
component_id: str | None,
declared: Mapping[str, Any] | None = None,
observed: Mapping[str, Any] | None = None,
reason: str,
evidence_refs: list[str] | None = None,
mismatches: list[str] | None = None,
) -> dict[str, Any]:
return {
"status": status,
"component_id": component_id,
"declared_identity": dict(declared) if declared is not None else None,
"observed_identity": dict(observed) if observed is not None else None,
"manifest_digest": declared.get("component_manifest_sha256") if declared else None,
"config_digest": declared.get("config_sha256") if declared else None,
"observed_at": observed.get("observed_at") if observed else None,
"expires_at": observed.get("expires_at") if observed else None,
"evidence_refs": list(evidence_refs or ()),
"reason": reason,
"mismatch_fields": list(mismatches or ()),
}
def declared_component_identity(manifest_path: Path | None, receipt_path: Path | None, component_id: str | None) -> dict[str, Any] | None:
if manifest_path is None or receipt_path is None or not component_id:
return None
manifest, manifest_digest = load_component_manifest(manifest_path)
receipt = load_artifact_receipt(receipt_path)
if receipt["component_manifest_sha256"] != manifest_digest:
raise RuntimeIdentityError("COMPONENT_MANIFEST_DIGEST_MISMATCH")
component = next((item for item in manifest["components"] if item["component_id"] == component_id), None)
if component is None:
raise RuntimeIdentityError("COMPONENT_ID_NOT_DECLARED")
return {
**component,
"artifact_sha256": receipt["artifact_sha256"],
"component_manifest_sha256": receipt["component_manifest_sha256"],
"manifest_sha256": receipt["manifest_sha256"],
}
def _timestamp(value: object, field: str) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(float(value)) or float(value) < 0:
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return float(value)
def _validate_observed(value: object) -> dict[str, Any]:
required = {
"component_id", "source_oid", "tree_oid", "modules", "extra_files", "config_sha256", "start_identity",
"artifact_sha256", "observed_at", "expires_at", "evidence_refs",
}
if not isinstance(value, Mapping) or set(value) != required:
raise RuntimeIdentityError("OBSERVED_IDENTITY_FIELDS_INVALID")
observed_at = _timestamp(value.get("observed_at"), "observed_at")
expires_at = _timestamp(value.get("expires_at"), "expires_at")
if expires_at <= observed_at:
raise RuntimeIdentityError("OBSERVATION_EXPIRY_INVALID")
refs = value.get("evidence_refs")
if not isinstance(refs, list) or not refs or any(not isinstance(ref, str) or not ref.strip() for ref in refs):
raise RuntimeIdentityError("EVIDENCE_REFS_INVALID")
return {
"component_id": _require_text(value.get("component_id"), "component_id"),
"source_oid": _require_oid(value.get("source_oid"), "source_oid"),
"tree_oid": _require_oid(value.get("tree_oid"), "tree_oid"),
"modules": _validate_modules(value.get("modules")),
"extra_files": _validate_extra_files(value.get("extra_files")),
"config_sha256": _require_sha(value.get("config_sha256"), "config_sha256"),
"start_identity": _validate_start_identity(value.get("start_identity")),
"artifact_sha256": _require_sha(value.get("artifact_sha256"), "artifact_sha256"),
"observed_at": observed_at,
"expires_at": expires_at,
"evidence_refs": list(refs),
}
TrustedComparison = Callable[[Mapping[str, Any]], Mapping[str, Any] | None]
def evaluate_component_runtime_identity(
*,
component_id: str | None,
manifest_path: Path | None,
receipt_path: Path | None,
trusted_comparison: TrustedComparison | None = None,
now: float | None = None,
) -> dict[str, Any]:
"""Project identity without granting trust to a declaration or self-report."""
try:
declared = declared_component_identity(manifest_path, receipt_path, component_id)
except RuntimeIdentityError as exc:
return _identity_result("FAIL", component_id=component_id, reason=str(exc))
if declared is None:
return _identity_result("UNKNOWN", component_id=component_id, reason="DECLARED_IDENTITY_UNAVAILABLE")
if trusted_comparison is None:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, reason="TRUSTED_OBSERVATION_UNAVAILABLE")
try:
raw_observed = trusted_comparison(dict(declared))
except Exception:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, reason="TRUSTED_COMPARISON_UNAVAILABLE")
if raw_observed is None:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, reason="OBSERVATION_MISSING")
try:
observed = _validate_observed(raw_observed)
except RuntimeIdentityError as exc:
return _identity_result("FAIL", component_id=component_id, declared=declared, reason=str(exc))
current = time.time() if now is None else now
if observed["expires_at"] <= current:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, observed=observed, reason="OBSERVATION_EXPIRED", evidence_refs=observed["evidence_refs"])
fields = ("component_id", "source_oid", "tree_oid", "modules", "extra_files", "config_sha256", "start_identity", "artifact_sha256")
mismatches = [field for field in fields if _canonical(declared[field]) != _canonical(observed[field])]
if mismatches:
return _identity_result("FAIL", component_id=component_id, declared=declared, observed=observed, reason="DECLARED_OBSERVED_DRIFT", evidence_refs=observed["evidence_refs"], mismatches=mismatches)
return _identity_result("PASS", component_id=component_id, declared=declared, observed=observed, reason="TRUSTED_COMPARISON_MATCH", evidence_refs=observed["evidence_refs"])
-11
View File
@@ -9,8 +9,6 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
from component_runtime_identity import evaluate_component_runtime_identity
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA = re.compile(r"^[0-9a-f]{64}$")
_STATES = {"STAGED", "ACTIVE", "ROLLED_BACK"}
@@ -27,13 +25,6 @@ class RuntimeDeploymentState:
return self.deployment_state == "ACTIVE"
def health_payload(self) -> dict[str, Any]:
# This listener owns liveness only. Its environment-derived release
# declaration must never become runtime identity evidence by itself.
identity = evaluate_component_runtime_identity(
component_id=None,
manifest_path=None,
receipt_path=None,
)
return {
"status": "ok",
"version": self.version,
@@ -42,8 +33,6 @@ class RuntimeDeploymentState:
"deployment_state": self.deployment_state,
"production_deployed": self.production_deployed,
"health_timestamp": dt.datetime.now(dt.timezone.utc).isoformat(),
"drift_status": identity["status"],
"component_runtime_identity": identity,
}
@classmethod
@@ -1,349 +0,0 @@
from __future__ import annotations
"""Authenticated Gitea consistency provider using the real governed Git transport.
The provider proves Gitea consistency without inventing a REST API:
* local HEAD == source_oid,
* local source_oid^{tree} == tree_oid,
* origin identifies the governed repository,
* exact checked-out branch exists remotely, and
* git ls-remote reports exactly source_oid for that branch.
It then writes an append-only root-owned v2 provider receipt using only governed
policy for provider identity, operation, store root, store id and modes.
The result explicitly records ``transport=git-ls-remote`` and
``api_verified=false``. It must never be described as REST/API evidence.
"""
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import stat
import subprocess
from typing import Callable, Mapping
from urllib.parse import urlsplit, urlunsplit
RECEIPT_SCHEMA = "aios.trusted-evidence-provider-receipt.v2"
RECEIPT_VERSION = 2
_OID_RE = re.compile(r"^[0-9a-f]{40}$")
_REF_RE = re.compile(r"^refs/heads/[A-Za-z0-9._/-]+$")
_REPO_RE = re.compile(r"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$")
class GiteaProviderError(RuntimeError):
pass
@dataclass(frozen=True)
class GitCommandResult:
returncode: int
stdout: str = ""
stderr: str = ""
GitRunner = Callable[[tuple[str, ...], Path], GitCommandResult]
def canonical_json_bytes(payload: Mapping) -> bytes:
return json.dumps(
dict(payload),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def canonical_sha256(payload: Mapping) -> str:
return hashlib.sha256(canonical_json_bytes(payload)).hexdigest()
def _runner(args: tuple[str, ...], cwd: Path) -> GitCommandResult:
proc = subprocess.run(
list(args),
cwd=str(cwd),
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=45,
check=False,
)
return GitCommandResult(proc.returncode, proc.stdout, proc.stderr)
def _git(run: GitRunner, cwd: Path, *args: str, error: str) -> str:
result = run(("git", *args), cwd)
if result.returncode != 0:
raise GiteaProviderError(f"{error}: {result.stderr.strip()[:300]}")
return result.stdout.strip()
def _oid(value: str, field: str) -> str:
value = str(value).strip().lower()
if not _OID_RE.fullmatch(value):
raise GiteaProviderError(f"{field.upper()}_INVALID")
return value
def _mode(value) -> int:
if isinstance(value, int):
return value
text = str(value).strip()
if text.isdigit():
return int(text, 8)
raise GiteaProviderError("PROVIDER_MODE_INVALID")
def _safe_ref(value: str) -> Path:
raw = str(value).strip()
p = Path(raw)
if not raw or p.is_absolute() or any(part in ("", ".", "..") for part in p.parts):
raise GiteaProviderError("PROVIDER_RECEIPT_REF_INVALID")
return p
def _safe_remote_url(url: str) -> str:
url = str(url).strip()
if "://" in url:
parts = urlsplit(url)
host = parts.hostname or ""
port = f":{parts.port}" if parts.port else ""
return urlunsplit((parts.scheme, host + port, parts.path, "", ""))
if ":" in url and not url.startswith(("/", "./", "../")):
left, right = url.split(":", 1)
return f"{left.rsplit('@', 1)[-1]}:{right}"
return url
def repository_identity_from_remote(url: str) -> str:
safe = _safe_remote_url(url)
if "://" in safe:
path = urlsplit(safe).path
elif ":" in safe and not safe.startswith("/"):
path = safe.split(":", 1)[1]
else:
path = safe
parts = [x for x in path.strip("/").split("/") if x]
if parts and parts[0] == "git":
parts = parts[1:]
if len(parts) < 2:
raise GiteaProviderError("GITEA_REPOSITORY_IDENTITY_UNAVAILABLE")
owner, repo = parts[-2], parts[-1]
if repo.endswith(".git"):
repo = repo[:-4]
identity = f"{owner}/{repo}".lower()
if not _REPO_RE.fullmatch(identity):
raise GiteaProviderError("GITEA_REPOSITORY_IDENTITY_INVALID")
return identity
def load_gitea_policy(policy_path: str | Path) -> dict:
raw = json.loads(Path(policy_path).read_text(encoding="utf-8"))
defaults = dict(raw.get("defaults") or {})
providers = raw.get("providers")
if not isinstance(providers, dict) or not isinstance(providers.get("gitea"), dict):
raise GiteaProviderError("GITEA_PROVIDER_POLICY_MISSING")
cfg = dict(providers["gitea"])
return {
"provider_identity": str(cfg["provider_identity"]),
"store_id": str(cfg["store_id"]),
"store_root": Path(str(cfg["store_root"])),
"provider_operation": str(cfg["allowed_operations"][0]),
"expected_owner_uid": int(cfg.get("expected_owner_uid", defaults.get("expected_owner_uid", 0))),
"directory_mode": min(
_mode(x) for x in cfg.get(
"allowed_directory_modes",
defaults.get("allowed_directory_modes", ["0700", "0750"]),
)
),
"receipt_mode": min(
_mode(x) for x in cfg.get(
"allowed_receipt_modes",
defaults.get("allowed_receipt_modes", ["0600", "0640"]),
)
),
"authentication_kind": str(
cfg.get(
"authentication_kind",
defaults.get("authentication_kind", "root-owned-store-v1"),
)
),
}
def collect_gitea_transport_consistency(
*,
repo_root: str | Path,
source_oid: str,
tree_oid: str,
expected_repository: str = "aios/aios",
remote: str = "origin",
git_runner: GitRunner | None = None,
) -> dict:
repo_root = Path(repo_root).resolve()
if not repo_root.is_dir():
raise GiteaProviderError("GITEA_CHECKOUT_UNAVAILABLE")
source_oid = _oid(source_oid, "source_oid")
tree_oid = _oid(tree_oid, "tree_oid")
expected_repository = str(expected_repository).strip().lower()
if not _REPO_RE.fullmatch(expected_repository):
raise GiteaProviderError("GITEA_EXPECTED_REPOSITORY_INVALID")
if not re.fullmatch(r"[A-Za-z0-9._/-]+", remote) or remote.startswith(("/", "-")):
raise GiteaProviderError("GITEA_REMOTE_NAME_INVALID")
run = git_runner or _runner
if _git(run, repo_root, "rev-parse", "--is-inside-work-tree",
error="GITEA_LOCAL_REPOSITORY_REQUIRED").lower() != "true":
raise GiteaProviderError("GITEA_LOCAL_REPOSITORY_REQUIRED")
actual_source = _oid(
_git(run, repo_root, "rev-parse", "HEAD^{commit}",
error="GITEA_LOCAL_SOURCE_UNAVAILABLE"),
"actual_source_oid",
)
if actual_source != source_oid:
raise GiteaProviderError("GITEA_LOCAL_SOURCE_OID_MISMATCH")
actual_tree = _oid(
_git(run, repo_root, "rev-parse", f"{source_oid}^{{tree}}",
error="GITEA_LOCAL_TREE_UNAVAILABLE"),
"actual_tree_oid",
)
if actual_tree != tree_oid:
raise GiteaProviderError("GITEA_LOCAL_TREE_OID_MISMATCH")
branch = _git(
run, repo_root, "symbolic-ref", "--quiet", "HEAD",
error="GITEA_GOVERNED_BRANCH_REQUIRED",
)
if not _REF_RE.fullmatch(branch):
raise GiteaProviderError("GITEA_GOVERNED_BRANCH_INVALID")
remote_url = _git(
run, repo_root, "remote", "get-url", remote,
error="GITEA_REMOTE_UNAVAILABLE",
)
identity = repository_identity_from_remote(remote_url)
if identity != expected_repository:
raise GiteaProviderError("GITEA_REPOSITORY_IDENTITY_MISMATCH")
output = _git(
run, repo_root, "ls-remote", "--exit-code", remote, branch,
error="GITEA_REMOTE_REF_UNAVAILABLE",
)
rows = []
for line in output.splitlines():
parts = line.split()
if len(parts) >= 2 and parts[1] == branch:
rows.append((parts[0].lower(), parts[1]))
if len(rows) != 1:
raise GiteaProviderError("GITEA_REMOTE_REF_AMBIGUOUS")
remote_oid = _oid(rows[0][0], "remote_ref_oid")
if remote_oid != source_oid:
raise GiteaProviderError("GITEA_REMOTE_REF_OID_MISMATCH")
return {
"consistent": True,
"repository": identity,
"source_commit": source_oid,
"remote_ref_oid": remote_oid,
"tree_oid": tree_oid,
"remote_ref": branch,
"remote_url": _safe_remote_url(remote_url),
"transport": "git-ls-remote",
"api_verified": False,
}
def _ensure_root_directory(path: Path, mode: int, expected_uid: int) -> None:
if os.geteuid() != expected_uid:
raise GiteaProviderError("GITEA_PROVIDER_ROOT_REQUIRED")
path.mkdir(parents=True, exist_ok=True, mode=mode)
st = os.lstat(path)
if stat.S_ISLNK(st.st_mode) or not stat.S_ISDIR(st.st_mode):
raise GiteaProviderError("GITEA_PROVIDER_STORE_INVALID")
if st.st_uid != expected_uid:
raise GiteaProviderError("GITEA_PROVIDER_STORE_OWNER_INVALID")
if stat.S_IMODE(st.st_mode) & 0o022:
raise GiteaProviderError("GITEA_PROVIDER_STORE_MODE_INVALID")
def _write_receipt_exclusive(path: Path, payload: Mapping, mode: int) -> str:
data = canonical_json_bytes(payload)
if path.exists() or path.is_symlink():
raise GiteaProviderError("GITEA_PROVIDER_RECEIPT_EXISTS")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
flags |= getattr(os, "O_NOFOLLOW", 0)
flags |= getattr(os, "O_BINARY", 0)
fd = os.open(path, flags, mode)
try:
view = memoryview(data)
while view:
written = os.write(fd, view)
if written <= 0:
raise GiteaProviderError("GITEA_PROVIDER_RECEIPT_WRITE_FAILED")
view = view[written:]
os.fsync(fd)
finally:
os.close(fd)
os.chmod(path, mode)
return hashlib.sha256(data).hexdigest()
def produce_gitea_consistency_receipt(
*,
repo_root: str | Path,
source_oid: str,
tree_oid: str,
policy_path: str | Path,
receipt_ref: str,
expected_repository: str = "aios/aios",
) -> tuple[str, dict]:
cfg = load_gitea_policy(policy_path)
if cfg["authentication_kind"] != "root-owned-store-v1":
raise GiteaProviderError("GITEA_PROVIDER_AUTH_KIND_UNSUPPORTED")
result = collect_gitea_transport_consistency(
repo_root=repo_root,
source_oid=source_oid,
tree_oid=tree_oid,
expected_repository=expected_repository,
)
now = datetime.now(timezone.utc).isoformat()
run_id = f"gitea-git-transport-{source_oid[:12]}-{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}"
rel = _safe_ref(receipt_ref)
target = cfg["store_root"].joinpath(rel)
root = cfg["store_root"].resolve()
if not str(target.resolve(strict=False)).startswith(str(root) + os.sep):
raise GiteaProviderError("GITEA_PROVIDER_RECEIPT_REF_ESCAPE")
_ensure_root_directory(root, cfg["directory_mode"], cfg["expected_owner_uid"])
parent = root
for part in rel.parts[:-1]:
parent = parent / part
_ensure_root_directory(parent, cfg["directory_mode"], cfg["expected_owner_uid"])
payload = {
"schema": RECEIPT_SCHEMA,
"version": RECEIPT_VERSION,
"provider_identity": cfg["provider_identity"],
"provider_operation": cfg["provider_operation"],
"operation_run_id": run_id,
"result_reference": f"git:{result['remote_url']}#{result['remote_ref']}",
"content_sha256": canonical_sha256(result),
"source_oid": _oid(source_oid, "source_oid"),
"tree_oid": _oid(tree_oid, "tree_oid"),
"environment": "hw-isolated-linux-git-transport",
"issued_at": now,
"result": result,
"authentication": {
"kind": "root-owned-store-v1",
"store_id": cfg["store_id"],
},
}
sha = _write_receipt_exclusive(target, payload, cfg["receipt_mode"])
return sha, payload
@@ -1,58 +0,0 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
from governed_provider_auth_adapter import (
ALL_RELEASE_EVIDENCE_KINDS,
authenticate_build_artifacts,
gate_w_authentication_status,
)
from trusted_provider_receipts import (
ProviderAuthenticationError,
TrustedProviderReceiptResolver,
)
def produce_authenticated_evidence_closure(
request: Any,
*,
provider_receipt_resolver: TrustedProviderReceiptResolver | None,
source_root: Path,
builder_policy: Path,
legacy_producer=None,
):
"""Production wrapper for the existing evidence producer.
This is the governed operation entrypoint. It refuses to call the legacy
closure assembler until *all* final-release provider receipts are resolved
from authenticated stores. For Gate-W, use `validate_gate_w_provider_auth`
instead; canary/rollback remain pending until Gate-R.
"""
if provider_receipt_resolver is None:
raise ProviderAuthenticationError("PROVIDER_AUTHENTICATION_REQUIRED")
if legacy_producer is None:
from governed_release_evidence_producer import produce_evidence_closure as legacy_producer
authenticated_request = authenticate_build_artifacts(
request,
resolver=provider_receipt_resolver,
required_kinds=ALL_RELEASE_EVIDENCE_KINDS,
)
return legacy_producer(
authenticated_request,
source_root=source_root,
builder_policy=builder_policy,
)
def validate_gate_w_provider_auth(
request: Any,
*,
provider_receipt_resolver: TrustedProviderReceiptResolver | None,
) -> dict[str, str]:
"""Gate-W authentication check without pretending Gate-R evidence exists."""
return gate_w_authentication_status(
request,
resolver=provider_receipt_resolver,
)
@@ -1,323 +0,0 @@
#!/usr/bin/env python3
"""Governed evidence runtime registration chain (V2R2C2 P0).
Establishes the REAL runtime chain for the governed release evidence producer:
Inventory -> Resolver -> operation -> Producer -> closure
The Producer alone (governed_release_evidence_producer) is not "runtime
registered" just because its OperationDefinition exists — registration only
holds when a runtime can actually discover the operation in an inventory,
resolve its ``handler_ref`` to the real callable, and invoke it to produce a
closure. That full path is what this module wires and what the
``PRODUCER_RUNTIME_REGISTERED_PASS`` verdict proves.
- Inventory : EvidenceOperationInventory — the set of governed evidence
operations the authority can execute (unions the producer
registry; extensible to future governed operations).
- Resolver : resolve_handler(handler_ref) maps the operation's declared
handler reference to the actual ``produce_evidence_closure``
callable. Resolution is exact and restrictive — it never
imports by arbitrary string eval.
- Producer : the resolved callable executes with validated, receipt-bound
build artifacts and materializes the evidence closure.
- closure : the produced source_root/<version>/ closure.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Callable, Iterable, Mapping
from aios_baseline.operations import OperationDefinition, OperationRegistry
from governed_authenticated_evidence_operation import (
produce_authenticated_evidence_closure,
)
from governed_provider_auth_adapter import gate_w_authentication_status
from governed_release_evidence_producer import (
EvidenceProduceError,
GovernedBuildArtifacts,
ProduceResult,
TRUSTED_PROVIDER_EVIDENCE_TYPES,
AUTHENTICATED_EVIDENCE_HANDLER_REF,
produce_evidence_closure,
producer_operation_definition,
producer_registry,
)
from trusted_provider_receipts import (
ProviderAuthenticationError,
TrustedProviderPolicy,
TrustedProviderReceiptResolver,
)
# Exact, restrictive handler_ref -> callable map. This is the ONLY way
# handler_refs are resolved; no arbitrary import/eval.
_HANDLERS: Mapping[str, Callable[..., ProduceResult]] = {
"governed_release_evidence_producer.produce_evidence_closure": produce_evidence_closure,
AUTHENTICATED_EVIDENCE_HANDLER_REF: produce_authenticated_evidence_closure,
}
class EvidenceOperationInventory:
"""Inventory of governed evidence operations available to the authority."""
def __init__(self, *registries: OperationRegistry) -> None:
definitions: list[OperationDefinition] = []
seen: set[str] = set()
for reg in registries:
for definition in reg.definitions:
if definition.operation_id in seen:
continue
seen.add(definition.operation_id)
definitions.append(definition)
self._registry = OperationRegistry(definitions)
self._by_id = {d.operation_id: d for d in definitions}
@classmethod
def default(cls) -> "EvidenceOperationInventory":
"""The standard inventory: the governed release evidence producer."""
return cls(producer_registry())
def contains(self, operation_id: str) -> bool:
return operation_id in self._by_id
def operation_ids(self) -> tuple[str, ...]:
return tuple(self._by_id)
def get(self, operation_id: str) -> OperationDefinition | None:
return self._by_id.get(operation_id)
def resolve_handler(handler_ref: str) -> Callable[..., ProduceResult]:
"""Resolver: turn an OperationDefinition.handler_ref into the real callable.
Exact lookup in a fixed allowlist. Unknown or non-producer references raise.
"""
handler = _HANDLERS.get(handler_ref)
if handler is None:
raise EvidenceProduceError("HANDLER_REF_UNRESOLVED")
return handler
def _payload_to_artifacts(payload: Mapping[str, Any]) -> GovernedBuildArtifacts:
try:
return GovernedBuildArtifacts(
version=payload["version"],
candidate_id=payload["candidate_id"],
source_commit=payload["source_commit"],
tree_oid=payload["tree_oid"],
artifact=bytes.fromhex(payload["artifact_hex"]),
manifest=payload["manifest"],
package_closure=bytes.fromhex(payload["package_closure_hex"]),
policy=payload["policy"],
tests=payload["tests"],
canary=payload["canary"],
gitea=payload["gitea"],
rollback=payload["rollback"],
secret_scan=payload["secret_scan"],
deployment=payload["deployment"],
tag=payload["tag"],
finalization=payload["finalization"],
rollback_sha256=payload.get("rollback_sha256", ""),
issued_at=payload.get("issued_at", ""),
)
except (KeyError, TypeError, ValueError) as exc:
raise EvidenceProduceError("PAYLOAD_SCHEMA_INVALID") from exc
def invoke_governed_produce(
*,
builder_policy: Path,
source_root: Path,
payload: Mapping[str, Any],
expected_owner_uid: int | None = 0,
) -> dict[str, Any]:
"""Real runtime chain: Inventory -> Resolver -> operation -> Producer -> closure.
Returns a verdict dict. Only a genuine end-to-end invocation that reaches
evidence production reports ``status == "PRODUCER_RUNTIME_REGISTERED_PASS"``.
"""
inventory = EvidenceOperationInventory.default()
operation_id = "governed.release.evidence.produce"
if not inventory.contains(operation_id):
raise EvidenceProduceError("OPERATION_NOT_IN_INVENTORY")
definition = inventory.get(operation_id)
assert definition is not None # guaranteed by contains()
handler = resolve_handler(definition.handler_ref)
request = _payload_to_artifacts(payload)
result = handler(
builder_policy=builder_policy,
source_root=source_root,
request=request,
expected_owner_uid=expected_owner_uid,
)
# The resolved handler returned a real ProduceResult from a real closure;
# the registered operation genuinely produced evidence.
return {
"status": "PRODUCER_RUNTIME_REGISTERED_PASS",
"operation_id": operation_id,
"handler_ref": definition.handler_ref,
"handler_resolved": handler.__module__ + "." + handler.__name__,
"evidence_types_bound": list(TRUSTED_PROVIDER_EVIDENCE_TYPES),
"result": {
"produce_status": result.status,
"idempotent": result.idempotent,
"receipt_sha256": result.receipt_sha256,
"closure_dir": result.closure_dir,
"evidence_digests": dict(result.evidence_digests),
},
}
# ---------------------------------------------------------------------------
# V2R2C2 Round-3: Provider-Authenticated runtime chain.
#
# Inventory -> Resolver -> authenticated operation -> TrustedProviderReceiptResolver
# -> Producer assembler (second-layer bound)
#
# The Round-3 runtime builds TrustedProviderReceiptResolver from the governed
# evidence-provider policy (never from request-supplied store paths, provider
# identities, key ids, or HMAC material). Gate-W authenticates only the
# provider classes that can exist before staging (tests/Gitea/secret-scan);
# canary/rollback remain pending until Gate-R.
# ---------------------------------------------------------------------------
# Sequence used for a full final release closure (Gate-R).
ALL_RELEASE_EVIDENCE_KINDS = ("tests", "canary", "gitea", "rollback", "secret_scan")
# Provider classes that are real before staging (Gate-W).
GATE_W_EVIDENCE_KINDS = ("tests", "gitea", "secret_scan")
_DEFAULT_PROVIDER_POLICY = Path(__file__).resolve().parents[2] / "config" / "gitops" / "trusted-evidence-providers.json"
def _load_provider_policy(path: Path) -> Mapping[str, Any]:
try:
decoded = json.loads(Path(path).read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise EvidenceProduceError("PROVIDER_POLICY_UNAVAILABLE") from exc
if not isinstance(decoded, Mapping) or decoded.get("schema") != "aios.trusted-evidence-providers.v1":
raise EvidenceProduceError("PROVIDER_POLICY_INVALID")
return decoded
def _policy_from_config(name: str, raw: Mapping[str, Any], defaults: Mapping[str, Any]) -> TrustedProviderPolicy:
def _mode(value: object, field: str) -> int:
if isinstance(value, int):
return value
if isinstance(value, str) and value.isdigit():
return int(value, 8)
raise EvidenceProduceError("PROVIDER_POLICY_INVALID")
try:
store_root = Path(str(raw["store_root"])).expanduser()
allowed_ops = frozenset(str(op) for op in raw["allowed_operations"])
return TrustedProviderPolicy(
evidence_kind=name,
provider_identity=str(raw["provider_identity"]),
store_id=str(raw["store_id"]),
store_root=store_root,
allowed_operations=allowed_ops,
expected_owner_uid=_mode(raw.get("expected_owner_uid", defaults.get("expected_owner_uid", 0)), "expected_owner_uid"),
allowed_directory_modes=frozenset(
_mode(v, "allowed_directory_modes")
for v in raw.get("allowed_directory_modes", defaults.get("allowed_directory_modes", ["0700", "0750"]))
),
allowed_receipt_modes=frozenset(
_mode(v, "allowed_receipt_modes")
for v in raw.get("allowed_receipt_modes", defaults.get("allowed_receipt_modes", ["0600", "0640"]))
),
authentication_kind=str(raw.get("authentication_kind", defaults.get("authentication_kind", "root-owned-store-v1"))),
max_age_seconds=raw.get("max_age_seconds", defaults.get("max_age_seconds")),
)
except (KeyError, TypeError, ValueError) as exc:
raise EvidenceProduceError("PROVIDER_POLICY_INVALID") from exc
def build_provider_receipt_resolver(
provider_policy_path: Path = _DEFAULT_PROVIDER_POLICY,
*,
hmac_key_resolver: Callable[[str], bytes] | None = None,
now_fn: Callable[[], Any] | None = None,
) -> TrustedProviderReceiptResolver:
"""Build TrustedProviderReceiptResolver from the governed evidence-provider policy.
Store paths, provider identities, key ids and HMAC material come only from
the governed policy/secret resolver — never from a request payload.
"""
policy = _load_provider_policy(provider_policy_path)
defaults = dict(policy.get("defaults") or {})
providers = policy.get("providers")
if not isinstance(providers, Mapping) or not providers:
raise EvidenceProduceError("PROVIDER_POLICY_INVALID")
policies: dict[str, TrustedProviderPolicy] = {}
for name, raw in providers.items():
if not isinstance(raw, Mapping):
raise EvidenceProduceError("PROVIDER_POLICY_INVALID")
policies[name] = _policy_from_config(name, raw, defaults)
return TrustedProviderReceiptResolver(policies, hmac_key_resolver=hmac_key_resolver, now_fn=now_fn)
def _resolver_guard(resolver: TrustedProviderReceiptResolver | None) -> TrustedProviderReceiptResolver:
if resolver is None:
raise ProviderAuthenticationError("PROVIDER_AUTHENTICATION_REQUIRED")
return resolver
def validate_gate_w_provider_auth(
request: GovernedBuildArtifacts,
*,
provider_receipt_resolver: TrustedProviderReceiptResolver | None,
) -> dict[str, str]:
"""Gate-W: authenticate only the provider classes real before staging.
Returns PRODUCER_RUNTIME_REGISTRATION_PASS / PROVIDER_AUTH_ENFORCEMENT_PASS /
FINAL_RELEASE_CLOSURE_PENDING_GATE_R. Canary/rollback are intentionally not
required here — they remain pending until Gate-R.
"""
resolver = _resolver_guard(provider_receipt_resolver)
gate_w_authentication_status(request, resolver=resolver)
return {
"producer_runtime": "PRODUCER_RUNTIME_REGISTRATION_PASS",
"provider_auth": "PROVIDER_AUTH_ENFORCEMENT_PASS",
"final_release_closure": "FINAL_RELEASE_CLOSURE_PENDING_GATE_R",
}
def _legacy_closure_adapter(authenticated_request: GovernedBuildArtifacts, *, source_root: Path, builder_policy: Path) -> ProduceResult:
"""Invoke the legacy closure assembler on the resolver-authenticated request.
The classified evidence fields now carry ``provider_receipt`` provenance;
the producer's second-layer validator re-asserts content/OID binding on it.
"""
return produce_evidence_closure(
builder_policy=builder_policy,
source_root=source_root,
request=authenticated_request,
expected_owner_uid=0,
)
def produce_authenticated_evidence(
request: GovernedBuildArtifacts,
*,
provider_receipt_resolver: TrustedProviderReceiptResolver | None,
source_root: Path,
builder_policy: Path,
required_kinds: Iterable[str] = ALL_RELEASE_EVIDENCE_KINDS,
) -> ProduceResult:
"""Gate-R production path: authenticate all five final-release provider
receipts, then invoke the legacy assembler only after authentication."""
resolver = _resolver_guard(provider_receipt_resolver)
try:
return produce_authenticated_evidence_closure(
request,
provider_receipt_resolver=resolver,
source_root=source_root,
builder_policy=builder_policy,
legacy_producer=_legacy_closure_adapter,
)
except ProviderAuthenticationError as exc:
raise EvidenceProduceError(str(exc)) from exc
@@ -1,87 +0,0 @@
from __future__ import annotations
from dataclasses import is_dataclass, replace
from typing import Any, Iterable, Mapping
from trusted_provider_receipts import (
ProviderAuthenticationError,
ResolvedEvidence,
TrustedProviderReceiptResolver,
)
ALL_RELEASE_EVIDENCE_KINDS = ("tests", "canary", "gitea", "rollback", "secret_scan")
GATE_W_EVIDENCE_KINDS = ("tests", "gitea", "secret_scan")
def authenticate_evidence_request(
evidence_kind: str,
evidence_request: Mapping[str, Any],
*,
source_oid: str,
tree_oid: str,
resolver: TrustedProviderReceiptResolver | None,
) -> dict[str, Any]:
if resolver is None:
raise ProviderAuthenticationError("PROVIDER_AUTHENTICATION_REQUIRED")
resolved: ResolvedEvidence = resolver.resolve_evidence(
evidence_kind,
evidence_request,
source_oid=source_oid,
tree_oid=tree_oid,
)
return {
**dict(resolved.result),
"provider_receipt": dict(resolved.provider_receipt),
}
def authenticate_build_artifacts(
request: Any,
*,
resolver: TrustedProviderReceiptResolver | None,
required_kinds: Iterable[str] = ALL_RELEASE_EVIDENCE_KINDS,
) -> Any:
"""Authenticate receipt_ref-only evidence and return a replaced request.
The input is expected to be the existing GovernedBuildArtifacts dataclass.
The function deliberately relies only on field names already used by the
producer (`source_commit`, `tree_oid`, and the evidence kind fields), so it
can be integrated without changing the deployment/release-plan contract.
"""
if resolver is None:
raise ProviderAuthenticationError("PROVIDER_AUTHENTICATION_REQUIRED")
if not is_dataclass(request):
raise TypeError("request must be a dataclass instance")
source_oid = getattr(request, "source_commit")
tree_oid = getattr(request, "tree_oid")
replacements: dict[str, Any] = {}
for kind in tuple(required_kinds):
evidence_request = getattr(request, kind, None)
if evidence_request is None:
raise ProviderAuthenticationError(f"EVIDENCE_{kind.upper()}_REQUIRED")
replacements[kind] = authenticate_evidence_request(
kind,
evidence_request,
source_oid=source_oid,
tree_oid=tree_oid,
resolver=resolver,
)
return replace(request, **replacements)
def gate_w_authentication_status(
request: Any,
*,
resolver: TrustedProviderReceiptResolver | None,
) -> dict[str, str]:
"""Authenticate Gate-W evidence without pretending final closure exists."""
authenticate_build_artifacts(
request,
resolver=resolver,
required_kinds=GATE_W_EVIDENCE_KINDS,
)
return {
"producer_runtime": "PRODUCER_RUNTIME_REGISTRATION_PASS",
"provider_auth": "PROVIDER_AUTH_ENFORCEMENT_PASS",
"final_release_closure": "FINAL_RELEASE_CLOSURE_PENDING_GATE_R",
}
@@ -1,379 +0,0 @@
"""Concrete transport adapter for the governed source publication boundary.
The adapter composes the already existing live writer executor. It owns no
leases, TaskPrincipal, credentials, retry policy, or release state machine.
Those remain server side. ``authenticated_readback`` is deliberately an
injected port: a local commit response is not evidence of an authenticated
source/tree binding.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping
import hashlib
import json
import re
from typing import Any, Protocol
from governed_unattended_release import SourcePublicationIntent
from task_plane.governed_writer_automation import GovernedWriterRequest
class GovernedPublicationError(RuntimeError):
"""Stable fail-closed publication error."""
def __init__(self, code: str):
self.code = code
super().__init__(code)
class GovernedWriterExecutor(Protocol):
def acquire(self, *, request: GovernedWriterRequest, job_id: str) -> Any: ...
def commit_and_readback(
self, *, session: Any, request: GovernedWriterRequest,
files: Mapping[str, bytes], expected_sha256: Mapping[str, str],
message: str,
) -> Mapping[str, Any]: ...
def submit_task_result(
self, *, session: Any, result: Mapping[str, Any], idempotency_key: str,
) -> Mapping[str, Any]: ...
class SourceFileReader(Protocol):
def __call__(self, intent: SourcePublicationIntent) -> Mapping[str, bytes]: ...
class AuthenticatedReadback(Protocol):
def __call__(
self, *, intent: SourcePublicationIntent, request: GovernedWriterRequest,
session: Any, commit_result: Mapping[str, Any],
) -> Mapping[str, Any]: ...
class AdmissionBindingReader(Protocol):
"""Read the server-side task/governed binding after admission."""
def __call__(
self, *, intent: SourcePublicationIntent,
admission_receipt: Mapping[str, Any],
) -> Mapping[str, Any]: ...
class GitTreeFileReader(Protocol):
"""Read bytes from an immutable committed tree object, never a workspace."""
def __call__(self, *, tree_oid: str, path: str) -> bytes: ...
class CommittedTreeSourceReader:
"""Adapt an authoritative git-object reader to the publication port."""
def __init__(self, reader: GitTreeFileReader) -> None:
if not callable(reader):
raise ValueError("git_object_reader_required")
self.reader = reader
def __call__(self, intent: SourcePublicationIntent) -> Mapping[str, bytes]:
return {
path: self.reader(tree_oid=intent.source_tree_oid, path=path)
for path in intent.file_allowlist
}
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA = re.compile(r"^[0-9a-f]{64}$")
_SAFE_CODE = re.compile(r"^(?:TASK_PRINCIPAL|EXECUTION_PLAN|GOVERNED)_[A-Z0-9_]{3,120}$")
_SAFE_OPERATION = re.compile(r"^[A-Za-z][A-Za-z0-9]{1,63}$")
def _oid(value: Any) -> str:
result = str(value or "").lower()
if not _OID.fullmatch(result):
raise GovernedPublicationError("PUBLICATION_OID_INVALID")
return result
def _sha(value: Any) -> str:
result = str(value or "").lower()
if not _SHA.fullmatch(result):
raise GovernedPublicationError("PUBLICATION_SHA_INVALID")
return result
def _canonical(value: Mapping[str, Any]) -> bytes:
try:
return json.dumps(
dict(value), sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise GovernedPublicationError("PUBLICATION_EVIDENCE_INVALID") from exc
class GovernedPublicationClient:
"""Run TaskLease -> TaskPrincipal -> WriterLease -> commit through ports.
``writer_executor`` is normally ``LiveGovernedWriterExecutor`` from
``task_plane.live_governed_writer_adapters``. ``source_files`` must return
the exact immutable bytes for the intent's allowlist; it is never allowed
to return a path or a mutable object. The authenticated readback port
must be backed by the authoritative provider/bridge and prove the final
source OID, tree OID, task, branch, base, generation, and execution digest.
"""
def __init__(
self,
*,
writer_executor: GovernedWriterExecutor,
source_files: SourceFileReader | None = None,
git_tree_file: GitTreeFileReader | None = None,
admission_binding: AdmissionBindingReader,
authenticated_readback: AuthenticatedReadback,
subject: str,
manifest_id: str,
runtime_fingerprint: str,
process_start_identity: str,
commit_message: str = "governed source publication",
diagnostic_sink: Callable[[Mapping[str, Any]], None] | None = None,
) -> None:
if (
writer_executor is None
or any(
not callable(getattr(writer_executor, name, None))
for name in (
"acquire", "commit_and_readback", "submit_task_result",
)
)
):
raise ValueError("writer_executor_required")
if source_files is not None:
raise ValueError("workspace_source_reader_forbidden")
if git_tree_file is None:
raise ValueError("git_object_source_reader_required")
if not callable(admission_binding) or not callable(authenticated_readback):
raise ValueError("authenticated_ports_required")
for name, value in (("subject", subject), ("manifest_id", manifest_id),
("runtime_fingerprint", runtime_fingerprint),
("process_start_identity", process_start_identity),
("commit_message", commit_message)):
if not isinstance(value, str) or not value:
raise ValueError(name + "_required")
self.writer_executor = writer_executor
self.source_files = None
self.git_tree_file = git_tree_file
self.admission_binding = admission_binding
self.authenticated_readback = authenticated_readback
self.subject = subject
self.manifest_id = manifest_id
self.runtime_fingerprint = runtime_fingerprint
self.process_start_identity = process_start_identity
self.commit_message = commit_message
if diagnostic_sink is not None and not callable(diagnostic_sink):
raise ValueError("diagnostic_sink_invalid")
self.diagnostic_sink = diagnostic_sink
def _record_uncertain_effect(self, *, exc: Exception, task_id: str,
stage: str) -> None:
"""Emit only allowlisted facts; never serialize an exception or body."""
if self.diagnostic_sink is None:
return
raw_status = getattr(exc, "status", None)
status = raw_status if type(raw_status) is int and 100 <= raw_status <= 599 else None
raw_code = getattr(exc, "error_code", None)
code = raw_code if isinstance(raw_code, str) and _SAFE_CODE.fullmatch(raw_code) else None
raw_operation = getattr(exc, "operation", None)
operation = (
raw_operation if isinstance(raw_operation, str)
and _SAFE_OPERATION.fullmatch(raw_operation) else stage
)
diagnostic = {
"task_id": task_id, "operation": operation,
"http_status": status,
"error_code": (
"transport_unknown" if status is None else (code or "issuer_error_unknown")
),
}
try:
self.diagnostic_sink(diagnostic)
except Exception:
# Diagnostic observation must never change the uncertainty policy.
pass
def _validate_admission(self, intent: SourcePublicationIntent, receipt: Mapping[str, Any]) -> Mapping[str, Any]:
if not isinstance(receipt, Mapping):
raise GovernedPublicationError("PUBLICATION_ADMISSION_INVALID")
for key, expected in (("task_id", intent.task_id), ("authority_base_oid", intent.authority_base_oid),
("target_branch", intent.target_branch), ("generation", intent.generation),
("execution_digest", intent.execution_digest)):
if receipt.get(key) != expected:
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_MISMATCH")
try:
binding = self.admission_binding(intent=intent, admission_receipt=receipt)
except Exception as exc:
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_UNAVAILABLE") from exc
if not isinstance(binding, Mapping):
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_INVALID")
expected = {
"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest,
}
if any(binding.get(key) != value for key, value in expected.items()):
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_MISMATCH")
job_id = binding.get("job_id")
if not isinstance(job_id, str) or not job_id:
raise GovernedPublicationError("PUBLICATION_JOB_BINDING_MISSING")
return dict(binding)
def _read_files(self, intent: SourcePublicationIntent) -> tuple[dict[str, bytes], dict[str, str]]:
try:
raw = {
path: self.git_tree_file(tree_oid=intent.source_tree_oid, path=path)
for path in intent.file_allowlist
}
except KeyError as exc:
raise GovernedPublicationError("PUBLICATION_FILE_SET_MISMATCH") from exc
except Exception as exc:
raise GovernedPublicationError("PUBLICATION_SOURCE_READ_FAILED") from exc
if not isinstance(raw, Mapping) or set(raw) != set(intent.file_allowlist):
raise GovernedPublicationError("PUBLICATION_FILE_SET_MISMATCH")
files: dict[str, bytes] = {}
hashes: dict[str, str] = {}
for path in intent.file_allowlist:
data = raw.get(path)
if isinstance(data, bytearray):
data = bytes(data)
if not isinstance(data, bytes):
raise GovernedPublicationError("PUBLICATION_FILE_BYTES_REQUIRED")
files[path] = bytes(data)
hashes[path] = hashlib.sha256(files[path]).hexdigest()
return files, hashes
def publish(self, intent: SourcePublicationIntent, admission_receipt: Mapping[str, Any]) -> dict[str, Any]:
if not isinstance(intent, SourcePublicationIntent):
raise GovernedPublicationError("PUBLICATION_INTENT_REQUIRED")
admission_binding = self._validate_admission(intent, admission_receipt)
job_id = str(admission_binding["job_id"])
files, hashes = self._read_files(intent)
request = GovernedWriterRequest(
subject=self.subject, task_id=intent.task_id, repository=intent.repository,
target_branch=intent.target_branch, risk=intent.risk, generation=intent.generation,
base_oid=intent.authority_base_oid,
required_capabilities=frozenset(intent.requested_capabilities),
file_allowlist=tuple(intent.file_allowlist),
plan_digest="sha256:" + intent.execution_digest,
manifest_id=self.manifest_id, runtime_fingerprint=self.runtime_fingerprint,
process_start_identity=self.process_start_identity,
)
stage = "acquireWriterLease"
try:
session = self.writer_executor.acquire(request=request, job_id=job_id)
stage = "commitFiles"
commit_result = self.writer_executor.commit_and_readback(
session=session, request=request, files=files,
expected_sha256=hashes, message=self.commit_message,
)
if not isinstance(commit_result, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_READBACK_INVALID")
readback = self.authenticated_readback(
intent=intent, request=request, session=session,
commit_result=commit_result,
)
except GovernedPublicationError:
raise
except Exception as exc:
# A transport exception after a mutation is indeterminate. Never
# resubmit it here; the caller must reconcile through the existing
# Job/recovery service.
self._record_uncertain_effect(exc=exc, task_id=intent.task_id, stage=stage)
raise GovernedPublicationError("PUBLICATION_EFFECT_UNKNOWN") from exc
if not isinstance(readback, Mapping) or readback.get("authenticated") is not True:
raise GovernedPublicationError("PUBLICATION_AUTHENTICATED_READBACK_REQUIRED")
required = {
"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest,
}
if any(readback.get(key) != value for key, value in required.items()):
raise GovernedPublicationError("PUBLICATION_READBACK_BINDING_MISMATCH")
for key in ("task_lease", "task_principal", "writer_lease"):
if not isinstance(readback.get(key), Mapping) or readback[key].get("task_id") != intent.task_id:
raise GovernedPublicationError("PUBLICATION_LEASE_CHAIN_MISSING")
source_oid = _oid(readback.get("source_oid"))
tree_oid = _oid(readback.get("tree_oid"))
nested_commit = readback.get("commit_receipt")
if nested_commit is not None and not isinstance(nested_commit, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_BINDING_MISMATCH")
commit_oid = _oid(readback.get("commit_oid") or (nested_commit or {}).get("commit_oid"))
if source_oid != commit_oid or tree_oid != intent.source_tree_oid:
raise GovernedPublicationError("PUBLICATION_SOURCE_TREE_BINDING_MISMATCH")
commit_receipt = readback.get("commit_receipt")
if not isinstance(commit_receipt, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_RECEIPT_REQUIRED")
# The live governed receipt binds commit_oid; the independently read
# Git tree is carried by authenticated readback. Preserve the original
# receipt instead of inventing a tree field absent from its schema.
if (commit_receipt.get("commit_oid") != commit_oid
or ("tree_oid" in commit_receipt and commit_receipt["tree_oid"] != tree_oid)):
raise GovernedPublicationError("PUBLICATION_COMMIT_BINDING_MISMATCH")
publication = {
**dict(readback), "authenticated": True, "task_id": intent.task_id,
"job_id": job_id, "source_oid": source_oid, "tree_oid": tree_oid,
"generation": intent.generation, "target_branch": intent.target_branch,
"authority_base_oid": intent.authority_base_oid,
"execution_digest": intent.execution_digest,
"commit_receipt": dict(commit_receipt),
}
task_result = {
"schema": "aios.governed-source-publication-result.v1",
"task_id": intent.task_id,
"job_id": job_id,
"repository": intent.repository,
"target_branch": intent.target_branch,
"generation": intent.generation,
"execution_digest": intent.execution_digest,
"authority_base_oid": _oid(intent.authority_base_oid),
"candidate_source_oid": _oid(intent.source_commit),
"published_source_oid": source_oid,
"source_tree_oid": tree_oid,
"commit_receipt_sha256": hashlib.sha256(
_canonical(commit_receipt)
).hexdigest(),
}
result_sha256 = hashlib.sha256(_canonical(task_result)).hexdigest()
idempotency_key = "governed-source-result-" + result_sha256
try:
submitted = self.writer_executor.submit_task_result(
session=session, result=task_result,
idempotency_key=idempotency_key,
)
if (
not isinstance(submitted, Mapping)
or submitted.get("task_id") != intent.task_id
or submitted.get("status") != "VERIFYING"
or submitted.get("result") != task_result
):
raise GovernedPublicationError(
"PUBLICATION_TASK_RESULT_READBACK_INVALID"
)
except GovernedPublicationError:
raise
except Exception as exc:
# A lost result response may have changed the durable Task. Never
# re-register, reclaim or retry here.
self._record_uncertain_effect(
exc=exc, task_id=intent.task_id, stage="submitTaskResult",
)
raise GovernedPublicationError("PUBLICATION_EFFECT_UNKNOWN") from exc
publication.update(
task_result=task_result,
task_result_idempotency_key=idempotency_key,
task_result_submission={
"task_id": submitted["task_id"], "status": submitted["status"],
"revision": submitted.get("revision"),
"idempotent": submitted.get("idempotent", False),
"result_sha256": result_sha256,
},
)
return publication
@@ -1,634 +0,0 @@
#!/usr/bin/env python3
"""Governed release evidence producer (V2R2C2 P0, operation governed.release.evidence.produce).
This module is the *producer* half of the governed evidence pipeline. It never
invents candidate/release evidence: every digest and pass/fail flag in the closure
is derived from real build artifacts and real verification executions supplied as
input. It assembles a self-consistent, root-owned evidence closure below
``source_root/<version>`` that ``TrustedBuilderEvidenceMaterializer`` (the projection
half) can re-validate and project byte-for-byte.
Contract (see docs/V2R2C2-P0-convergence-20260909.md section 1):
- operation_id : governed.release.evidence.produce
- input : governed build artifacts + deployment conventions (no hand-write)
- output : source_root/<version>/ full evidence_layout closure + receipt.json
- fail-closed : mismatched secret-scan digest, tampered artifact, non-passing
tests/canary/rollback/secret-scan, or inconsistent gitea all abort
without writing a closure.
- idempotent : an existing identical closure is accepted; a conflicting one fails.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import secrets
from dataclasses import dataclass, field
from pathlib import Path
import shutil
import stat
from typing import Any, Mapping
from aios_baseline.operations import OperationDefinition, OperationRegistry
from aios_trusted_candidate_builder import (
load_builder_policy,
TrustedCandidateBuilderConfig,
)
from release_evidence_materializer import EvidenceMaterializationError
from release_version import validate_release_version
PRODUCE_SCHEMA = "aios.governed-release-evidence-producer.v1"
EVIDENCE_RECEIPT_SCHEMA = "aios.trusted-candidate-builder.evidence.v1"
# Each governed evidence type (tests/canary/gitea/rollback/secret_scan) must be
# bound to a trusted-provider receipt. A plain caller-supplied {"passed": true}
# is never accepted on its own: the producer requires the receipt that proves a
# trusted provider actually ran the check and produced the referenced result.
TRUSTED_PROVIDER_RECEIPT_SCHEMA = "aios.trusted-provider.evidence.v1"
TRUSTED_PROVIDER_EVIDENCE_TYPES = ("tests", "canary", "gitea", "rollback", "secret_scan")
class EvidenceProduceError(RuntimeError):
"""Stable, non-sensitive producer failure."""
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=True,
).encode("utf-8")
def _file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with Path(path).open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _sha256_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def _validate_oid(value: object, name: str) -> str:
normalized = str(value or "").strip().lower()
if (
len(normalized) != 40
or any(char not in "0123456789abcdef" for char in normalized)
):
raise EvidenceProduceError(f"{name.upper()}_INVALID")
return normalized
def _validate_identifier(value: object, name: str) -> str:
normalized = str(value or "").strip()
if (
not normalized
or len(normalized) > 255
or normalized[0] in ".-"
or any(
char not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._:-"
for char in normalized
)
):
raise EvidenceProduceError(f"{name.upper()}_INVALID")
return normalized
def _validate_sha256(value: object, name: str, *, allow_empty: bool = False) -> str:
normalized = str(value or "").strip().lower()
if allow_empty and not normalized:
return ""
if (
len(normalized) != 64
or any(char not in "0123456789abcdef" for char in normalized)
):
raise EvidenceProduceError(f"{name.upper()}_INVALID")
return normalized
@dataclass(frozen=True)
class GovernedBuildArtifacts:
"""Real build artifacts + real verification results. Producer never invents these."""
version: str
candidate_id: str
source_commit: str
tree_oid: str
artifact: bytes
manifest: Mapping[str, Any]
package_closure: bytes
policy: Mapping[str, Any]
tests: Mapping[str, Any]
canary: Mapping[str, Any]
gitea: Mapping[str, Any]
rollback: Mapping[str, Any]
secret_scan: Mapping[str, Any]
# Deployment conventions (supplied by release planning, not evidence invention).
deployment: Mapping[str, Any]
tag: Mapping[str, Any]
finalization: Mapping[str, Any]
rollback_sha256: str = ""
issued_at: str = ""
@dataclass(frozen=True)
class ProduceResult:
status: str
idempotent: bool
receipt_sha256: str
closure_dir: str
evidence_digests: Mapping[str, str] = field(default_factory=dict)
def _require_trusted_provider_receipt(
name: str,
evidence: Mapping[str, Any],
source_commit: str,
tree_oid: str,
) -> None:
"""Require a trusted-provider binding for `evidence` (second-layer check).
A caller may never pass a bare ``{"passed": true}``: each governed evidence
type must be bound to a trusted provider. Two accepted forms, both
fail-closed on any missing/structurally invalid/digest-mismatched binding:
1. Round-2 native ``receipt`` (schema ``aios.trusted-provider.evidence.v1``)
— a caller-carried receipt.
2. Round-3 resolver-authenticated ``provider_receipt`` provenance (schema
``aios.trusted-evidence-provider-receipt.v2``) — produced by
``TrustedProviderReceiptResolver`` from an authenticated store. Its input
is the resolver-authenticated provenance, never a caller-supplied body.
This helper is a *second-layer* binding validator: for the Round-3 path the
authoritative proof is the resolver's authenticated provenance embedded as
``provider_receipt``, and this check only re-asserts content/OID binding on
top of it.
"""
receipt = evidence.get("receipt")
if isinstance(receipt, Mapping) and receipt:
body = {k: v for k, v in dict(evidence).items() if k != "receipt"}
expected = _sha256_bytes(_canonical_json(body))
if receipt.get("schema") != TRUSTED_PROVIDER_RECEIPT_SCHEMA:
raise EvidenceProduceError("EVIDENCE_RECEIPT_INVALID")
if receipt.get("content_sha256") != expected:
raise EvidenceProduceError("EVIDENCE_RECEIPT_CONTENT_MISMATCH")
for f in ("provider_identity", "operation_run_id", "result_reference",
"environment", "issued_at"):
if not str(receipt.get(f, "") or "").strip():
raise EvidenceProduceError("EVIDENCE_RECEIPT_INVALID")
if receipt.get("source_oid") not in (None, "", source_commit):
raise EvidenceProduceError("EVIDENCE_RECEIPT_SOURCE_OID_MISMATCH")
if receipt.get("tree_oid") not in (None, "", tree_oid):
raise EvidenceProduceError("EVIDENCE_RECEIPT_TREE_OID_MISMATCH")
return
provider_receipt = evidence.get("provider_receipt")
if isinstance(provider_receipt, Mapping) and provider_receipt:
if (
provider_receipt.get("schema")
!= "aios.trusted-evidence-provider-receipt.v2"
):
raise EvidenceProduceError("EVIDENCE_RECEIPT_INVALID")
for f in ("provider_identity", "provider_operation", "operation_run_id",
"result_reference", "content_sha256", "source_oid", "tree_oid",
"issued_at"):
if not str(provider_receipt.get(f, "") or "").strip():
raise EvidenceProduceError("EVIDENCE_RECEIPT_INVALID")
if provider_receipt.get("source_oid") != source_commit:
raise EvidenceProduceError("EVIDENCE_RECEIPT_SOURCE_OID_MISMATCH")
if provider_receipt.get("tree_oid") != tree_oid:
raise EvidenceProduceError("EVIDENCE_RECEIPT_TREE_OID_MISMATCH")
return
raise EvidenceProduceError("EVIDENCE_RECEIPT_MISSING")
def _verify_artifacts(
config: TrustedCandidateBuilderConfig,
request: GovernedBuildArtifacts,
) -> dict[str, str]:
"""Validate input consistency (fail-closed) and return the evidence digests."""
try:
version = validate_release_version(request.version)
except ValueError as exc:
raise EvidenceProduceError("VERSION_INVALID") from exc
if version != request.version:
raise EvidenceProduceError("VERSION_INVALID")
candidate_id = _validate_identifier(request.candidate_id, "candidate_id")
source_commit = _validate_oid(request.source_commit, "source_commit")
tree_oid = _validate_oid(request.tree_oid, "tree_oid")
if (
set(request.manifest) != {
"artifact_sha256", "package_closure_sha256",
}
):
raise EvidenceProduceError("MANIFEST_SCHEMA_INVALID")
closure_sha = _validate_sha256(
request.manifest.get("package_closure_sha256"), "package_closure_sha256"
)
artifact_sha = _sha256_bytes(request.artifact)
if request.manifest["artifact_sha256"] != artifact_sha:
raise EvidenceProduceError("MANIFEST_ARTIFACT_DIGEST_MISMATCH")
# manifest_sha256 is the digest of the manifest evidence file itself. It is
# derived (never invented) from the manifest content; the manifest does not
# embed its own digest to avoid a self-referential fixed point.
manifest_sha = _sha256_bytes(_canonical_json(dict(request.manifest)))
if _sha256_bytes(request.package_closure) != closure_sha:
raise EvidenceProduceError("PACKAGE_CLOSURE_DIGEST_MISMATCH")
# policy_sha256 is the digest of the policy evidence file content; derived
# here (never invented, never self-referential).
policy_sha = _sha256_bytes(_canonical_json(dict(request.policy)))
# Secret scan must cover the exact artifact bytes.
secret_scan_artifact = _validate_sha256(
request.secret_scan.get("artifact_sha256"), "secret_scan.artifact_sha256"
)
if secret_scan_artifact != artifact_sha:
raise EvidenceProduceError("SECRET_SCAN_ARTIFACT_DIGEST_MISMATCH")
# Every governed evidence type must be bound to a trusted-provider receipt.
# A bare caller-supplied {"passed": true} (no receipt) fails closed here;
# the producer never accepts an invented pass flag.
for etype in TRUSTED_PROVIDER_EVIDENCE_TYPES:
_require_trusted_provider_receipt(
etype, getattr(request, etype), source_commit, tree_oid
)
if request.secret_scan.get("passed") is not True:
raise EvidenceProduceError("SECRET_SCAN_NOT_PASSED")
if request.tests.get("passed") is not True:
raise EvidenceProduceError("TESTS_NOT_PASSED")
if request.canary.get("passed") is not True:
raise EvidenceProduceError("CANARY_NOT_PASSED")
if request.rollback.get("ready") is not True:
raise EvidenceProduceError("ROLLBACK_NOT_READY")
if (
request.gitea.get("consistent") is not True
or request.gitea.get("repository") != config.repository
or request.gitea.get("source_commit") != source_commit
or request.gitea.get("remote_ref_oid") != source_commit
or request.gitea.get("tree_oid") != tree_oid
):
raise EvidenceProduceError("GITEA_INCONSISTENT")
digests = {
"artifact": artifact_sha,
"manifest": manifest_sha,
"package_closure": closure_sha,
"policy": policy_sha,
"tests": _sha256_bytes(_canonical_json(dict(request.tests))),
"canary": _sha256_bytes(_canonical_json(dict(request.canary))),
"gitea": _sha256_bytes(_canonical_json(dict(request.gitea))),
"rollback": _sha256_bytes(_canonical_json(dict(request.rollback))),
"secret_scan": _sha256_bytes(_canonical_json(dict(request.secret_scan))),
}
return digests
def _build_receipt(
config: TrustedCandidateBuilderConfig,
request: GovernedBuildArtifacts,
) -> Mapping[str, Any]:
"""Assemble the self-consistent receipt from verified artifacts."""
from silent_release_controller import ReleaseCandidate, ReleaseState
artifact_sha = _sha256_bytes(request.artifact)
manifest_sha = _sha256_bytes(_canonical_json(dict(request.manifest)))
closure_sha = _validate_sha256(
request.manifest.get("package_closure_sha256"), "package_closure_sha256"
)
policy_sha = _sha256_bytes(_canonical_json(dict(request.policy)))
candidate_kwargs: dict[str, Any] = {}
issued_at = str(request.issued_at or "").strip()
if issued_at:
# The evidence was produced at the build execution instant; bind it so the
# same verified artifacts reproduce an identical, idempotent closure.
candidate_kwargs["created_at"] = issued_at
candidate_kwargs["updated_at"] = issued_at
candidate = ReleaseCandidate(
candidate_id=request.candidate_id,
source_commit=request.source_commit,
tree_oid=request.tree_oid,
artifact_sha256=artifact_sha,
manifest_sha256=manifest_sha,
tests=dict(request.tests),
canary=dict(request.canary),
rollback_ready=request.rollback.get("ready") is True,
git_consistency={
"consistent": True,
"repository": config.repository,
"source_commit": request.source_commit,
"tree_oid": request.tree_oid,
},
gitea_consistency=dict(request.gitea),
state=ReleaseState.READY_FOR_APPROVAL,
**candidate_kwargs,
)
deployment_payload = dict(request.deployment)
deployment_payload.update(
{
"service": request.deployment["service"],
"source_commit": request.source_commit,
"tree_oid": request.tree_oid,
"artifact_sha256": artifact_sha,
"manifest_sha256": manifest_sha,
"package_closure_sha256": closure_sha,
"policy_sha256": policy_sha,
"rollback_sha256": _validate_sha256(
request.rollback_sha256, "rollback_sha256"
)
or request.rollback.get("rollback_sha256", ""),
"repository_identity": config.repository,
"authoritative_git_dir": request.deployment["authoritative_git_dir"],
}
)
if not deployment_payload["rollback_sha256"]:
raise EvidenceProduceError("ROLLBACK_SHA256_REQUIRED")
from silent_release_hardening import (
DeploymentPlan,
FinalizationPlan,
ReleasePlan,
TagPlan,
TrustedCandidateBundle,
)
release_plan = ReleasePlan(
deployment=DeploymentPlan.from_dict(deployment_payload),
tag=TagPlan.from_dict(dict(request.tag)),
finalization=FinalizationPlan.from_dict(dict(request.finalization)),
policy_sha256=policy_sha,
)
checks = {name: True for name in config.required_checks}
TrustedCandidateBundle(candidate, release_plan, checks).validate()
return {
"schema": EVIDENCE_RECEIPT_SCHEMA,
"version": request.version,
"candidate": candidate.to_dict(),
"release_plan": release_plan.to_dict(),
}
def _safe_target(source_root: Path, version: str) -> Path:
relative = Path(version)
if relative.is_absolute() or any(
part in {"", ".", ".."} for part in relative.parts
):
raise EvidenceProduceError("CLOSURE_PATH_INVALID")
candidate = Path(source_root) / version
try:
metadata = Path(source_root).stat(follow_symlinks=False)
except OSError as exc:
raise EvidenceProduceError("SOURCE_ROOT_UNAVAILABLE") from exc
if not stat.S_ISDIR(metadata.st_mode) or Path(source_root).is_symlink():
raise EvidenceProduceError("SOURCE_ROOT_INVALID")
return candidate
def _write_durable_json(path: Path, payload: Mapping[str, Any], mode: int = 0o600) -> str:
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
# The deterministic collector compares file digests against the canonical
# payload hash (no trailing newline); write the exact canonical bytes so the
# producer's closure re-validates under TrustedBuilderEvidenceMaterializer.
raw = _canonical_json(payload)
descriptor = os.open(
path,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
| getattr(os, "O_BINARY", 0),
mode,
)
try:
os.write(descriptor, raw)
os.fsync(descriptor)
finally:
os.close(descriptor)
os.chmod(path, mode)
return hashlib.sha256(raw).hexdigest()
def _write_durable_bytes(path: Path, value: bytes, mode: int = 0o600) -> str:
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
descriptor = os.open(
path,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
| getattr(os, "O_BINARY", 0),
mode,
)
try:
os.write(descriptor, value)
os.fsync(descriptor)
finally:
os.close(descriptor)
os.chmod(path, mode)
return hashlib.sha256(value).hexdigest()
def produce_evidence_closure(
*,
builder_policy: Path,
source_root: Path,
request: GovernedBuildArtifacts,
expected_owner_uid: int | None = 0,
) -> ProduceResult:
"""Produce a self-consistent, root-owned evidence closure, fail-closed and idempotent."""
try:
config = TrustedCandidateBuilderConfig.from_policy(
load_builder_policy(builder_policy)
)
except Exception as exc:
raise EvidenceProduceError(str(exc)) from exc
try:
version = validate_release_version(request.version)
except ValueError as exc:
raise EvidenceProduceError("VERSION_INVALID") from exc
digests = _verify_artifacts(config, request)
receipt = _build_receipt(config, request)
# Resolve layout paths from policy (aligned with evidence_layout; never invented).
target = _safe_target(source_root, version)
layout_paths = {
name: target / Path(template.format(version=version)).relative_to(version)
for name, template in config.evidence_layout.items()
}
if target.exists() or target.is_symlink():
# Idempotency: an existing identical closure (same receipt digest + same
# artifact bytes) is accepted; anything else is a conflict.
existing_receipt = target / "receipt.json"
if not existing_receipt.is_file():
raise EvidenceProduceError("CLOSURE_CONFLICT")
try:
existing = json.loads(existing_receipt.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise EvidenceProduceError("CLOSURE_CONFLICT") from exc
if _canonical_json(dict(existing)) != _canonical_json(dict(receipt)):
raise EvidenceProduceError("CLOSURE_CONFLICT")
for name, path in layout_paths.items():
if not (path.is_file() and _file_sha256(path) == digests[name]):
raise EvidenceProduceError("CLOSURE_CONFLICT")
return ProduceResult(
status="EVIDENCE_PRODUCED",
idempotent=True,
receipt_sha256=_sha256_bytes(_canonical_json(dict(receipt))),
closure_dir=str(target),
evidence_digests=dict(digests),
)
temporary = source_root / (
f".{version}.{os.getpid()}.{secrets.token_hex(8)}.prod.tmp"
)
if temporary.exists() or temporary.is_symlink():
raise EvidenceProduceError("PRODUCE_TEMP_CONFLICT")
temporary.mkdir(mode=0o700)
# The closure directory mirrors source_root/<version>/; every written file uses
# the policy-declared basename so the layout is aligned with evidence_layout.
try:
_write_durable_json(temporary / "receipt.json", receipt)
_write_durable_bytes(temporary / "artifact.tar.gz", request.artifact)
_write_durable_json(temporary / "manifest.json", request.manifest)
_write_durable_bytes(temporary / "checksums.sha256", request.package_closure)
_write_durable_json(temporary / "trusted-release-policy.json", request.policy)
_write_durable_json(temporary / "tests.json", request.tests)
_write_durable_json(temporary / "canary.json", request.canary)
_write_durable_json(temporary / "gitea.json", request.gitea)
_write_durable_json(temporary / "rollback.json", request.rollback)
_write_durable_json(temporary / "secret-scan.json", request.secret_scan)
os.replace(temporary, target)
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
return ProduceResult(
status="EVIDENCE_PRODUCED",
idempotent=False,
receipt_sha256=_sha256_bytes(_canonical_json(dict(receipt))),
closure_dir=str(target),
evidence_digests=dict(digests),
)
def producer_operation_definition() -> OperationDefinition:
"""Register the governed.release.evidence.produce operation (lookup-only registry).
This is the *legacy closure assembler* entrypoint. V2R2C2 Round-3 keeps it
registered for backward compatibility with the existing runtime chain; the
governed production path now resolves
``governed_authenticated_evidence_operation.produce_authenticated_evidence_closure``
(see ``authenticated_producer_operation_definition``).
"""
return OperationDefinition.from_dict(
{
"operation_id": "governed.release.evidence.produce",
"version": 1,
"effect_class": "RECONCILABLE",
"handler_ref": "governed_release_evidence_producer.produce_evidence_closure",
"input_schema": PRODUCE_SCHEMA,
"required_permissions": ["governed.release.evidence.produce"],
"timeout_seconds": 60.0,
"readback": {"mode": "STATE", "required": True},
"retry": {"mode": "NEVER"},
"reconcile": {"mode": "EXACT_READBACK"},
"rollback": {"mode": "COMPENSATE"},
"owner_boundary": {"mode": "WITHIN_GRANT"},
}
)
AUTHENTICATED_EVIDENCE_HANDLER_REF = (
"governed_authenticated_evidence_operation.produce_authenticated_evidence_closure"
)
def authenticated_producer_operation_definition() -> OperationDefinition:
"""Register the governed, provider-authenticated release evidence operation.
V2R2C2 Round-3 production entrypoint. Its ``handler_ref`` points at the
authenticated wrapper, which refuses to invoke the legacy closure assembler
until every final-release provider receipt has been resolved from an
authenticated store (Gate-W only authenticates tests/Gitea/secret-scan;
canary/rollback require Gate-R). See Round-3 Provider Authentication.
"""
return OperationDefinition.from_dict(
{
"operation_id": "governed.authenticated.release.evidence.produce",
"version": 1,
"effect_class": "RECONCILABLE",
"handler_ref": AUTHENTICATED_EVIDENCE_HANDLER_REF,
"input_schema": PRODUCE_SCHEMA,
"required_permissions": ["governed.release.evidence.produce"],
"timeout_seconds": 60.0,
"readback": {"mode": "STATE", "required": True},
"retry": {"mode": "NEVER"},
"reconcile": {"mode": "EXACT_READBACK"},
"rollback": {"mode": "COMPENSATE"},
"owner_boundary": {"mode": "WITHIN_GRANT"},
}
)
def producer_registry() -> OperationRegistry:
"""Union of the legacy assembler and the authenticated production operation."""
return OperationRegistry(
[producer_operation_definition(), authenticated_producer_operation_definition()]
)
def _main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("operation", choices=("produce",))
parser.add_argument("--version", required=True)
parser.add_argument("--builder-policy", required=True, type=Path)
parser.add_argument("--source-root", required=True, type=Path)
parser.add_argument("--artifacts-json", required=True, type=Path)
arguments = parser.parse_args()
try:
payload = json.loads(arguments.artifacts_json.read_text(encoding="utf-8"))
request = GovernedBuildArtifacts(
version=arguments.version,
candidate_id=payload["candidate_id"],
source_commit=payload["source_commit"],
tree_oid=payload["tree_oid"],
artifact=bytes.fromhex(payload["artifact_hex"]),
manifest=payload["manifest"],
package_closure=bytes.fromhex(payload["package_closure_hex"]),
policy=payload["policy"],
tests=payload["tests"],
canary=payload["canary"],
gitea=payload["gitea"],
rollback=payload["rollback"],
secret_scan=payload["secret_scan"],
deployment=payload["deployment"],
tag=payload["tag"],
finalization=payload["finalization"],
rollback_sha256=payload.get("rollback_sha256", ""),
)
result = produce_evidence_closure(
builder_policy=arguments.builder_policy,
source_root=arguments.source_root,
request=request,
)
except (EvidenceProduceError, KeyError, ValueError, OSError, json.JSONDecodeError) as exc:
raise SystemExit(str(exc)) from exc
from dataclasses import asdict
print(json.dumps(asdict(result), sort_keys=True, separators=(",", ":")))
if __name__ == "__main__":
_main()
@@ -1,405 +0,0 @@
"""Narrow C5 unattended source-publication policy lifecycle.
The policy is an Owner-signed, exact-scope admission object. It is checked
against the configured Owner policy at issuance and against a live revocation
source for every request. It is not an activation/configuration mechanism
and does not create release or production authority.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import re
import time
from pathlib import PurePosixPath
from typing import Any, Callable, Mapping
from aios_single_host_authority import (
OWNER_NAMESPACE,
OWNER_IDENTITY,
SOURCE_PUBLICATION_CAPABILITIES,
SOURCE_PUBLICATION_POLICY_ID,
SOURCE_PUBLICATION_POLICY_SCHEMA,
SignedArtifact,
SingleHostAuthorityError,
SourcePublicationPolicyRequest,
canonical_json_bytes,
sha256_json,
)
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_RISK_ORDER = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
_POLICY_FIELDS = frozenset(
{
"schema", "policy_id", "owner_identity", "repository", "trust_domain",
"policy_digest",
"authority_base_oid", "source_oid", "source_tree_oid", "target_agent",
"target_branch", "generation", "execution_digest", "exact_files",
"capabilities", "risk", "production_impact", "renewal", "nonce",
"issued_at", "not_before", "expires_at", "status", "signature_namespace",
"signature_key_id", "canonical_payload_digest",
}
)
_REQUEST_FIELDS = frozenset(SourcePublicationPolicyRequest.__dataclass_fields__)
# The typed Owner epoch producer uses the configured policy's production_scope.
# The installed C5 source-only policy contract uses ``none`` for production
# impact; keep this exact value bound to the C5 policy rather than accepting an
# arbitrary non-empty scope from a raw signer.
SOURCE_PUBLICATION_EPOCH_PRODUCTION_SCOPE = "none"
SOURCE_PUBLICATION_EPOCH_DESTRUCTIVE_POLICY = "forbidden"
@dataclass(frozen=True, slots=True)
class TaskAdmissionPolicyProjection:
"""The only policy projection exposed after an exact request guard."""
max_risk: str
repository: str
target_branch: str
capabilities: tuple[str, ...]
exact_files: tuple[str, ...]
def to_dict(self) -> dict[str, Any]:
return asdict(self)
def _request_dict(request: SourcePublicationPolicyRequest | Mapping[str, Any]) -> dict[str, Any]:
if isinstance(request, SourcePublicationPolicyRequest):
value = asdict(request)
elif isinstance(request, Mapping):
value = dict(request)
else:
raise TypeError("request must be SourcePublicationPolicyRequest or mapping")
if set(value) != _REQUEST_FIELDS:
raise ValueError("SOURCE_PUBLICATION_REQUEST_FIELDS_INVALID")
if "exact_files" in value:
value["exact_files"] = tuple(value["exact_files"])
return value
class SourcePublicationPolicyVerifier:
"""Strict verifier over the existing Owner registry verifier interface."""
def __init__(
self,
owner_verifier: Any,
*,
revocation_check: Callable[[Mapping[str, Any]], bool] | None,
expected_policy_digest: str | None = None,
clock: Callable[[], float] = time.time,
) -> None:
self.owner_verifier = owner_verifier
self.revocation_check = revocation_check
self.expected_policy_digest = expected_policy_digest
self.clock = clock
def _deny(self, code: str) -> None:
raise SingleHostAuthorityError(code)
def _validate_payload(self, artifact: SignedArtifact) -> dict[str, Any]:
if not isinstance(artifact, SignedArtifact):
self._deny("SOURCE_PUBLICATION_ARTIFACT_INVALID")
payload = dict(artifact.payload)
if set(payload) != _POLICY_FIELDS:
self._deny("SOURCE_PUBLICATION_UNKNOWN_FIELDS")
try:
if artifact.canonical_payload != canonical_json_bytes(payload):
self._deny("SOURCE_PUBLICATION_CANONICAL_BYTES_MISMATCH")
if payload["canonical_payload_digest"] != sha256_json(
{k: v for k, v in payload.items() if k != "canonical_payload_digest"}
):
self._deny("SOURCE_PUBLICATION_PAYLOAD_DIGEST_INVALID")
if payload["schema"] != SOURCE_PUBLICATION_POLICY_SCHEMA:
self._deny("SOURCE_PUBLICATION_SCHEMA_INVALID")
if payload["policy_id"] != SOURCE_PUBLICATION_POLICY_ID:
self._deny("SOURCE_PUBLICATION_POLICY_ID_INVALID")
if payload["owner_identity"] != OWNER_IDENTITY:
self._deny("SOURCE_PUBLICATION_OWNER_INVALID")
if not isinstance(payload["policy_digest"], str) or not _HEX64.fullmatch(payload["policy_digest"]):
self._deny("SOURCE_PUBLICATION_POLICY_DIGEST_INVALID")
if self.expected_policy_digest is not None and payload["policy_digest"] != self.expected_policy_digest:
self._deny("SOURCE_PUBLICATION_POLICY_DIGEST_MISMATCH")
if payload["signature_namespace"] != OWNER_NAMESPACE:
self._deny("SOURCE_PUBLICATION_NAMESPACE_INVALID")
if payload["signature_key_id"] != artifact.key_id or artifact.namespace != OWNER_NAMESPACE:
self._deny("SOURCE_PUBLICATION_ISSUER_MISMATCH")
for name in ("authority_base_oid", "source_oid", "source_tree_oid"):
if not isinstance(payload[name], str) or not _HEX40.fullmatch(payload[name]):
self._deny("SOURCE_PUBLICATION_OID_INVALID")
if not isinstance(payload["execution_digest"], str) or not _HEX64.fullmatch(payload["execution_digest"]):
self._deny("SOURCE_PUBLICATION_EXECUTION_DIGEST_INVALID")
branch = payload["target_branch"]
agent = payload["target_agent"]
if (
not isinstance(branch, str) or not branch.startswith("agent/")
or "\\" in branch or ".." in PurePosixPath(branch).parts
or branch.endswith(("/", "."))
or not isinstance(agent, str) or not agent.startswith("agent/")
):
self._deny("SOURCE_PUBLICATION_TARGET_INVALID")
if payload["risk"] != "engineering" or payload["production_impact"] != "none" or payload["renewal"] != "none":
self._deny("SOURCE_PUBLICATION_SCOPE_INVALID")
if payload["capabilities"] != sorted(SOURCE_PUBLICATION_CAPABILITIES):
self._deny("SOURCE_PUBLICATION_CAPABILITIES_INVALID")
if not isinstance(payload["exact_files"], list) or not payload["exact_files"] or len(set(payload["exact_files"])) != len(payload["exact_files"]):
self._deny("SOURCE_PUBLICATION_FILES_INVALID")
if type(payload["generation"]) is not int or payload["generation"] < 1:
self._deny("SOURCE_PUBLICATION_GENERATION_INVALID")
if type(payload["issued_at"]) is not int or type(payload["not_before"]) is not int or type(payload["expires_at"]) is not int:
self._deny("SOURCE_PUBLICATION_TIME_INVALID")
now = int(self.clock())
if not payload["not_before"] <= payload["issued_at"] <= now < payload["expires_at"]:
self._deny("SOURCE_PUBLICATION_EXPIRED")
if payload["expires_at"] <= payload["issued_at"]:
self._deny("SOURCE_PUBLICATION_LIFETIME_INVALID")
if payload["expires_at"] - payload["issued_at"] > 3600 or payload["status"] != "ACTIVE":
self._deny("SOURCE_PUBLICATION_LIFETIME_INVALID")
if self.revocation_check is None:
self._deny("SOURCE_PUBLICATION_REVOCATION_CHECK_MISSING")
if self.revocation_check(payload) is not True:
self._deny("SOURCE_PUBLICATION_REVOKED")
except SingleHostAuthorityError:
raise
except Exception as exc:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_INVALID") from exc
return payload
def verify(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any] | None = None,
) -> bool:
try:
payload = self._validate_payload(artifact)
# Verify exactly the bytes supplied by the signed artifact. Do not
# reconstruct or reserialize them before invoking the registry API.
if self.owner_verifier.verify(
namespace=artifact.namespace,
payload=artifact.canonical_payload,
signature=artifact.signature,
key_id=artifact.key_id,
) is not True:
return False
if request is not None:
expected = _request_dict(request)
for field in (
"policy_id", "repository", "authority_base_oid", "source_oid",
"source_tree_oid", "target_agent", "target_branch", "generation",
"execution_digest", "exact_files", "nonce", "risk",
"production_impact", "renewal",
):
actual = tuple(payload[field]) if field == "exact_files" else payload[field]
if actual != expected.get(field):
return False
if int(payload["expires_at"]) - int(payload["issued_at"]) != int(expected["ttl_seconds"]):
return False
return True
except (SingleHostAuthorityError, TypeError, ValueError, KeyError):
return False
except Exception:
return False
def require(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> dict[str, Any]:
if not self.verify(artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
return dict(artifact.payload)
def require_source_publication_epoch_scope(epoch: Any, policy_payload: Mapping[str, Any]) -> None:
"""Require v2 epoch provenance to match the signed C5 policy exactly."""
schema = getattr(epoch, "schema", None)
if schema is None and isinstance(epoch, Mapping):
schema = epoch.get("schema")
if schema != "aios.owner-delegation-epoch.v2":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_EPOCH_SCHEMA_REQUIRED")
def field(name: str) -> Any:
if isinstance(epoch, Mapping):
return epoch.get(name)
return getattr(epoch, name, None)
if (
field("production_scope") != SOURCE_PUBLICATION_EPOCH_PRODUCTION_SCOPE
or field("destructive_policy") != SOURCE_PUBLICATION_EPOCH_DESTRUCTIVE_POLICY
or field("policy_digest") != policy_payload.get("policy_digest")
or field("repository") != policy_payload.get("repository")
or field("trust_domain") != policy_payload.get("trust_domain")
):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_EPOCH_SCOPE_MISMATCH")
class ScopedSourcePublicationPolicyAdmission:
"""Rechecks a policy for every exact request before scoped admission."""
def __init__(
self,
verifier: SourcePublicationPolicyVerifier,
*,
bootstrap: Any | None = None,
artifact: SignedArtifact | None = None,
request: SourcePublicationPolicyRequest | Mapping[str, Any] | None = None,
expected_agent_id: str | None = None,
) -> None:
self.verifier = verifier
self.bootstrap = bootstrap
self.artifact = artifact
self.request = request
self.expected_agent_id = expected_agent_id
def _check_bootstrap_binding(self, payload: Mapping[str, Any]) -> None:
if self.bootstrap is None:
return
policy = getattr(self.bootstrap, "policy", None)
digest_provider = getattr(policy, "normalized_policy_digest", None)
digest = digest_provider() if callable(digest_provider) else None
trust_domain = getattr(policy, "trust_domain", None)
if not isinstance(digest, str) or digest != payload.get("policy_digest"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_POLICY_DIGEST_MISMATCH")
if not isinstance(trust_domain, str) or trust_domain != payload.get("trust_domain"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_TRUST_DOMAIN_MISMATCH")
if self.expected_agent_id is None or payload.get("target_agent") != self.expected_agent_id:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_TARGET_AGENT_MISMATCH")
def authorize(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> bool:
try:
payload = self.verifier._validate_payload(artifact)
if self.expected_agent_id is not None and payload.get("target_agent") != self.expected_agent_id:
return False
self._check_bootstrap_binding(payload)
except SingleHostAuthorityError:
return False
return self.verifier.verify(artifact, request)
verify = authorize
def _request_for_raw(self, raw: Mapping[str, Any]) -> SourcePublicationPolicyRequest:
if self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_ARTIFACT_REQUIRED")
expected_task = getattr(self, "expected_task_id", None)
if expected_task is not None and raw.get("task_id") != expected_task:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_PLAN_TASK_MISMATCH")
payload = self.verifier._validate_payload(self.artifact)
self._check_bootstrap_binding(payload)
if self.request is not None:
base = _request_dict(self.request)
else:
base = {
"policy_id": payload["policy_id"], "repository": payload["repository"],
"authority_base_oid": payload["authority_base_oid"], "source_oid": payload["source_oid"],
"source_tree_oid": payload["source_tree_oid"], "target_agent": payload["target_agent"],
"target_branch": payload["target_branch"], "generation": payload["generation"],
"execution_digest": payload["execution_digest"], "exact_files": tuple(payload["exact_files"]),
"nonce": payload["nonce"], "ttl_seconds": payload["expires_at"] - payload["issued_at"],
"risk": payload["risk"], "production_impact": payload["production_impact"], "renewal": payload["renewal"],
}
checks = {
"repository": base["repository"], "authority_base_oid": base["authority_base_oid"],
"target_branch": base["target_branch"], "generation": base["generation"],
"execution_digest": base["execution_digest"], "risk": base["risk"],
}
for field, expected in checks.items():
if raw.get(field) != expected:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_REQUEST_SCOPE_MISMATCH")
caps = tuple(raw.get("requested_capabilities", ()))
if set(caps) != set(base["capabilities"] if "capabilities" in base else payload["capabilities"]):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_CAPABILITY_SCOPE_MISMATCH")
if tuple(raw.get("file_allowlist", ())) != tuple(base["exact_files"]):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_FILE_SCOPE_MISMATCH")
return SourcePublicationPolicyRequest(**base)
def create_envelope(self, raw: Mapping[str, Any]) -> dict[str, Any]:
if self.bootstrap is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
request = self._request_for_raw(raw)
if not self.verifier.verify(self.artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
return self.bootstrap.create_envelope(raw)
def authorize_and_admit(self, raw: Mapping[str, Any]) -> dict[str, Any]:
if self.bootstrap is None or self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
if raw.get("authorization_kind") != "delegation_epoch":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_OWNER_SIGNATURE_BYPASS_DENIED")
envelope = self.bootstrap._load_envelope(str(raw.get("envelope_id") or ""))
envelope_raw = envelope.as_dict() if hasattr(envelope, "as_dict") else dict(envelope)
request = self._request_for_raw(envelope_raw)
if not self.verifier.verify(self.artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
epoch = self.bootstrap._verify_epoch(
epoch_id=str(raw.get("delegation_epoch_id") or ""),
envelope=envelope,
now=int(self.verifier.clock()),
)
require_source_publication_epoch_scope(
epoch, self.verifier._validate_payload(self.artifact)
)
return self.bootstrap.authorize_and_admit(raw)
def status(self, raw: Mapping[str, Any] | str) -> Any:
"""Read status only after the same policy and epoch guard as admission."""
if self.bootstrap is None or self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
if isinstance(raw, str):
raw = {"envelope_id": raw}
envelope = self.bootstrap._load_envelope(str(raw.get("envelope_id") or ""))
envelope_raw = envelope.as_dict() if hasattr(envelope, "as_dict") else dict(envelope)
request = self._request_for_raw(envelope_raw)
self.verifier.require(self.artifact, request)
epoch_id = str(raw.get("delegation_epoch_id") or "")
if not epoch_id:
grant = self.bootstrap.store.get_grant_by_task_id(envelope.task_id)
epoch_id = grant.parent_epoch_id if grant is not None else ""
if not epoch_id or not hasattr(self.bootstrap, "_verify_epoch"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_STATUS_EPOCH_REQUIRED")
epoch = self.bootstrap._verify_epoch(
epoch_id=epoch_id,
envelope=envelope,
now=int(self.verifier.clock()),
)
require_source_publication_epoch_scope(
epoch, self.verifier._validate_payload(self.artifact)
)
if hasattr(self.bootstrap, "status"):
return self.bootstrap.status(envelope.envelope_id)
return envelope_raw
def reconcile_status(self, envelope_id: str, authorization: Mapping[str, Any]) -> Any:
"""Carry the verified epoch identity when a Task effect has no receipt yet."""
if authorization.get("authorization_kind") != "delegation_epoch":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_OWNER_SIGNATURE_BYPASS_DENIED")
return self.status({"envelope_id": envelope_id,
"delegation_epoch_id": authorization.get("delegation_epoch_id")})
def project_task_admission_policy(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> TaskAdmissionPolicyProjection:
payload = self.verifier.require(artifact, request)
return TaskAdmissionPolicyProjection(
max_risk="engineering",
repository=payload["repository"],
target_branch=payload["target_branch"],
capabilities=tuple(payload["capabilities"]),
exact_files=tuple(payload["exact_files"]),
)
to_task_admission_policy = project_task_admission_policy
# Short aliases for callers that prefer the lifecycle terminology.
SourcePublicationPolicyAdmission = ScopedSourcePublicationPolicyAdmission
SourcePublicationVerifier = SourcePublicationPolicyVerifier
@@ -1,372 +0,0 @@
"""Thin unattended release runner.
This module is an adapter around the existing admission and silent-release
services. It deliberately owns no release state machine and never creates
approval evidence. The runner only binds immutable source facts, delegates
authority, and validates durable readbacks before moving to the next phase.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import re
import time
from typing import Any, Callable, Mapping
OID_RE = re.compile(r"^[0-9a-fA-F]{40}$")
SHA256_RE = re.compile(r"^[0-9a-fA-F]{64}$")
IDENT_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$")
RETRYABLE_CODES = {408, 425, 429, 500, 502, 503, 504}
RETRYABLE_NAMES = {
"UNAVAILABLE", "BUSY", "TEMPORARY_FAILURE", "TASK_ADMISSION_IN_PROGRESS",
}
RISKS = {"low", "medium", "engineering", "high"}
class UnattendedReleaseError(RuntimeError):
"""A fail-closed runner error with a stable code."""
def __init__(self, code: str, message: str | None = None):
self.code = code
super().__init__(message or code)
def _canon(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
def _digest(value: Any) -> str:
return hashlib.sha256(_canon(value)).hexdigest()
def _require_oid(name: str, value: Any) -> str:
value = str(value or "")
if not OID_RE.fullmatch(value):
raise UnattendedReleaseError("INVALID_" + name.upper())
return value.lower()
def _require_sha(name: str, value: Any) -> str:
value = str(value or "")
if not SHA256_RE.fullmatch(value):
raise UnattendedReleaseError("INVALID_" + name.upper())
return value.lower()
@dataclass(frozen=True)
class SourcePublicationIntent:
"""Immutable, deterministic source and execution binding."""
repository: str
source_commit: str
source_tree_oid: str
authority_base_oid: str
target_branch: str
file_allowlist: tuple[str, ...]
requested_capabilities: tuple[str, ...]
risk: str
execution_digest: str
generation: int = 1
event_id: str = "release"
version: str = ""
admission_task_id: str = ""
admission_idempotency_key: str = ""
def __post_init__(self) -> None:
if not self.repository or not isinstance(self.repository, str):
raise UnattendedReleaseError("INVALID_REPOSITORY")
for name in ("source_commit", "source_tree_oid", "authority_base_oid"):
object.__setattr__(self, name, _require_oid(name, getattr(self, name)))
if not isinstance(self.target_branch, str) or not self.target_branch or not self.target_branch.startswith("agent/") or self.target_branch.startswith("-"):
raise UnattendedReleaseError("INVALID_TARGET_BRANCH")
if ".." in self.target_branch or "//" in self.target_branch or "@{" in self.target_branch:
raise UnattendedReleaseError("INVALID_TARGET_BRANCH")
if not isinstance(self.generation, int) or isinstance(self.generation, bool) or self.generation < 1:
raise UnattendedReleaseError("INVALID_GENERATION")
object.__setattr__(self, "execution_digest", _require_sha("execution_digest", self.execution_digest))
if not self.file_allowlist or any(not isinstance(p, str) or not p or p.startswith(("/", "\\")) or ".." in p or "\\" in p or p == ".git" or p.startswith(".git/") for p in self.file_allowlist):
raise UnattendedReleaseError("INVALID_FILE_ALLOWLIST")
if len(set(self.file_allowlist)) != len(self.file_allowlist):
raise UnattendedReleaseError("DUPLICATE_FILE_ALLOWLIST")
if not self.requested_capabilities or any(not isinstance(c, str) or not c or "*" in c for c in self.requested_capabilities):
raise UnattendedReleaseError("INVALID_CAPABILITIES")
if not isinstance(self.risk, str) or self.risk.lower() not in RISKS:
raise UnattendedReleaseError("INVALID_RISK")
object.__setattr__(self, "risk", self.risk.lower())
object.__setattr__(self, "file_allowlist", tuple(sorted(self.file_allowlist)))
object.__setattr__(self, "requested_capabilities", tuple(sorted(set(self.requested_capabilities))))
if not IDENT_RE.fullmatch(self.event_id):
raise UnattendedReleaseError("INVALID_EVENT_ID")
if self.version and not IDENT_RE.fullmatch(self.version):
raise UnattendedReleaseError("INVALID_VERSION")
if bool(self.admission_task_id) != bool(self.admission_idempotency_key):
raise UnattendedReleaseError("INCOMPLETE_ADMISSION_IDENTITY")
for value in (self.admission_task_id, self.admission_idempotency_key):
if value and (not isinstance(value, str) or not IDENT_RE.fullmatch(value)):
raise UnattendedReleaseError("INVALID_ADMISSION_IDENTITY")
def as_dict(self) -> dict[str, Any]:
value = {
"repository": self.repository, "source_commit": self.source_commit.lower(),
"source_tree_oid": self.source_tree_oid.lower(), "authority_base_oid": self.authority_base_oid.lower(),
"target_branch": self.target_branch, "file_allowlist": list(self.file_allowlist),
"requested_capabilities": list(self.requested_capabilities), "risk": self.risk,
"execution_digest": self.execution_digest.lower(), "generation": self.generation,
"event_id": self.event_id, "version": self.version,
}
if self.admission_task_id:
value.update(admission_task_id=self.admission_task_id,
admission_idempotency_key=self.admission_idempotency_key)
return value
@property
def task_id(self) -> str:
return self.admission_task_id or "release-task-" + _digest(self.as_dict())[:32]
@property
def idempotency_key(self) -> str:
return self.admission_idempotency_key or "release-intake-" + _digest({"task_id": self.task_id, "execution_digest": self.execution_digest.lower()})[:32]
def admission_request(self) -> dict[str, Any]:
"""Return the sole normalized request accepted by Task admission."""
return {"idempotency_key": self.idempotency_key, "task_id": self.task_id,
"repository": self.repository, "authority_base_oid": self.authority_base_oid,
"target_branch": self.target_branch, "generation": self.generation,
"execution_digest": self.execution_digest, "requested_capabilities": list(self.requested_capabilities),
"file_allowlist": list(self.file_allowlist), "risk": self.risk}
def _error_code(exc: BaseException) -> str:
code = getattr(exc, "code", None) or getattr(exc, "status", None) or exc.__class__.__name__
value = str(code).upper()
return value if re.fullmatch(r"[A-Z0-9_]{1,100}", value) else "BACKEND_ERROR"
def _bound_receipt(receipt: Any, intent: SourcePublicationIntent) -> bool:
if not isinstance(receipt, Mapping):
return False
expected = {"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest}
return (receipt.get("schema") == "aios.task-admission-receipt.v1"
and all(_canon(receipt.get(k)) == _canon(v) for k, v in expected.items())
and receipt.get("task_state") in {"QUEUED", "CLAIMED", "RUNNING"}
and all(isinstance(receipt.get(k), str) and receipt[k] for k in ("envelope_id", "grant_id")))
def _retryable(exc: BaseException) -> bool:
status = getattr(exc, "status_code", None) or getattr(exc, "http_status", None) or getattr(exc, "status", None)
if isinstance(status, int):
return status in RETRYABLE_CODES
code = _error_code(exc)
return code in RETRYABLE_NAMES
def _unknown_outcome(exc: BaseException) -> bool:
return isinstance(exc, (TimeoutError, ConnectionError, OSError)) or _error_code(exc) in {"TASK_ADMISSION_TASK_STATE_UNKNOWN", "TIMEOUT", "TIMED_OUT", "CONNECTION_LOST"}
class GovernedUnattendedReleaseRunner:
"""Phase-separated runner; preparation has no release/deployment rights."""
def __init__(self, *, admission: Any, release_service: Any = None,
publication_adapter: Any = None, owner_approval_broker: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None = None,
deployment_enabled: bool = False, deployment_service: Any = None,
max_attempts: int = 3, deadline_seconds: float = 30.0,
environment_mode: str = "SIMULATION",
clock: Callable[[], float] = time.monotonic, sleep: Callable[[float], None] = time.sleep):
if admission is None:
raise ValueError("admission is required")
if max_attempts < 1 or max_attempts > 10 or deadline_seconds <= 0:
raise ValueError("invalid retry bounds")
self.admission = admission
self.environment_mode = str(environment_mode).upper()
if self.environment_mode not in {"SIMULATION", "AUTHORITY"}:
raise ValueError("environment_mode must be SIMULATION or AUTHORITY")
self.release_service = release_service
self.publication_adapter = publication_adapter
self.owner_approval_broker = owner_approval_broker
self.deployment_enabled = bool(deployment_enabled)
self.deployment_service = deployment_service
self.max_attempts = max_attempts
self.deadline_seconds = deadline_seconds
self.clock, self.sleep = clock, sleep
def intake(self, intent: SourcePublicationIntent) -> dict[str, Any]:
if not isinstance(intent, SourcePublicationIntent):
raise UnattendedReleaseError("INTENT_TYPE_REQUIRED")
return {"phase": "PREPARATION", "mode": self.environment_mode, "task_id": intent.task_id,
"idempotency_key": intent.idempotency_key, "intent": intent.as_dict(),
"release_rights": False, "deployment_rights": False}
def admit(self, intent: SourcePublicationIntent, authorization: Mapping[str, Any]) -> dict[str, Any]:
if not isinstance(authorization, Mapping):
raise UnattendedReleaseError("AUTHORIZATION_REQUIRED")
if self.environment_mode == "SIMULATION" and getattr(self.admission, "simulation_only", False) is not True:
return {"phase": "BLOCKED", "mode": self.environment_mode, "task_id": intent.task_id, "error": "AUTHORITY_MODE_REQUIRED"}
raw = intent.admission_request()
deadline = self.clock() + self.deadline_seconds
envelope_id = None
for attempt in range(1, self.max_attempts + 1):
if self.clock() >= deadline:
return {"phase": "RECONCILIATION_REQUIRED" if envelope_id else "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": "ADMISSION_DEADLINE_EXCEEDED"}
try:
created = self.admission.create_envelope(raw)
envelope = created.get("envelope", created) if isinstance(created, Mapping) else {}
envelope_id = envelope.get("envelope_id") or envelope_id
if not isinstance(envelope_id, str) or not envelope_id:
raise UnattendedReleaseError("ENVELOPE_BINDING_MISSING")
auth = dict(authorization)
auth["envelope_id"] = envelope_id
if self.clock() >= deadline:
return self._reconcile(envelope_id, intent, "ADMISSION_DEADLINE_EXCEEDED", authorization)
receipt = self.admission.authorize_and_admit(auth)
if not _bound_receipt(receipt, intent) or receipt["envelope_id"] != envelope_id:
raise UnattendedReleaseError("ADMISSION_RECEIPT_BINDING_MISMATCH")
if self.clock() >= deadline:
return self._reconcile(envelope_id, intent, "ADMISSION_DEADLINE_EXCEEDED", authorization)
return {"phase": "ADMITTED", "mode": self.environment_mode, "attempt": attempt,
"envelope": created, "receipt": receipt}
except Exception as exc:
status_code = getattr(exc, "status_code", None) or getattr(exc, "http_status", None) or getattr(exc, "status", None)
if status_code == 403 or _error_code(exc) in {"403", "FORBIDDEN"}:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": _error_code(exc)}
# Once authorization may have run, every failure reconciles.
# A 5xx is never sufficient proof of NOT_APPLIED.
if envelope_id:
return self._reconcile(envelope_id, intent, _error_code(exc), authorization)
if not _retryable(exc) or self.clock() >= deadline or attempt >= self.max_attempts:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": _error_code(exc)}
self.sleep(min(0.05 * (2 ** (attempt - 1)), max(0.0, deadline - self.clock())))
raise UnattendedReleaseError("ADMISSION_RETRY_EXHAUSTED")
def _reconcile(self, envelope_id: str | None, intent: SourcePublicationIntent, error: str,
authorization: Mapping[str, Any]) -> dict[str, Any]:
if not envelope_id:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": error}
try:
reconcile = getattr(self.admission, "reconcile_status", None)
status = reconcile(envelope_id, authorization) if callable(reconcile) else self.admission.status(envelope_id)
except Exception as exc:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": error, "status_error": _error_code(exc)}
state = str(status.get("status") or status.get("task_state") or "").upper() if isinstance(status, Mapping) else ""
if state in {"NOT_APPLIED", "ABSENT", "NOT_FOUND"}:
return {"phase": "RETRYABLE_RECONCILIATION", "mode": "AUTHORITY", "task_id": intent.task_id, "status": status, "error": error}
if state == "ADMITTED" and _bound_receipt(status.get("task"), intent):
return {"phase": "RECONCILED", "mode": self.environment_mode, "task_id": intent.task_id, "status": status, "receipt": status["task"]}
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "status": status, "error": error}
def publish(self, intent: SourcePublicationIntent, admission_receipt: Mapping[str, Any]) -> dict[str, Any]:
if self.environment_mode != "AUTHORITY":
return {"phase": "BLOCKED", "mode": self.environment_mode, "error": "AUTHORITY_MODE_REQUIRED"}
if self.publication_adapter is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_BACKEND_MISSING"}
required = ("task_lease", "task_principal", "writer_lease", "commit_receipt", "source_oid", "tree_oid", "authenticated")
try:
if not _bound_receipt(admission_receipt, intent):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "ADMISSION_RECEIPT_BINDING_MISMATCH"}
result = self.publication_adapter.publish(intent, admission_receipt)
if not isinstance(result, Mapping) or result.get("authenticated") is not True or any(not result.get(k) for k in required[:-1]):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_LEASE_CHAIN_MISSING"}
if any(not isinstance(result[k], Mapping) or result[k].get("task_id") != intent.task_id
for k in ("task_lease", "task_principal", "writer_lease")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_LEASE_BINDING_MISMATCH"}
commit = result["commit_receipt"]
# Existing continuation receipts carry remote OIDs, while the
# authenticated task/job readback carries scope. Preserve both
# originals; never fabricate missing fields inside a receipt.
optional_bindings = {"task_id": intent.task_id, "tree_oid": result["tree_oid"],
"execution_digest": intent.execution_digest,
"target_branch": intent.target_branch,
"base_oid": intent.authority_base_oid}
if (not isinstance(commit, Mapping) or commit.get("commit_oid") != result["source_oid"]
or any(key in commit and commit[key] != value for key, value in optional_bindings.items())
or result.get("generation") != intent.generation):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_COMMIT_BINDING_MISMATCH"}
if not OID_RE.fullmatch(str(result["source_oid"])) or str(result["tree_oid"]).lower() != intent.source_tree_oid.lower() or any(str(result.get(k, "")) != str(v) for k, v in (("task_id", intent.task_id), ("authority_base_oid", intent.authority_base_oid), ("target_branch", intent.target_branch), ("execution_digest", intent.execution_digest))):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_RC_BINDING_MISMATCH"}
return {"phase": "PUBLISHED", "mode": "AUTHORITY", "readback": dict(result)}
except Exception as exc:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "error": _error_code(exc)}
def postpublication_rc(self, *, event_id: str, source_commit: str, version: str = "", source_readback: Mapping[str, Any] | None = None) -> dict[str, Any]:
if self.environment_mode != "AUTHORITY":
return {"phase": "BLOCKED", "mode": self.environment_mode, "error": "AUTHORITY_MODE_REQUIRED"}
if not isinstance(source_readback, Mapping) or source_readback.get("authenticated") is not True or str(source_readback.get("source_commit", "")).lower() != str(source_commit).lower():
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "SOURCE_READBACK_REQUIRED"}
if self.release_service is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "RELEASE_BACKEND_MISSING"}
try:
# Only a host-injected authenticated resolver can admit a source.
# The caller's readback object alone is never authorization.
resolver = getattr(self.release_service, "source_admission_resolver", None)
admitted_source = resolver(source_commit) if callable(resolver) else None
if (not isinstance(admitted_source, Mapping) or not admitted_source.get("admission_id")
or admitted_source.get("source_commit") != source_commit
or admitted_source.get("tree_oid") != source_readback.get("tree_oid")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "AUTHENTICATED_SOURCE_ADMISSION_REQUIRED"}
intent = self.release_service.create_release_intent({"event_id": event_id, "source_commit": source_commit, **({"version": version} if version else {})})
candidate_id = str(intent.get("candidate_id") or intent.get("candidate", {}).get("candidate_id") or "")
if not candidate_id:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "CANDIDATE_BINDING_MISSING"}
readers = ("get_release_candidate", "get_release_plan", "get_acceptance")
if any(not callable(getattr(self.release_service, name, None)) for name in readers):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "RC_READBACK_BACKEND_MISSING"}
candidate = self.release_service.get_release_candidate(candidate_id)
plan = self.release_service.get_release_plan(candidate_id)
acceptance = self.release_service.get_acceptance(candidate_id)
deployment = plan.get("deployment") if isinstance(plan, Mapping) else None
if (not isinstance(candidate, Mapping) or not isinstance(plan, Mapping) or not isinstance(deployment, Mapping) or not isinstance(acceptance, Mapping)
or str(candidate.get("candidate_id")) != candidate_id
or str(candidate.get("source_commit")) .lower() != str(source_commit).lower()
or str(plan.get("candidate_id", candidate_id)) != candidate_id
or str(deployment.get("source_commit")) .lower() != str(source_commit).lower()
or str(deployment.get("tree_oid", "")).lower() != str(source_readback.get("tree_oid", "")).lower()
or candidate.get("tree_oid") != deployment.get("tree_oid")
or candidate.get("artifact_sha256") != deployment.get("artifact_sha256")
or candidate.get("manifest_sha256") != deployment.get("manifest_sha256")
or str(acceptance.get("candidate_id")) != candidate_id
or acceptance.get("release_plan_sha256") != plan.get("sha256")
or str(acceptance.get("status")) != "ACCEPTED"):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "RC_READBACK_BINDING_MISMATCH"}
# Public readback is redacted. Reverify original signed bytes inside
# the existing service, not by reconstructing them from API output.
verify_acceptance = getattr(self.release_service, "verify_release_acceptance", None)
if not callable(verify_acceptance):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "BUILDER_ATTESTATION_REQUIRED"}
verified = verify_acceptance(candidate_id)
if (not isinstance(verified, Mapping) or verified.get("status") != "VERIFIED"
or verified.get("candidate_id") != candidate_id
or verified.get("release_plan_sha256") != plan.get("sha256")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "BUILDER_ATTESTATION_NOT_VERIFIED"}
envelope = self.release_service.get_approval_envelope(candidate_id)
if self.owner_approval_broker is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "OWNER_APPROVAL_BACKEND_MISSING"}
approval = self.owner_approval_broker(dict(envelope))
if not isinstance(approval, Mapping):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "OWNER_APPROVAL_MISSING"}
accepted = self.release_service.submit_owner_approval(dict(approval))
if not isinstance(accepted, Mapping) or accepted.get("status") != "VERIFIED_AND_STORED":
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "APPROVAL_NOT_VERIFIED"}
return {"phase": "APPROVED", "mode": "AUTHORITY", "candidate_id": candidate_id,
"readback": {"candidate": dict(candidate), "plan": dict(plan), "acceptance": dict(acceptance)},
"approval": accepted}
except Exception as exc:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": _error_code(exc)}
def deploy(self, candidate_id: str) -> dict[str, Any]:
if (self.environment_mode != "AUTHORITY" or not self.deployment_enabled or self.deployment_service is None
or getattr(self.deployment_service, "execution_enabled", False) is not True):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "DEPLOYMENT_NOT_ENABLED"}
try:
result = self.deployment_service.start_deployment(candidate_id)
saga = self.deployment_service.get_release_saga(candidate_id)
if (not isinstance(result, Mapping) or result.get("candidate_id") != candidate_id
or result.get("state") != "COMPLETED" or result.get("status") not in {"VERIFIED", "IDEMPOTENT"}
or not isinstance(saga, Mapping) or saga.get("state") != "COMPLETED"):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "DEPLOYMENT_NOT_VERIFIED", "result": dict(result) if isinstance(result, Mapping) else {}}
return {"phase": "DEPLOYED", "mode": "AUTHORITY", "result": dict(result)}
except Exception as exc:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": _error_code(exc)}
@@ -1,136 +0,0 @@
"""Local adapter for the root-owned registered Owner signer service.
The adapter intentionally has no public arbitrary-payload signing method.
``LocalOwnerAuthority`` calls the private typed seam after validating and
canonicalizing a structured Owner authorization object.
"""
from __future__ import annotations
import os
from pathlib import Path
import stat
import subprocess
import tempfile
from typing import Any, Callable
DEFAULT_REGISTERED_SIGNER_COMMAND = Path("/usr/local/libexec/aios-grant-signer-adapter")
OWNER_NAMESPACE = "aios-owner-approval-v1"
MAX_SIGNATURE_BYTES = 128 * 1024
MAX_KEY_ID_BYTES = 512
class RegisteredOwnerSignerError(RuntimeError):
pass
class RegisteredOwnerSignerAdapter:
"""Invoke the registered signer without reading or copying private keys."""
def __init__(
self,
command: Path | str = DEFAULT_REGISTERED_SIGNER_COMMAND,
*,
key_id: str,
owner_verifier: Any,
require_root: bool = True,
test_mode: bool = False,
runner: Callable[..., Any] = subprocess.run,
) -> None:
self.command = str(command)
self.key_id = key_id
self.owner_verifier = owner_verifier
self.require_root = require_root
self.test_mode = test_mode
self.runner = runner
if not isinstance(key_id, str) or not key_id:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_KEY_ID_REQUIRED")
if not test_mode:
self._validate_command()
@property
def fingerprint(self) -> str:
return self.key_id
def _validate_command(self) -> None:
path = Path(self.command)
if not path.is_absolute() or path.is_symlink() or not path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_INVALID")
st = path.stat()
if not hasattr(os, "geteuid") or st.st_uid != os.geteuid():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_OWNER_INVALID")
if stat.S_IMODE(st.st_mode) & 0o022:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_PERMISSIONS_INVALID")
def _read_output(self, path: Path, maximum: int) -> bytes:
if path.is_symlink() or not path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_INVALID")
st = path.stat()
if not self.test_mode and (not hasattr(os, "geteuid") or st.st_uid != os.geteuid()):
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_OWNER_INVALID")
if st.st_size > maximum:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_TOO_LARGE")
return path.read_bytes()
def _sign_canonical_payload(self, namespace: str, payload: bytes) -> str:
"""Private seam used only by typed ``LocalOwnerAuthority`` issuance."""
if namespace != OWNER_NAMESPACE:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_NAMESPACE_INVALID")
if not isinstance(payload, bytes) or not payload:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_PAYLOAD_INVALID")
if self.require_root:
if not hasattr(os, "geteuid") or os.geteuid() != 0:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_ROOT_REQUIRED")
if not self.test_mode:
self._validate_command()
with tempfile.TemporaryDirectory(prefix="aios-owner-sign-") as directory:
root = Path(directory)
input_path = root / "payload"
signature_path = root / "payload.sig"
key_id_path = root / "key-id"
input_path.write_bytes(payload)
try:
result = self.runner(
[
self.command,
"sign",
"--namespace",
namespace,
"--input",
str(input_path),
"--signature-out",
str(signature_path),
"--key-id-out",
str(key_id_path),
],
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=30,
)
except Exception as exc:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_FAILED") from exc
if getattr(result, "returncode", None) != 0:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_FAILED")
if not signature_path.is_file() or not key_id_path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_MISSING")
supplied_key_id = self._read_output(key_id_path, MAX_KEY_ID_BYTES).decode("utf-8").strip()
if supplied_key_id != self.key_id:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_KEY_ID_MISMATCH")
signature = self._read_output(signature_path, MAX_SIGNATURE_BYTES).decode("utf-8")
if not signature.startswith("-----BEGIN SSH SIGNATURE-----"):
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_SIGNATURE_INVALID")
try:
verified = self.owner_verifier.verify(
namespace=namespace,
payload=payload,
signature=signature,
key_id=self.key_id,
)
except Exception as exc:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_VERIFY_FAILED") from exc
if verified is not True:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_VERIFY_REJECTED")
return signature
@@ -1,359 +0,0 @@
#!/usr/bin/env python3
"""Governed, append-only reopen of one expired Owner approval cycle."""
from __future__ import annotations
import hashlib
import json
import os
import re
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Mapping
REQUEST_SCHEMA = "aios.owner-approval-reopen-request.v1"
EVENT_NAME = "owner_approval_reopen_authorized"
_IDEMPOTENCY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$")
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
class ApprovalReopenError(RuntimeError):
def __init__(self, code: str, detail: str | None = None):
self.code = code
self.detail = detail
super().__init__(code if not detail else f"{code}:{detail}")
@dataclass(frozen=True)
class ReopenRequest:
candidate_id: str
expected_approval_fingerprint: str
expected_source_commit: str
expected_tree_oid: str
expected_artifact_sha256: str
expected_manifest_sha256: str
expected_release_plan_sha256: str
expected_policy_sha256: str
reason: str
idempotency_key: str
@classmethod
def from_mapping(cls, raw: Mapping[str, Any]) -> "ReopenRequest":
if raw.get("schema") != REQUEST_SCHEMA:
raise ApprovalReopenError("APPROVAL_REOPEN_SCHEMA_INVALID")
allowed = {
"schema", "candidate_id", "expected_approval_fingerprint",
"expected_source_commit", "expected_tree_oid",
"expected_artifact_sha256", "expected_manifest_sha256",
"expected_release_plan_sha256", "expected_policy_sha256",
"reason", "idempotency_key",
}
if set(raw) != allowed:
raise ApprovalReopenError("APPROVAL_REOPEN_REQUEST_FIELDS_INVALID")
req = cls(
candidate_id=str(raw["candidate_id"]),
expected_approval_fingerprint=str(raw["expected_approval_fingerprint"]),
expected_source_commit=str(raw["expected_source_commit"]),
expected_tree_oid=str(raw["expected_tree_oid"]),
expected_artifact_sha256=str(raw["expected_artifact_sha256"]),
expected_manifest_sha256=str(raw["expected_manifest_sha256"]),
expected_release_plan_sha256=str(raw["expected_release_plan_sha256"]),
expected_policy_sha256=str(raw["expected_policy_sha256"]),
reason=str(raw["reason"]),
idempotency_key=str(raw["idempotency_key"]),
)
if not req.candidate_id or "/" in req.candidate_id or ".." in req.candidate_id:
raise ApprovalReopenError("APPROVAL_REOPEN_CANDIDATE_ID_INVALID")
if not _HEX64.fullmatch(req.expected_approval_fingerprint):
raise ApprovalReopenError("APPROVAL_REOPEN_FINGERPRINT_INVALID")
if not _HEX40.fullmatch(req.expected_source_commit):
raise ApprovalReopenError("APPROVAL_REOPEN_SOURCE_INVALID")
if not _HEX40.fullmatch(req.expected_tree_oid):
raise ApprovalReopenError("APPROVAL_REOPEN_TREE_INVALID")
for value, code in (
(req.expected_artifact_sha256, "APPROVAL_REOPEN_ARTIFACT_INVALID"),
(req.expected_manifest_sha256, "APPROVAL_REOPEN_MANIFEST_INVALID"),
(req.expected_release_plan_sha256, "APPROVAL_REOPEN_RELEASE_PLAN_INVALID"),
(req.expected_policy_sha256, "APPROVAL_REOPEN_POLICY_INVALID"),
):
if not _HEX64.fullmatch(value):
raise ApprovalReopenError(code)
if not (1 <= len(req.reason.strip()) <= 512):
raise ApprovalReopenError("APPROVAL_REOPEN_REASON_INVALID")
if not _IDEMPOTENCY_RE.fullmatch(req.idempotency_key):
raise ApprovalReopenError("APPROVAL_REOPEN_IDEMPOTENCY_KEY_INVALID")
return req
def _canon(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
def _sha(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _time(value: Any, code: str) -> datetime:
if not isinstance(value, str) or not value:
raise ApprovalReopenError(code)
try:
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise ApprovalReopenError(code) from exc
if dt.tzinfo is None:
raise ApprovalReopenError(code)
return dt.astimezone(timezone.utc)
def _secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise ApprovalReopenError("APPROVAL_REOPEN_STATE_FILE_INVALID")
st = path.stat()
if st.st_mode & 0o022:
raise ApprovalReopenError("APPROVAL_REOPEN_STATE_FILE_WRITABLE")
if st.st_uid not in {0, os.geteuid()}:
raise ApprovalReopenError("APPROVAL_REOPEN_STATE_FILE_OWNER_INVALID")
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ApprovalReopenError("APPROVAL_REOPEN_STATE_FILE_INVALID") from exc
if not isinstance(obj, dict):
raise ApprovalReopenError("APPROVAL_REOPEN_STATE_FILE_INVALID")
return obj
def _fsync_dir(path: Path) -> None:
fd = os.open(path, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(fd)
finally:
os.close(fd)
def _paths(root: Path, cid: str) -> dict[str, Path]:
return {
"candidate": root / "candidates" / f"{cid}.json",
"saga": root / "sagas" / f"{cid}.json",
"plan": root / "release-plans" / f"{cid}.json",
"acceptance": root / "acceptance" / f"{cid}.json",
"approval": root / "approvals" / f"{cid}.json",
}
def _history(root: Path, cid: str, fp: str) -> Path:
return root / "approval-history" / cid / f"{fp}.json"
def _archive(active: Path, history: Path) -> None:
history.parent.mkdir(parents=True, exist_ok=True, mode=0o750)
try:
os.chmod(history.parent, 0o750)
except PermissionError:
pass
original = active.read_bytes()
if history.exists():
if history.is_symlink() or not history.is_file() or history.read_bytes() != original:
raise ApprovalReopenError("APPROVAL_REOPEN_HISTORY_COLLISION")
return
try:
os.link(active, history)
except OSError:
fd = os.open(history, os.O_WRONLY | os.O_CREAT | os.O_EXCL,
active.stat().st_mode & 0o777)
try:
view = memoryview(original)
while view:
n = os.write(fd, view)
view = view[n:]
os.fsync(fd)
finally:
os.close(fd)
if history.read_bytes() != original:
raise ApprovalReopenError("APPROVAL_REOPEN_HISTORY_COPY_FAILED")
_fsync_dir(history.parent)
def _details(event: Mapping[str, Any]) -> Mapping[str, Any]:
val = event.get("details")
return val if isinstance(val, Mapping) else {}
def _cycle_event(saga: Mapping[str, Any], cycle_id: str) -> Mapping[str, Any] | None:
for event in saga.get("events") or []:
if isinstance(event, Mapping) and event.get("event") == EVENT_NAME \
and _details(event).get("approval_cycle_id") == cycle_id:
return event
return None
def _state(name: str) -> Any:
from silent_release_hardening import SagaState # lazy: avoids import cycle
return getattr(SagaState, name)
def _cycle_id(req: ReopenRequest) -> str:
return "approval-reopen-" + _sha(_canon({
"schema": REQUEST_SCHEMA,
"candidate_id": req.candidate_id,
"old_fingerprint": req.expected_approval_fingerprint,
"source_commit": req.expected_source_commit,
"tree_oid": req.expected_tree_oid,
"artifact_sha256": req.expected_artifact_sha256,
"manifest_sha256": req.expected_manifest_sha256,
"release_plan_sha256": req.expected_release_plan_sha256,
"policy_sha256": req.expected_policy_sha256,
"idempotency_key": req.idempotency_key,
}))[:24]
def _validate(req: ReopenRequest, candidate: Mapping[str, Any],
saga: Mapping[str, Any], plan: Mapping[str, Any],
acceptance: Mapping[str, Any], approval: Mapping[str, Any],
now: datetime) -> None:
if saga.get("journal_integrity") is False:
raise ApprovalReopenError("APPROVAL_REOPEN_JOURNAL_INTEGRITY_INVALID")
if saga.get("state") != "APPROVED":
raise ApprovalReopenError("APPROVAL_REOPEN_SAGA_STATE_INVALID")
if candidate.get("candidate_id") != req.candidate_id:
raise ApprovalReopenError("APPROVAL_REOPEN_CANDIDATE_MISMATCH")
if candidate.get("state") not in {"READY_FOR_APPROVAL", "APPROVED"}:
raise ApprovalReopenError("APPROVAL_REOPEN_CANDIDATE_STATE_INVALID")
if candidate.get("approval_state") not in {None, "PENDING"}:
raise ApprovalReopenError("APPROVAL_REOPEN_CANDIDATE_APPROVAL_STATE_INVALID")
# ReleaseCandidate v1 durable records do not persist side_effects_executed.
# Compatibility rule is intentionally fail-closed:
# - an absent key is accepted only together with the independent Saga
# invariant below that the accepted Owner-approval event is the final
# Saga event;
# - if a future durable schema persists the key, it must be exactly False;
# - explicit True/None/number/string values are rejected.
if "side_effects_executed" in candidate and candidate.get("side_effects_executed") is not False:
raise ApprovalReopenError("APPROVAL_REOPEN_SIDE_EFFECTS_ALREADY_EXECUTED")
checks = {
"source": candidate.get("source_commit") == req.expected_source_commit,
"tree": candidate.get("tree_oid") == req.expected_tree_oid,
"artifact": candidate.get("artifact_sha256") == req.expected_artifact_sha256,
"manifest": candidate.get("manifest_sha256") == req.expected_manifest_sha256,
"approval_fingerprint": approval.get("fingerprint") == req.expected_approval_fingerprint,
"approval_source": approval.get("source_commit") == req.expected_source_commit,
"approval_tree": approval.get("tree_oid") == req.expected_tree_oid,
"approval_artifact": approval.get("artifact_sha256") == req.expected_artifact_sha256,
"approval_manifest": approval.get("manifest_sha256") == req.expected_manifest_sha256,
"approval_release_plan": approval.get("release_plan_sha256") == req.expected_release_plan_sha256,
"approval_policy": approval.get("policy_sha256") == req.expected_policy_sha256,
"acceptance": acceptance.get("status") == "ACCEPTED",
"acceptance_release_plan": acceptance.get("release_plan_sha256") == req.expected_release_plan_sha256,
"plan_sha": (plan.get("sha256") or plan.get("release_plan_sha256")) == req.expected_release_plan_sha256,
"plan_policy": plan.get("policy_sha256") == req.expected_policy_sha256,
}
bad = sorted(k for k, ok in checks.items() if not ok)
if bad:
raise ApprovalReopenError("APPROVAL_REOPEN_BINDING_MISMATCH", ",".join(bad))
if now <= _time(approval.get("expires_at"), "APPROVAL_REOPEN_APPROVAL_EXPIRY_INVALID"):
raise ApprovalReopenError("APPROVAL_REOPEN_APPROVAL_NOT_EXPIRED")
events = [e for e in saga.get("events") or [] if isinstance(e, Mapping)]
accepted = None
for i, event in enumerate(events):
if event.get("event") == "signed_owner_approval_accepted" \
and _details(event).get("approval_fingerprint") == req.expected_approval_fingerprint:
accepted = i
if accepted is None:
raise ApprovalReopenError("APPROVAL_REOPEN_ACCEPTED_EVENT_MISSING")
if accepted != len(events) - 1:
raise ApprovalReopenError("APPROVAL_REOPEN_SAGA_ALREADY_ADVANCED")
def reopen_owner_approval(service: Any, raw: Mapping[str, Any], *,
state_root: Path | None = None,
now: datetime | None = None) -> dict[str, Any]:
req = ReopenRequest.from_mapping(raw)
raw_root = state_root or os.environ.get("AIOS_SILENT_RELEASE_STATE_ROOT", "")
if not str(raw_root):
raise ApprovalReopenError("APPROVAL_REOPEN_STATE_ROOT_MISSING")
root = Path(raw_root).expanduser().resolve()
paths = _paths(root, req.candidate_id)
cycle = _cycle_id(req)
history = _history(root, req.candidate_id, req.expected_approval_fingerprint)
saga = _secure_json(paths["saga"])
prior = _cycle_event(saga, cycle)
if prior is not None:
if saga.get("state") != "READY_FOR_APPROVAL":
raise ApprovalReopenError("APPROVAL_REOPEN_CYCLE_ALREADY_CONSUMED")
if not history.is_file():
raise ApprovalReopenError("APPROVAL_REOPEN_HISTORY_MISSING")
if paths["approval"].exists():
active = _secure_json(paths["approval"])
if active.get("fingerprint") != req.expected_approval_fingerprint:
raise ApprovalReopenError("APPROVAL_REOPEN_ACTIVE_APPROVAL_CHANGED")
paths["approval"].unlink()
_fsync_dir(paths["approval"].parent)
return {
"schema": "aios.owner-approval-reopen-result.v1",
"status": "IDEMPOTENT",
"candidate_id": req.candidate_id,
"approval_cycle_id": cycle,
"supersedes_fingerprint": req.expected_approval_fingerprint,
"saga_state": "READY_FOR_APPROVAL",
"history_ref": str(history.relative_to(root)),
}
candidate = _secure_json(paths["candidate"])
plan = _secure_json(paths["plan"])
acceptance = _secure_json(paths["acceptance"])
approval = _secure_json(paths["approval"])
_validate(req, candidate, saga, plan, acceptance, approval,
(now or datetime.now(timezone.utc)).astimezone(timezone.utc))
_archive(paths["approval"], history)
event_details = {
"schema": "aios.owner-approval-reopen-event.v1",
"approval_cycle_id": cycle,
"supersedes_approval_fingerprint": req.expected_approval_fingerprint,
"expired_at": approval.get("expires_at"),
"release_plan_sha256": req.expected_release_plan_sha256,
"source_commit": req.expected_source_commit,
"tree_oid": req.expected_tree_oid,
"artifact_sha256": req.expected_artifact_sha256,
"reason": req.reason.strip(),
"idempotency_digest": _sha(req.idempotency_key.encode()),
}
try:
service.sagas.compare_and_set(
req.candidate_id,
expected_revision=saga.get("revision"),
expected_states={_state("APPROVED")},
target=_state("READY_FOR_APPROVAL"),
event=EVENT_NAME,
details=event_details,
)
except Exception as exc:
raise ApprovalReopenError("APPROVAL_REOPEN_SAGA_CAS_FAILED") from exc
try:
active = _secure_json(paths["approval"])
if active.get("fingerprint") != req.expected_approval_fingerprint:
raise ApprovalReopenError("APPROVAL_REOPEN_ACTIVE_APPROVAL_CHANGED")
paths["approval"].unlink()
_fsync_dir(paths["approval"].parent)
except ApprovalReopenError:
raise
except OSError as exc:
raise ApprovalReopenError("APPROVAL_REOPEN_ACTIVE_APPROVAL_REMOVE_FAILED") from exc
return {
"schema": "aios.owner-approval-reopen-result.v1",
"status": "REOPENED",
"candidate_id": req.candidate_id,
"approval_cycle_id": cycle,
"supersedes_fingerprint": req.expected_approval_fingerprint,
"saga_state": "READY_FOR_APPROVAL",
"history_ref": str(history.relative_to(root)),
"release_plan_sha256": req.expected_release_plan_sha256,
"source_commit": req.expected_source_commit,
"tree_oid": req.expected_tree_oid,
}
@@ -162,8 +162,7 @@ def _copy_exact(source: Path, target: Path, *, mode: int = 0o640) -> None:
target.parent.mkdir(parents=True, exist_ok=True, mode=0o750)
descriptor = os.open(
target,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
| getattr(os, "O_BINARY", 0),
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
mode,
)
try:
@@ -183,8 +182,7 @@ def _write_json_exclusive(path: Path, payload: Mapping[str, Any]) -> str:
raw = _canonical_json(payload) + b"\n"
descriptor = os.open(
path,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
| getattr(os, "O_BINARY", 0),
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
0o640,
)
try:
File diff suppressed because it is too large Load Diff
@@ -553,7 +553,7 @@ class ReleaseSourceAdmissionStore:
supplied_candidate = str(candidate_id or "")
if supplied_candidate and supplied_candidate != record["candidate_id"]:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_CANDIDATE_MISMATCH")
result = {
return {
"mode": RECIPE_MODE,
"task_id": record["task_id"],
"candidate_id": record["candidate_id"],
@@ -562,9 +562,6 @@ class ReleaseSourceAdmissionStore:
"version": record["version"],
"purpose": record["purpose"],
}
if version == "V2R2C5":
result["tree_oid"] = record["tree_oid"]
return result
@dataclass(slots=True)
@@ -1,716 +0,0 @@
#!/usr/bin/env python3
"""Host-injected broker for short release approvals under standing authority.
The broker deliberately owns no business-authority ledger. Its readers must
return verified current snapshots from the host's durable authority and
release-lineage stores. It only derives, verifies, and persists short-lived
``SignedReleasePlanApproval`` children for the exact local ``ReleasePlan``.
"""
from __future__ import annotations
import dataclasses
import hashlib
import secrets
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path, PurePosixPath
from typing import Any, Callable, Mapping, Protocol
from silent_release_controller import CANDIDATE_ID_RE, OID_RE, SHA256_RE, _parse_utc
from silent_release_hardening import (
ALLOWED_CAPABILITIES,
CapabilityAuthorizationError,
ReleasePlan,
SignedReleasePlanApproval,
SilentReleaseActionService,
_canonical_json,
release_file_lock,
)
from task_plane.governance_control_kernel import (
AuthorityDecisionContext,
AuthorityLifecycleState,
ExactScopeState,
OwnerBoundaryReason,
)
_EFFECT_STATES = frozenset(
{"KNOWN_NOT_APPLIED", "KNOWN_IN_PROGRESS", "APPLIED_VERIFIED", "UNKNOWN"}
)
_LINEAGE_KINDS = frozenset({"ROOT", "SUCCESSOR"})
_BINDING_KEYS = frozenset(
{
"authorization_id",
"generation",
"authorization_sha256",
"version",
"repository",
"production_scope",
"authority_base_oid",
"trust_root_sha256",
"actor_id",
"mission_id",
"run_id",
}
)
class StandingAuthorityError(CapabilityAuthorizationError):
"""A current standing-authority or exact-scope check failed closed."""
def __init__(self, code: str, message: str):
super().__init__(message)
self.code = code
def _text(name: str, value: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{name} must be a non-empty canonical string")
return value
def _sha(name: str, value: str) -> str:
value = _text(name, value)
if not SHA256_RE.fullmatch(value):
raise ValueError(f"{name} must be a SHA-256 digest")
return value
def _oid(name: str, value: str) -> str:
value = _text(name, value)
if not OID_RE.fullmatch(value):
raise ValueError(f"{name} must be a Git object id")
return value
def _tuple_of_text(name: str, value: tuple[str, ...]) -> tuple[str, ...]:
if not isinstance(value, tuple) or not value:
raise ValueError(f"{name} must be a non-empty tuple")
normalized = tuple(_text(name, item) for item in value)
if len(set(normalized)) != len(normalized):
raise ValueError(f"{name} contains duplicates")
return normalized
def _utc(value: datetime) -> datetime:
if not isinstance(value, datetime) or value.tzinfo is None:
raise ValueError("clock must return a timezone-aware datetime")
return value.astimezone(timezone.utc)
def _format_utc(value: datetime) -> str:
return _utc(value).replace(microsecond=0).isoformat().replace("+00:00", "Z")
@dataclass(frozen=True)
class CanonicalPlanScope:
version: str
repository: str
service: str
release_root: str
cutover_link: str
authoritative_git_dir: str
dropin_relative_path: str
rollback_target: str
required_capabilities: tuple[str, ...]
def __post_init__(self) -> None:
for name in (
"version",
"repository",
"service",
"release_root",
"cutover_link",
"authoritative_git_dir",
"dropin_relative_path",
"rollback_target",
):
_text(name, getattr(self, name))
capabilities = _tuple_of_text(
"required_capabilities", self.required_capabilities
)
if tuple(sorted(capabilities)) != capabilities:
raise ValueError("required_capabilities must be sorted")
object.__setattr__(self, "required_capabilities", capabilities)
@property
def canonical_payload(self) -> dict[str, Any]:
return {
"cutover_link": self.cutover_link,
"authoritative_git_dir": self.authoritative_git_dir,
"dropin_relative_path": self.dropin_relative_path,
"release_root": self.release_root,
"repository": self.repository,
"required_capabilities": list(self.required_capabilities),
"rollback_target": self.rollback_target,
"service": self.service,
"version": self.version,
}
@property
def sha256(self) -> str:
return hashlib.sha256(_canonical_json(self.canonical_payload)).hexdigest()
def canonical_plan_scope(
plan: ReleasePlan, required_capabilities: tuple[str, ...]
) -> CanonicalPlanScope:
"""Derive production scope only from a validated local ReleasePlan."""
plan.validate()
release_path = PurePosixPath(plan.deployment.release_path)
dropin_path = PurePosixPath(plan.deployment.dropin_path)
try:
dropin_relative = dropin_path.relative_to(release_path).as_posix()
except ValueError as exc:
raise ValueError("drop-in path is outside the candidate release path") from exc
if dropin_relative in {"", "."}:
raise ValueError("drop-in path must be a child of the release path")
return CanonicalPlanScope(
version=plan.tag.version,
repository=plan.deployment.repository_identity,
service=plan.deployment.service,
release_root=plan.deployment.release_root,
cutover_link=plan.deployment.cutover_link,
authoritative_git_dir=plan.deployment.authoritative_git_dir,
dropin_relative_path=dropin_relative,
rollback_target=plan.deployment.rollback_path,
required_capabilities=tuple(sorted(required_capabilities)),
)
@dataclass(frozen=True)
class StandingReleaseAuthorization:
authorization_id: str
generation: int
authorization_sha256: str
version: str
repository: str
production_scope: str
authority_base_oid: str
trust_root_sha256: str
trust_domain: str
lineage_root_candidate_fingerprint: str
allowed_capabilities: tuple[str, ...]
rollback_target: str
owner_identity: str
allowed_actor_ids: tuple[str, ...]
mission_id: str
run_id: str
lifecycle_state: AuthorityLifecycleState
acceptance_complete: bool
acceptance_evidence_ref: str | None = None
expires_at: str | None = None
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
for name in (
"authorization_id",
"version",
"repository",
"trust_domain",
"rollback_target",
"owner_identity",
"mission_id",
"run_id",
):
_text(name, getattr(self, name))
if (
not isinstance(self.generation, int)
or isinstance(self.generation, bool)
or self.generation < 1
):
raise ValueError("generation must be a positive integer")
_sha("authorization_sha256", self.authorization_sha256)
_sha("production_scope", self.production_scope)
_oid("authority_base_oid", self.authority_base_oid)
_sha("trust_root_sha256", self.trust_root_sha256)
_sha(
"lineage_root_candidate_fingerprint",
self.lineage_root_candidate_fingerprint,
)
capabilities = _tuple_of_text(
"allowed_capabilities", self.allowed_capabilities
)
if not set(capabilities).issubset(ALLOWED_CAPABILITIES):
raise ValueError("allowed_capabilities contains an unsupported capability")
object.__setattr__(self, "allowed_capabilities", capabilities)
object.__setattr__(
self, "allowed_actor_ids", _tuple_of_text("allowed_actor_ids", self.allowed_actor_ids)
)
if not isinstance(self.lifecycle_state, AuthorityLifecycleState):
raise ValueError("lifecycle_state must be AuthorityLifecycleState")
if type(self.acceptance_complete) is not bool:
raise ValueError("acceptance_complete must be a boolean")
if self.acceptance_complete:
_text("acceptance_evidence_ref", self.acceptance_evidence_ref or "")
elif self.acceptance_evidence_ref is not None:
raise ValueError("acceptance evidence is only valid for completed acceptance")
if self.expires_at is not None:
_parse_utc(self.expires_at)
if not isinstance(self.evidence_refs, tuple):
raise ValueError("evidence_refs must be a tuple")
for value in self.evidence_refs:
_text("evidence_ref", value)
@dataclass(frozen=True)
class ReleaseExecutionScope:
release_plan_id: str
candidate_id: str
candidate_fingerprint: str
source_commit: str
tree_oid: str
artifact_sha256: str
manifest_sha256: str
deployment_plan_sha256: str
release_plan_sha256: str
approval_plan_id: str
evidence_closure_oid: str
owner_approval_record_oid: str
authority_base_oid: str
trust_root_sha256: str
lineage_root_candidate_fingerprint: str
actor_id: str
mission_id: str
run_id: str
effect_state: str
lineage_kind: str
predecessor_candidate_fingerprint: str | None = None
successor_lineage_sha256: str | None = None
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
for name in (
"release_plan_id",
"candidate_id",
"approval_plan_id",
"actor_id",
"mission_id",
"run_id",
):
_text(name, getattr(self, name))
if not CANDIDATE_ID_RE.fullmatch(self.candidate_id):
raise ValueError("candidate_id is invalid")
for name in ("source_commit", "tree_oid", "evidence_closure_oid", "owner_approval_record_oid", "authority_base_oid"):
_oid(name, getattr(self, name))
for name in (
"candidate_fingerprint",
"artifact_sha256",
"manifest_sha256",
"deployment_plan_sha256",
"release_plan_sha256",
"trust_root_sha256",
"lineage_root_candidate_fingerprint",
):
_sha(name, getattr(self, name))
if self.effect_state not in _EFFECT_STATES:
raise ValueError("effect_state is invalid")
if self.lineage_kind not in _LINEAGE_KINDS:
raise ValueError("lineage_kind is invalid")
if self.lineage_kind == "ROOT":
if self.predecessor_candidate_fingerprint is not None or self.successor_lineage_sha256 is not None:
raise ValueError("root candidate cannot carry successor lineage")
else:
_sha("predecessor_candidate_fingerprint", self.predecessor_candidate_fingerprint or "")
_sha("successor_lineage_sha256", self.successor_lineage_sha256 or "")
if self.predecessor_candidate_fingerprint == self.candidate_fingerprint:
raise ValueError("successor predecessor must be a different candidate")
if not isinstance(self.evidence_refs, tuple):
raise ValueError("evidence_refs must be a tuple")
for value in self.evidence_refs:
_text("evidence_ref", value)
class RegisteredApprovalSigner(Protocol):
@property
def key_id(self) -> str: ...
def sign(
self, unsigned: SignedReleasePlanApproval
) -> SignedReleasePlanApproval | Mapping[str, Any]: ...
class StandingReleaseAuthorizationBroker:
"""Issue and renew exact short approvals from two trusted read layers."""
def __init__(
self,
service: SilentReleaseActionService,
authority_reader: Callable[[], StandingReleaseAuthorization],
execution_scope_reader: Callable[[Mapping[str, Any]], ReleaseExecutionScope],
signer: RegisteredApprovalSigner,
*,
clock: Callable[[], datetime] = lambda: datetime.now(timezone.utc),
execution_ttl_seconds: int = 1200,
renewal_margin_seconds: int = 120,
):
if not callable(authority_reader) or not callable(execution_scope_reader):
raise TypeError("standing authority readers must be callable")
if not callable(getattr(signer, "sign", None)):
raise TypeError("registered approval signer must provide sign()")
if not callable(clock):
raise TypeError("clock must be callable")
if not isinstance(service, SilentReleaseActionService):
raise TypeError("service must be SilentReleaseActionService")
if service.execution_enabled and getattr(signer, "test_only", False) is True:
raise CapabilityAuthorizationError(
"test-only approval signer is forbidden for production execution"
)
if not isinstance(execution_ttl_seconds, int) or isinstance(execution_ttl_seconds, bool) or not 1 <= execution_ttl_seconds <= 3600:
raise ValueError("execution_ttl_seconds must be between 1 and 3600")
if not isinstance(renewal_margin_seconds, int) or isinstance(renewal_margin_seconds, bool) or not 0 <= renewal_margin_seconds < execution_ttl_seconds:
raise ValueError("renewal_margin_seconds must be shorter than the execution TTL")
self.service = service
self.authority_reader = authority_reader
self.execution_scope_reader = execution_scope_reader
self.signer = signer
self.clock = clock
self.execution_ttl_seconds = execution_ttl_seconds
self.renewal_margin_seconds = renewal_margin_seconds
self._lock_root = Path(service.approvals_root) / ".standing-authority-locks"
@staticmethod
def _result(status: str, context: AuthorityDecisionContext, approval=None):
# silent_release_execution imports this module during host composition.
from silent_release_execution import StandingAuthorizationResult
return StandingAuthorizationResult(
status=status, authority_context=context, approval=approval
)
@staticmethod
def _context(
authorization: StandingReleaseAuthorization | None,
scope_state: ExactScopeState,
*,
lifecycle_state: AuthorityLifecycleState | None = None,
boundary: OwnerBoundaryReason | None = None,
) -> AuthorityDecisionContext:
refs = () if authorization is None else authorization.evidence_refs
return AuthorityDecisionContext(
lifecycle_state=lifecycle_state
or (authorization.lifecycle_state if authorization else AuthorityLifecycleState.UNKNOWN),
scope_state=scope_state,
owner_boundary_reason=boundary,
evidence_refs=refs,
)
@staticmethod
def _binding(
authorization: StandingReleaseAuthorization, scope: ReleaseExecutionScope
) -> dict[str, Any]:
return {
"authorization_id": authorization.authorization_id,
"generation": authorization.generation,
"authorization_sha256": authorization.authorization_sha256,
"version": authorization.version,
"repository": authorization.repository,
"production_scope": authorization.production_scope,
"authority_base_oid": authorization.authority_base_oid,
"trust_root_sha256": authorization.trust_root_sha256,
"actor_id": scope.actor_id,
"mission_id": authorization.mission_id,
"run_id": authorization.run_id,
}
def _read(self, envelope: Mapping[str, Any]):
authorization = self.authority_reader()
# The trusted scope reader receives only the locally checked release
# identity. Caller-added envelope keys cannot become authority input.
query_keys = (
"approval_plan_id",
"artifact_sha256",
"candidate_fingerprint",
"candidate_id",
"deployment_plan_sha256",
"evidence_closure_oid",
"manifest_sha256",
"policy_sha256",
"release_plan_sha256",
"required_capabilities",
"schema",
"source_commit",
"tree_oid",
)
scope = self.execution_scope_reader(
{name: envelope[name] for name in query_keys if name in envelope}
)
if not isinstance(authorization, StandingReleaseAuthorization):
raise StandingAuthorityError("AUTHORITY_SNAPSHOT_INVALID", "authority reader returned an invalid snapshot")
if not isinstance(scope, ReleaseExecutionScope):
raise StandingAuthorityError("RELEASE_SCOPE_INVALID", "scope reader returned an invalid snapshot")
return authorization, scope
def _load_plan_truth(self, envelope: Mapping[str, Any]):
candidate_id = str(envelope.get("candidate_id") or "")
if not CANDIDATE_ID_RE.fullmatch(candidate_id):
raise StandingAuthorityError("APPROVAL_ENVELOPE_INVALID", "candidate id is invalid")
candidate = self.service.candidates.load(candidate_id)
plan = self.service.release_plans.load(candidate_id)
required = tuple(sorted(str(item) for item in envelope.get("required_capabilities", ())))
if not required or len(required) != len(set(required)) or not set(required).issubset(ALLOWED_CAPABILITIES):
raise StandingAuthorityError("APPROVAL_ENVELOPE_INVALID", "required capabilities are invalid")
plan_scope = canonical_plan_scope(plan, required)
expected_envelope = {
"candidate_id": candidate.candidate_id,
"candidate_fingerprint": candidate.fingerprint,
"source_commit": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"manifest_sha256": candidate.manifest_sha256,
"deployment_plan_sha256": plan.deployment.sha256,
"release_plan_sha256": plan.sha256,
"approval_plan_id": plan.tag.approval_plan_id,
"evidence_closure_oid": plan.tag.evidence_closure_oid,
"policy_sha256": self.service.policy_sha256,
}
for name, expected in expected_envelope.items():
if envelope.get(name) != expected:
raise StandingAuthorityError("APPROVAL_ENVELOPE_STALE", f"approval envelope {name} is stale")
return candidate, plan, plan_scope, required
def _assert_current(
self,
authorization: StandingReleaseAuthorization,
scope: ReleaseExecutionScope,
envelope: Mapping[str, Any],
plan_scope: CanonicalPlanScope,
required: tuple[str, ...],
now: datetime,
) -> None:
if authorization.acceptance_complete:
raise StandingAuthorityError("AUTHORITY_ACCEPTED_AND_CLOSED", "trusted release acceptance is complete")
if authorization.lifecycle_state is not AuthorityLifecycleState.ACTIVE:
raise StandingAuthorityError(f"AUTHORITY_{authorization.lifecycle_state.value}", "standing authorization is not active")
if authorization.expires_at is not None and _parse_utc(authorization.expires_at) <= now:
raise StandingAuthorityError("AUTHORITY_EXPIRED", "standing authorization has expired")
if authorization.version != plan_scope.version or authorization.repository != plan_scope.repository:
raise StandingAuthorityError("AUTHORITY_RELEASE_SCOPE_MISMATCH", "release identity is outside standing authority")
if authorization.production_scope != plan_scope.sha256 or authorization.rollback_target != plan_scope.rollback_target:
raise StandingAuthorityError("AUTHORITY_PRODUCTION_SCOPE_MISMATCH", "production or rollback scope drifted")
if not set(required).issubset(authorization.allowed_capabilities):
raise StandingAuthorityError("AUTHORITY_CAPABILITY_DENIED", "required capability is outside standing authority")
exact_scope = (
scope.candidate_id == envelope["candidate_id"]
and scope.candidate_fingerprint == envelope["candidate_fingerprint"]
and scope.source_commit == envelope["source_commit"]
and scope.tree_oid == envelope["tree_oid"]
and scope.artifact_sha256 == envelope["artifact_sha256"]
and scope.manifest_sha256 == envelope["manifest_sha256"]
and scope.deployment_plan_sha256 == envelope["deployment_plan_sha256"]
and scope.release_plan_sha256 == envelope["release_plan_sha256"]
and scope.approval_plan_id == envelope["approval_plan_id"]
and scope.evidence_closure_oid == envelope["evidence_closure_oid"]
and scope.authority_base_oid == authorization.authority_base_oid
and scope.trust_root_sha256 == authorization.trust_root_sha256
and scope.actor_id in authorization.allowed_actor_ids
and scope.mission_id == authorization.mission_id
and scope.run_id == authorization.run_id
)
if not exact_scope:
raise StandingAuthorityError("AUTHORITY_EXACT_SCOPE_MISMATCH", "trusted release execution scope does not match")
durable_release_plan_id = self.service.sagas.load(
scope.candidate_id
).release_plan_id
if scope.release_plan_id != durable_release_plan_id:
raise StandingAuthorityError(
"RELEASE_PLAN_ID_MISMATCH",
"trusted scope does not match the durable Saga release plan",
)
if scope.lineage_root_candidate_fingerprint != authorization.lineage_root_candidate_fingerprint:
raise StandingAuthorityError("AUTHORITY_LINEAGE_ROOT_MISMATCH", "candidate lineage is not rooted in standing authority")
if scope.lineage_kind == "ROOT" and scope.candidate_fingerprint != authorization.lineage_root_candidate_fingerprint:
raise StandingAuthorityError("AUTHORITY_LINEAGE_REQUIRED", "changed candidate requires verified successor lineage")
if scope.effect_state == "UNKNOWN":
raise StandingAuthorityError("RELEASE_EFFECT_UNKNOWN", "release effect must be reconciled before issuance")
def _unsigned(
self,
authorization: StandingReleaseAuthorization,
scope: ReleaseExecutionScope,
plan: ReleasePlan,
required: tuple[str, ...],
now: datetime,
) -> SignedReleasePlanApproval:
expires = now + timedelta(seconds=self.execution_ttl_seconds)
if authorization.expires_at is not None:
expires = min(expires, _parse_utc(authorization.expires_at))
if expires <= now:
raise StandingAuthorityError("AUTHORITY_EXPIRED", "standing authorization cannot cover a new child")
issued_at = _format_utc(now)
return SignedReleasePlanApproval(
release_plan_id=scope.release_plan_id,
candidate_id=scope.candidate_id,
source_commit=scope.source_commit,
tree_oid=scope.tree_oid,
artifact_sha256=scope.artifact_sha256,
manifest_sha256=scope.manifest_sha256,
deployment_plan_sha256=plan.deployment.sha256,
policy_sha256=self.service.policy_sha256,
approval_plan_id=scope.approval_plan_id,
evidence_closure_oid=scope.evidence_closure_oid,
owner_approval_record_oid=scope.owner_approval_record_oid,
capabilities=required,
expires_at=_format_utc(expires),
nonce="standing-" + secrets.token_hex(16),
key_id=_text("signer.key_id", self.signer.key_id),
signature="",
owner_identity=authorization.owner_identity,
trust_domain=authorization.trust_domain,
issued_at=issued_at,
not_before=issued_at,
release_plan_sha256=plan.sha256,
authorization_binding=self._binding(authorization, scope),
)
@staticmethod
def _signed(value: SignedReleasePlanApproval | Mapping[str, Any]) -> SignedReleasePlanApproval:
return value if isinstance(value, SignedReleasePlanApproval) else SignedReleasePlanApproval.from_dict(value)
def _verify_signed(self, approval, candidate, plan, now):
verifier = self.service.approval_verifier
if verifier is None:
raise StandingAuthorityError("APPROVAL_VERIFIER_MISSING", "approval verifier is not configured")
verifier.verify_release_plan(approval, candidate, plan, self.service.policy_sha256, now=now)
def __call__(self, envelope: Mapping[str, Any]):
if not isinstance(envelope, Mapping):
raise TypeError("approval envelope must be a mapping")
candidate_id = str(envelope.get("candidate_id") or "")
lock_name = hashlib.sha256(candidate_id.encode("utf-8")).hexdigest() + ".lock"
lock_path = self._lock_root / lock_name
with release_file_lock(lock_path, blocking=True):
try:
candidate, plan, plan_scope, required = self._load_plan_truth(envelope)
now = _utc(self.clock())
authorization, scope = self._read(envelope)
self._assert_current(authorization, scope, envelope, plan_scope, required, now)
try:
existing = self.service.approvals.load(candidate_id)
except FileNotFoundError:
existing = None
if existing is not None:
# Expiry is the one expected reason an otherwise valid
# short child needs replacement. Verify its signature,
# immutable evidence, key and revocation state at the end
# of its signed window; a future/not-yet-valid or malformed
# child still fails the verifier.
# Expiry is exclusive. When renewing an already expired
# child, verify its signature at the final instant inside
# its original signed window rather than at expiry itself.
old_expiry = _parse_utc(existing.expires_at)
old_verification_time = min(
now, old_expiry - timedelta(microseconds=1)
)
self._verify_signed(existing, candidate, plan, old_verification_time)
self._check_binding(existing, authorization, scope, required)
if _parse_utc(existing.expires_at) - now > timedelta(seconds=self.renewal_margin_seconds):
return self._result("APPROVAL_ISSUED", self._context(authorization, ExactScopeState.IN_SCOPE), existing.to_dict())
unsigned = self._unsigned(authorization, scope, plan, required, now)
signed = self._signed(self.signer.sign(unsigned))
# A signer may only add its signature to the complete request.
if signed.canonical_payload != unsigned.canonical_payload:
raise StandingAuthorityError("SIGNER_MUTATED_APPROVAL", "registered signer changed the approval request")
self._verify_signed(signed, candidate, plan, now)
after_authorization, after_scope = self._read(envelope)
after_now = _utc(self.clock())
self._assert_current(after_authorization, after_scope, envelope, plan_scope, required, after_now)
if after_authorization != authorization or after_scope != scope:
raise StandingAuthorityError("AUTHORITY_CHANGED_DURING_SIGN", "authority changed while the child was signed")
self._verify_signed(signed, candidate, plan, after_now)
if existing is None:
self.service.submit_owner_approval(signed.to_dict())
else:
self.service.renew_owner_approval(candidate_id, signed.to_dict(), expected_fingerprint=existing.fingerprint)
persisted = self.service.approvals.load(candidate_id)
if persisted.fingerprint != signed.fingerprint:
raise StandingAuthorityError("APPROVAL_PERSISTENCE_MISMATCH", "persisted approval differs from signed approval")
return self._result("APPROVAL_ISSUED", self._context(authorization, ExactScopeState.IN_SCOPE), persisted.to_dict())
except StandingAuthorityError as exc:
authorization = locals().get("authorization")
closed_states = {
"AUTHORITY_ACCEPTED_AND_CLOSED": AuthorityLifecycleState.CLOSED,
"AUTHORITY_EXPIRED": AuthorityLifecycleState.CLOSED,
"AUTHORITY_REVOKED": AuthorityLifecycleState.REVOKED,
"AUTHORITY_CLOSED": AuthorityLifecycleState.CLOSED,
"AUTHORITY_CANCELLED": AuthorityLifecycleState.CANCELLED,
}
if exc.code in closed_states:
context = self._context(
authorization,
ExactScopeState.IN_SCOPE,
lifecycle_state=closed_states[exc.code],
)
return self._result("AUTHORIZATION_DENIED", context)
if exc.code in {
"AUTHORITY_UNKNOWN",
"AUTHORITY_SNAPSHOT_INVALID",
"AUTHORITY_CHANGED_DURING_SIGN",
}:
return self._result(
"WAITING_AUTHORITY",
self._context(
authorization,
ExactScopeState.UNKNOWN,
lifecycle_state=AuthorityLifecycleState.UNKNOWN,
),
)
if exc.code.startswith("AUTHORITY_") and exc.code not in {"AUTHORITY_SNAPSHOT_INVALID", "AUTHORITY_CHANGED_DURING_SIGN"}:
goal_change = {
"AUTHORITY_RELEASE_SCOPE_MISMATCH",
"AUTHORITY_PRODUCTION_SCOPE_MISMATCH",
"AUTHORITY_CAPABILITY_DENIED",
"AUTHORITY_LINEAGE_REQUIRED",
}
context = self._context(
authorization,
ExactScopeState.OUT_OF_SCOPE,
boundary=(
OwnerBoundaryReason.GOAL_CHANGE
if exc.code in goal_change
else OwnerBoundaryReason.TRUST_BOUNDARY_CHANGE
),
)
return self._result("AUTHORIZATION_DENIED", context)
context = self._context(authorization, ExactScopeState.UNKNOWN)
return self._result("WAITING_AUTHORITY", context)
except Exception:
return self._result("WAITING_AUTHORITY", self._context(None, ExactScopeState.UNKNOWN))
def _check_binding(self, approval, authorization, scope, required=None):
binding = getattr(approval, "authorization_binding", None)
if not isinstance(binding, Mapping) or set(binding) != _BINDING_KEYS:
raise StandingAuthorityError("AUTHORIZATION_BINDING_INVALID", "approval lacks an exact standing authorization binding")
if dict(binding) != self._binding(authorization, scope):
raise StandingAuthorityError("AUTHORIZATION_BINDING_STALE", "approval standing authorization binding is stale")
if approval.owner_identity != authorization.owner_identity or approval.trust_domain != authorization.trust_domain:
raise StandingAuthorityError("AUTHORIZATION_IDENTITY_MISMATCH", "approval owner or trust binding is stale")
if (
approval.release_plan_id != scope.release_plan_id
or approval.evidence_closure_oid != scope.evidence_closure_oid
or approval.owner_approval_record_oid != scope.owner_approval_record_oid
):
raise StandingAuthorityError(
"AUTHORIZATION_OPERATION_BINDING_MISMATCH",
"approval operation identity is stale",
)
if required is not None and tuple(sorted(approval.capabilities)) != tuple(required):
raise StandingAuthorityError(
"AUTHORIZATION_CAPABILITY_BINDING_MISMATCH",
"approval capability set does not match the exact plan scope",
)
def check_current(self, approval: SignedReleasePlanApproval, capability: str, operation: str) -> None:
if not isinstance(approval, SignedReleasePlanApproval):
raise StandingAuthorityError("APPROVAL_INVALID", "current authority guard requires a signed approval")
_text("operation", operation)
if capability not in approval.capabilities:
raise StandingAuthorityError("CAPABILITY_NOT_GRANTED", "approval does not grant the requested capability")
envelope = self.service.get_approval_envelope(approval.candidate_id)
candidate, plan, plan_scope, required = self._load_plan_truth(envelope)
now = _utc(self.clock())
authorization, scope = self._read(envelope)
self._assert_current(authorization, scope, envelope, plan_scope, required, now)
self._check_binding(approval, authorization, scope, required)
self._verify_signed(approval, candidate, plan, now)
@@ -1,91 +0,0 @@
"""Distinct filename/path contracts and no-follow writes at the filesystem edge."""
from __future__ import annotations
import os
import ipaddress
from pathlib import Path, PurePosixPath
import re
import secrets
import stat
def require_isolated_dev_profile(host: str) -> None:
if os.getenv("AIOS_GITOPS_DEV_ALLOW", "0") != "1":
return
try:
loopback = ipaddress.ip_address(host).is_loopback
except (TypeError, ValueError):
loopback = False
if os.getenv("AIOS_RUNTIME_PROFILE") != "isolated-staging" or not loopback:
raise RuntimeError("DEV_ALLOW requires isolated-staging and loopback")
def safe_segment(value: str, *, max_length: int = 160) -> str:
if (not isinstance(value, str) or not value or len(value) > max_length
or value in (".", "..") or value.endswith((".", " "))
or re.fullmatch(r"[A-Za-z0-9_@.-]+", value) is None
or value.split(".")[0].upper() in {"CON", "PRN", "AUX", "NUL", *[f"COM{i}" for i in range(1, 10)], *[f"LPT{i}" for i in range(1, 10)]}):
raise ValueError("invalid single path segment")
return value
def relative_parts(value: str) -> tuple[str, ...]:
if not isinstance(value, str) or "\\" in value or ":" in value or value.startswith("/"):
raise ValueError("invalid relative path")
parts = tuple(value.split("/"))
for part in parts:
safe_segment(part, max_length=255)
if parts[0] == ".git":
raise ValueError("Git metadata path forbidden")
return parts
def contained_path(root: Path, segment: str) -> Path:
safe_segment(segment)
root = Path(root)
if root.is_symlink():
raise ValueError("symlink root forbidden")
path = root / segment
if path.is_symlink() or not path.resolve().is_relative_to(root.resolve()):
raise ValueError("path escapes root")
return path
def write_text_no_follow(root: Path, relative: str, content: str) -> None:
"""Atomic POSIX write using directory descriptors; no lexical-only fallback."""
parts = relative_parts(relative)
if os.name != "posix" or not hasattr(os, "O_NOFOLLOW"):
raise OSError("secure directory-relative writes require POSIX no-follow support")
root = Path(root)
flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
current = os.open(root, flags)
temporary = ".c6-" + secrets.token_hex(16)
try:
for part in parts[:-1]:
try:
os.mkdir(part, mode=0o750, dir_fd=current)
except FileExistsError:
pass
child = os.open(part, flags, dir_fd=current)
os.close(current)
current = child
try:
info = os.stat(parts[-1], dir_fd=current, follow_symlinks=False)
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
raise ValueError("nonregular or multiply-linked target forbidden")
except FileNotFoundError:
pass
fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o600, dir_fd=current)
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as stream:
stream.write(content); stream.flush(); os.fsync(stream.fileno())
os.replace(temporary, parts[-1], src_dir_fd=current, dst_dir_fd=current)
os.fsync(current)
finally:
try:
os.unlink(temporary, dir_fd=current)
except FileNotFoundError:
pass
finally:
os.close(current)
+36 -438
View File
@@ -22,16 +22,6 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
)
from silent_release_controller import _sanitize, utc_now
from task_plane.governance_control_kernel import (
AutonomousDecisionEngine,
AuthorityDecisionContext,
AuthorityLifecycleState,
DecisionInput,
DecisionEffectIntent,
ExactScopeState,
NextAction,
OwnerBoundaryReason,
)
from silent_release_hardening import (
BuilderAttestation,
CapabilityAuthorizationError,
@@ -43,60 +33,9 @@ from silent_release_hardening import (
_canonical_json,
_durable_json,
_safe_child,
release_file_lock,
)
@dataclasses.dataclass(frozen=True)
class StandingAuthorizationResult:
"""Typed result from a current authority reader plus formal issuer.
This object is routing evidence, not an authorization token. A result that
carries an approval still has to pass ``SignedApprovalVerifier`` inside the
release service before the saga can advance.
"""
status: str
authority_context: AuthorityDecisionContext
approval: Mapping[str, Any] | None = None
effect_intent: DecisionEffectIntent = DecisionEffectIntent.SIDE_EFFECT
def __post_init__(self) -> None:
allowed = {
"APPROVAL_ISSUED",
"AUTHORIZATION_DENIED",
"OWNER_REQUIRED",
"WAITING_AUTHORITY",
}
if self.status not in allowed:
raise ValueError("invalid standing authorization status")
if not isinstance(self.authority_context, AuthorityDecisionContext):
raise ValueError("authority_context must be AuthorityDecisionContext")
if self.effect_intent is not DecisionEffectIntent.SIDE_EFFECT:
raise ValueError("standing authorization must be a SIDE_EFFECT decision")
if self.status == "APPROVAL_ISSUED":
if not isinstance(self.approval, Mapping):
raise ValueError("issued authorization requires a signed approval")
if (
self.authority_context.lifecycle_state
is not AuthorityLifecycleState.ACTIVE
or self.authority_context.scope_state is not ExactScopeState.IN_SCOPE
or self.authority_context.owner_boundary_reason is not None
):
raise ValueError("issued approval is inconsistent with authority context")
elif self.approval is not None:
raise ValueError("non-issued authorization cannot carry an approval")
if self.status == "OWNER_REQUIRED" and (
self.authority_context.owner_boundary_reason is None
):
raise ValueError("OWNER_REQUIRED needs an explicit boundary reason")
StandingAuthorizationBroker = Callable[
[Mapping[str, Any]], StandingAuthorizationResult | Mapping[str, Any]
]
class BuilderSigner(Protocol):
@property
def key_id(self) -> str:
@@ -323,56 +262,38 @@ class LeaderLock:
def __init__(self, path: Path):
self.path = Path(path)
self.handle = None
self._lock_context = None
def __enter__(self):
self._lock_context = release_file_lock(self.path, blocking=False)
self.path.parent.mkdir(parents=True, exist_ok=True)
os.chmod(self.path.parent, 0o700)
self.handle = self.path.open("a+", encoding="utf-8")
os.chmod(self.path, 0o600)
try:
self.handle = self._lock_context.__enter__()
import fcntl
fcntl.flock(
self.handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB
)
except BlockingIOError as exc:
self.handle.close()
self.handle = None
self._lock_context = None
raise RuntimeError("silent release leader is already active") from exc
return self
def __exit__(self, exc_type, exc, tb):
if self.handle is not None:
try:
self._lock_context.__exit__(exc_type, exc, tb)
import fcntl
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
finally:
self.handle.close()
self.handle = None
self._lock_context = None
class PersistentSilentReleaseController:
"""Idempotent main-event controller with durable startup/recovery reports."""
_UNKNOWN_EFFECT_ACTIONS = frozenset(
{
"reconcile_baseline_registry",
"reconcile_release",
"reconcile_tag",
"resume_rollback",
"resume_runtime_switch",
"verify_runtime",
}
)
_RECOVERY_SIDE_EFFECT_STATES = frozenset(
{
"APPROVED",
"PRE_SWITCH_REVALIDATING",
"SWITCH_PENDING",
"SWITCHED",
"RUNTIME_VERIFYING",
"RUNTIME_VERIFIED",
"TAG_PENDING",
"TAG_VERIFIED",
"BASELINE_REGISTRY_PENDING",
"ROLLBACK_PENDING",
"RECONCILIATION_REQUIRED",
}
)
def __init__(
self,
root: Path,
@@ -380,42 +301,14 @@ class PersistentSilentReleaseController:
*,
main_probe: Callable[[], str],
recovery_worker: ReleaseRecoveryWorker | None = None,
owner_approval_broker: StandingAuthorizationBroker | None = None,
decision_engine: AutonomousDecisionEngine | None = None,
):
self.root = Path(root)
self.service = service
self.main_probe = main_probe
self.recovery_worker = recovery_worker
self.owner_approval_broker = owner_approval_broker
self.decision_engine = decision_engine or AutonomousDecisionEngine()
self.leader_path = self.root / "controller.leader.lock"
self.status_path = self.root / "controller-status.json"
def run_once_safely(self) -> dict[str, Any]:
"""Keep read-only diagnostics available when a control tick fails.
A dependency failure never grants write permission or repairs a corrupt
journal. Only a later successful, fully checked tick may advance work.
Exception text can contain credentials, so persist its type only.
"""
try:
return self.run_once()
except Exception as exc:
result = {
"error": type(exc).__name__,
"execution_enabled": self.service.execution_enabled,
"shadow_mode": not self.service.execution_enabled,
# A failed production tick may have reached an adapter before
# losing its acknowledgement. Never assert zero effects here.
"side_effects_executed": (
None if self.service.execution_enabled else False
),
"status": "FAIL_CLOSED",
}
_durable_json(self.status_path, result)
return _sanitize(result)
def startup_verify(self) -> dict[str, Any]:
checked = 0
failed = 0
@@ -436,196 +329,6 @@ class PersistentSilentReleaseController:
_durable_json(self.status_path, status)
return status
@classmethod
def _unknown_effects_pending(cls, recovery: Mapping[str, Any]) -> bool:
return any(
isinstance(item, Mapping)
and item.get("recovery_action") in cls._UNKNOWN_EFFECT_ACTIONS
for item in recovery.get("sagas", ())
)
@classmethod
def _recovery_side_effect_candidates(
cls, recovery: Mapping[str, Any]
) -> tuple[str, ...]:
return tuple(
str(item["candidate_id"])
for item in recovery.get("sagas", ())
if isinstance(item, Mapping)
and item.get("state") in cls._RECOVERY_SIDE_EFFECT_STATES
and item.get("candidate_id")
)
@staticmethod
def _authority_context_payload(
context: AuthorityDecisionContext,
) -> dict[str, Any]:
return {
"lifecycle_state": context.lifecycle_state.value,
"scope_state": context.scope_state.value,
"owner_boundary_reason": (
context.owner_boundary_reason.value
if context.owner_boundary_reason is not None
else None
),
"evidence_refs": list(context.evidence_refs),
}
def _try_standing_authorization(self, candidate_id: str) -> dict[str, Any]:
try:
existing = self.service.approvals.load(candidate_id)
except FileNotFoundError:
existing = None
# Default-off compatibility: an already accepted exact approval may be
# revalidated, but absence of a current broker never derives new
# authority. When a broker is configured it must be consulted on every
# side-effect decision so a still-valid short approval cannot mask a
# revoked/closed root authorization or an exact-scope change.
if self.owner_approval_broker is None and existing is not None:
try:
accepted = self.service.submit_owner_approval(existing.to_dict())
except Exception:
accepted = None
else:
return {
"candidate_id": candidate_id,
"approval_fingerprint": accepted.get("fingerprint"),
"status": "APPROVED",
"idempotent": True,
}
if self.owner_approval_broker is None:
return {
"candidate_id": candidate_id,
"reason_code": "OWNER_APPROVAL_BACKEND_MISSING",
"status": "WAITING_AUTHORITY",
}
envelope = self.service.get_approval_envelope(candidate_id)
try:
issued = self.owner_approval_broker(dict(envelope))
except Exception as exc:
code = str(getattr(exc, "code", "") or type(exc).__name__)
return {
"candidate_id": candidate_id,
"reason_code": code,
"status": "WAITING_AUTHORITY",
}
authority_context = None
decision = None
if isinstance(issued, StandingAuthorizationResult):
authority_context = issued.authority_context
context_payload = self._authority_context_payload(authority_context)
decision = self.decision_engine.decide(
DecisionInput(
goal_id=candidate_id,
admission_ready=True,
effect_intent=DecisionEffectIntent.SIDE_EFFECT,
authority_context=authority_context,
evidence_refs=authority_context.evidence_refs,
)
)
decision_payload = decision.to_dict()
if decision.next_valid_action is NextAction.DENY_EFFECT:
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": decision.blocker_code
or "STANDING_AUTHORIZATION_DENIED",
"status": "AUTHORIZATION_DENIED",
}
if decision.next_valid_action is NextAction.OWNER_BOUNDARY:
boundary_reason = decision.owner_boundary_reason
if boundary_reason is None:
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": "OWNER_BOUNDARY_REASON_MISSING",
"status": "WAITING_AUTHORITY",
}
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": boundary_reason.value,
"status": "OWNER_REQUIRED",
}
if decision.next_valid_action is not NextAction.CREATE_OR_RESUME_NEXT_CHILD:
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": decision.blocker_code
or decision.next_valid_action.value,
"status": "WAITING_AUTHORITY",
}
if issued.status != "APPROVAL_ISSUED":
status = (
"AUTHORIZATION_DENIED"
if issued.status == "AUTHORIZATION_DENIED"
else "WAITING_AUTHORITY"
)
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": issued.status,
"status": status,
}
approval = dict(issued.approval or {})
elif isinstance(issued, Mapping):
# Backward-compatible C5 callback contract. Mapping coverage does
# not grant authority; submit_owner_approval verifies the signed
# object against the current exact candidate and ReleasePlan.
approval = dict(issued)
else:
return {
"candidate_id": candidate_id,
"reason_code": "OWNER_APPROVAL_MISSING",
"status": "WAITING_AUTHORITY",
}
try:
accepted = self.service.submit_owner_approval(approval)
except Exception as exc:
code = str(getattr(exc, "code", "") or type(exc).__name__)
result = {
"candidate_id": candidate_id,
"reason_code": code,
"status": "WAITING_AUTHORITY",
}
if authority_context is not None:
result["authority_context"] = self._authority_context_payload(
authority_context
)
if decision is not None:
result["decision"] = decision.to_dict()
return result
if accepted.get("status") not in {"VERIFIED_AND_STORED", "IDEMPOTENT"}:
return {
"candidate_id": candidate_id,
"reason_code": "APPROVAL_NOT_VERIFIED",
"status": "WAITING_AUTHORITY",
}
result = {
"candidate_id": candidate_id,
"approval_fingerprint": accepted.get("fingerprint"),
"idempotent": accepted.get("status") == "IDEMPOTENT",
"saga_state": accepted.get("saga_state"),
"status": "APPROVED",
}
if authority_context is not None:
result["authority_context"] = self._authority_context_payload(
authority_context
)
if decision is not None:
result["decision"] = decision.to_dict()
return result
def run_once(self) -> dict[str, Any]:
with LeaderLock(self.leader_path):
startup = self.startup_verify()
@@ -642,98 +345,26 @@ class PersistentSilentReleaseController:
}
_durable_json(self.status_path, result)
return _sanitize(result)
# ReleaseRecoveryWorker can resume an externally visible effect.
# Re-resolve current root lifecycle and exact scope for every such
# candidate before invoking it; executor/verifier checks remain the
# final fence. This also prevents a cached short approval from
# bypassing root revocation during restart recovery.
recovery_status = self.service.get_recovery_status()
unknown_effect = self._unknown_effects_pending(recovery_status)
recovery_authority_decisions: list[dict[str, Any]] = []
recovery_candidates = (
self._recovery_side_effect_candidates(recovery_status)
if self.recovery_worker is not None
else ()
)
for candidate_id in recovery_candidates:
authorization = self._try_standing_authorization(candidate_id)
recovery_authority_decisions.append(authorization)
if authorization.get("status") != "APPROVED":
notification = None
if (
not unknown_effect
and authorization.get("status") == "OWNER_REQUIRED"
):
notification = self.service.ensure_owner_notification(
candidate_id,
reason_code=str(authorization["reason_code"]),
)
result = {
"authority_decision": authorization,
"execution_enabled": self.service.execution_enabled,
"intent": None,
"notification": notification,
"recovery": recovery_status,
"recovery_authority_decisions": (
recovery_authority_decisions
),
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
"status": (
"RECONCILIATION_REQUIRED"
if unknown_effect
else authorization.get("status")
),
}
_durable_json(self.status_path, result)
return _sanitize(result)
recovery = (
self.recovery_worker.run_once()
if self.recovery_worker is not None
else recovery_status
)
recovery_status = self.service.get_recovery_status()
if self._unknown_effects_pending(recovery_status):
result = {
"authority_decision": None,
"execution_enabled": self.service.execution_enabled,
"intent": None,
"notification": None,
"recovery": recovery,
"recovery_authority_decisions": (
recovery_authority_decisions
),
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
"status": "RECONCILIATION_REQUIRED",
}
_durable_json(self.status_path, result)
return _sanitize(result)
source_commit = str(self.main_probe())
event_id = f"main-{source_commit}"
intent = self.service.create_release_intent(
{"event_id": event_id, "source_commit": source_commit}
)
notification = None
authorization = None
if intent.get("candidate_id"):
candidate_id = str(intent["candidate_id"])
authorization = self._try_standing_authorization(candidate_id)
if authorization.get("status") == "OWNER_REQUIRED":
notification = self.service.ensure_owner_notification(
candidate_id,
reason_code=str(authorization["reason_code"]),
)
notification = self.service.ensure_owner_notification(
str(intent["candidate_id"])
)
recovery = (
self.recovery_worker.run_once()
if self.recovery_worker is not None
else self.service.get_recovery_status()
)
result = {
"authority_decision": authorization,
"execution_enabled": self.service.execution_enabled,
"intent": intent,
"notification": notification,
"recovery": recovery,
"recovery_authority_decisions": recovery_authority_decisions,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
@@ -742,51 +373,6 @@ class PersistentSilentReleaseController:
return _sanitize(result)
def compose_standing_release_controller(
root: Path,
service: SilentReleaseActionService,
*,
main_probe: Callable[[], str],
authority_reader: Callable[[], Any],
execution_scope_reader: Callable[[Mapping[str, Any]], Any],
signer: Any,
recovery_worker: ReleaseRecoveryWorker | None = None,
execution_ttl_seconds: int = 1200,
renewal_margin_seconds: int = 120,
clock: Callable[[], Any] | None = None,
) -> PersistentSilentReleaseController:
"""Compose trusted host adapters into the existing controller and effects.
Readers must return the validated snapshot types defined by
``release_standing_authority``. The signer must already belong to the
service's release approval trust domain. This function neither registers
keys nor performs network/authority reads during cold startup.
"""
from release_standing_authority import StandingReleaseAuthorizationBroker
options = {
"execution_ttl_seconds": execution_ttl_seconds,
"renewal_margin_seconds": renewal_margin_seconds,
}
if clock is not None:
options["clock"] = clock
broker = StandingReleaseAuthorizationBroker(
service,
authority_reader,
execution_scope_reader,
signer,
**options,
)
service.install_current_authority_guard(broker.check_current)
return PersistentSilentReleaseController(
root,
service,
main_probe=main_probe,
recovery_worker=recovery_worker,
owner_approval_broker=broker,
)
class ShadowActionHandler(BaseHTTPRequestHandler):
service: SilentReleaseActionService
controller: PersistentSilentReleaseController
@@ -899,12 +485,24 @@ def serve_shadow(
service,
main_probe=git_main_probe(repository),
)
controller.run_once_safely()
controller.run_once()
stop = threading.Event()
def controller_loop() -> None:
while not stop.wait(poll_seconds):
controller.run_once_safely()
try:
controller.run_once()
except Exception as exc: # fail closed and keep the API available
_durable_json(
controller.status_path,
{
"error": type(exc).__name__,
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
"status": "FAIL_CLOSED",
},
)
handler = type(
"ConfiguredShadowActionHandler",
File diff suppressed because it is too large Load Diff
@@ -1,271 +0,0 @@
"""Exact source-only admission composition within the existing Bridge.
This module neither installs itself nor issues authority. A host activation must
pin an Owner-signed policy and an independently verified epoch. The existing
Task admission store, V3 producer, Task Plane and principal verifier are reused.
"""
from __future__ import annotations
import hashlib
import base64
import json
from pathlib import Path
from typing import Any, Mapping
from aios_single_host_authority import (
SignedArtifact, canonical_json_bytes, _open_secure,
)
from aios_task_admission_bootstrap import TaskAdmissionError
from aios_task_admission_bridge_contract import TaskAdmissionBridgeContract
from aios_task_admission_live_adapters import TaskAdmissionRuntimeBundle
from governed_source_publication_policy import (
ScopedSourcePublicationPolicyAdmission, SourcePublicationPolicyVerifier,
)
def _read_object(path: Path) -> tuple[bytes, dict[str, Any]]:
raw = _open_secure(path, private=False, require_root_owner=True, maximum=1024 * 1024)
value = json.loads(raw)
if not isinstance(value, dict):
raise ValueError("SOURCE_ACTIVATION_OBJECT_REQUIRED")
return raw, value
class ScopedPrincipalProvider:
"""Policy revocation also fences principals from already admitted Tasks."""
def __init__(self, guarded: ScopedSourcePublicationPolicyAdmission, delegate: Any):
self.guarded = guarded
self.delegate = delegate
def readiness(self) -> Mapping[str, Any]:
try:
self.guarded.verifier.require(self.guarded.artifact, self.guarded.request)
return self.delegate.readiness()
except Exception as exc:
return {"ready": False, "mode": "source-publication-only", "error": str(exc)}
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]) -> Any:
grant = self.delegate.store.get_grant_by_task_id(str(request.get("task_id") or ""))
if grant is None or grant.parent_epoch_id != self.delegate.epoch_id:
raise TaskAdmissionError("SOURCE_PRINCIPAL_GRANT_REQUIRED", 403)
# status rechecks the signed policy, exact durable envelope and original
# signed epoch. The delegate then verifies the live TaskLease/state.
self.guarded.status({"envelope_id": grant.envelope_id,
"delegation_epoch_id": grant.parent_epoch_id})
return self.delegate(transport, request)
class _AdmissionContext:
"""Metadata only for legacy hosts retaining their signed principal verifier."""
def __init__(self, bundle):
self.store, self.authorization = bundle.store, bundle.authorization
self.task_port, self.epoch_id = bundle.task_port, bundle.epoch_id
def readiness(self):
return {"ready": False, "error": "REGISTERED_SIGNED_PRINCIPAL_VERIFIER_REQUIRED"}
def __call__(self, *_args, **_kwargs):
raise TaskAdmissionError("REGISTERED_SIGNED_PRINCIPAL_VERIFIER_REQUIRED", 403)
def guard_source_runtime(
runtime: TaskAdmissionRuntimeBundle, *, artifact: SignedArtifact,
verifier: SourcePublicationPolicyVerifier, expected_agent_id: str,
) -> TaskAdmissionRuntimeBundle:
service = runtime.service
if service is None:
raise ValueError("SOURCE_ACTIVATION_BOOTSTRAP_REQUIRED")
guarded = ScopedSourcePublicationPolicyAdmission(
verifier, bootstrap=service, artifact=artifact,
expected_agent_id=expected_agent_id,
)
# Build the typed request through the same exact-envelope guard used by the
# HTTP contract, instead of accepting a caller-supplied request projection.
payload = artifact.payload
request = guarded._request_for_raw({
"repository": payload["repository"],
"authority_base_oid": payload["authority_base_oid"],
"target_branch": payload["target_branch"], "generation": payload["generation"],
"execution_digest": payload["execution_digest"], "risk": payload["risk"],
"file_allowlist": payload["exact_files"], "requested_capabilities": payload["capabilities"],
})
guarded.request = request
verifier.require(artifact, request)
principal = runtime.principal_verifier or _AdmissionContext(runtime)
epoch = principal.authorization.verify_delegation_epoch(principal.epoch_id)
# The complete per-envelope check runs again on authorization and every
# principal use; reject a production-capable epoch already at composition.
if (getattr(epoch, "production_scope", None) != "none"
or getattr(epoch, "destructive_policy", None) != "forbidden"):
raise TaskAdmissionError("SOURCE_ACTIVATION_EPOCH_SCOPE_DENIED", 403)
return TaskAdmissionRuntimeBundle(
handlers=TaskAdmissionBridgeContract(guarded).handlers(),
principal_verifier=ScopedPrincipalProvider(guarded, principal),
service=guarded,
)
def build_source_runtime_from_activation_file(path: Path, *, post_admission: Any,
ordinary_activation_file: Path):
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
load_owner_approval_verifier,
)
_, config = _read_object(path)
if set(config) != {"schema", "policy_artifact", "policy_sha256", "policy_revocations",
"admission_activation", "expected_agent_id"} or config["schema"] != "aios.source-publication-host-activation.v1":
raise ValueError("SOURCE_ACTIVATION_FIELDS_INVALID")
for key in ("policy_artifact", "policy_revocations", "admission_activation"):
value = config[key]
if not isinstance(value, str) or not value.startswith("/") or ".." in Path(value).parts:
raise ValueError("SOURCE_ACTIVATION_PATH_INVALID")
raw, signed = _read_object(Path(config["policy_artifact"]))
if hashlib.sha256(raw).hexdigest() != config["policy_sha256"]:
raise ValueError("SOURCE_ACTIVATION_POLICY_HASH_MISMATCH")
if set(signed) != {"payload", "signature", "key_id", "namespace"}:
raise ValueError("SOURCE_ACTIVATION_ARTIFACT_INVALID")
artifact = SignedArtifact(canonical_payload=canonical_json_bytes(signed["payload"]), **signed)
_, ordinary_config = _read_object(ordinary_activation_file)
_, source_config = _read_object(Path(config["admission_activation"]))
for field in ("state_db", "task_api_url", "task_control_token_file", "owner_verifier_module"):
if not ordinary_config.get(field) or source_config.get(field) != ordinary_config[field]:
raise ValueError("SOURCE_ACTIVATION_CONTROL_PLANE_BINDING_MISMATCH")
def not_revoked(payload: Mapping[str, Any]) -> bool:
_, value = _read_object(Path(config["policy_revocations"]))
if (set(value) != {"schema", "revoked_nonces"}
or value["schema"] != "aios.source-publication-revocations.v1"
or not isinstance(value["revoked_nonces"], list)
or any(not isinstance(nonce, str) or not nonce for nonce in value["revoked_nonces"])):
return False
return payload["nonce"] not in value["revoked_nonces"]
runtime = build_task_admission_runtime_from_activation_file(
Path(config["admission_activation"]), post_admission=post_admission)
verifier = SourcePublicationPolicyVerifier(
load_owner_approval_verifier(Path(source_config["owner_verifier_module"])),
revocation_check=not_revoked,
expected_policy_digest=runtime.service.policy.normalized_policy_digest(),
)
guarded_runtime = guard_source_runtime(runtime, artifact=artifact, verifier=verifier,
expected_agent_id=config["expected_agent_id"])
payload = artifact.payload
plan_path = Path(source_config["execution_plan_dir"]) / (payload["execution_digest"] + ".json")
plan_bytes, plan = _read_object(plan_path)
if (hashlib.sha256(plan_bytes).hexdigest() != payload["execution_digest"]
or canonical_json_bytes(plan) != plan_bytes):
raise ValueError("SOURCE_ACTIVATION_PLAN_DIGEST_MISMATCH")
hashes = plan.get("file_sha256")
if not isinstance(hashes, dict) or set(hashes) != set(payload["exact_files"]):
raise ValueError("SOURCE_ACTIVATION_FILE_HASH_SET_MISMATCH")
for field in ("repository", "authority_base_oid", "target_branch", "generation", "risk"):
if plan.get(field) != payload[field]:
raise ValueError("SOURCE_ACTIVATION_PLAN_SCOPE_MISMATCH")
if sorted(plan.get("capabilities", [])) != sorted(payload["capabilities"]):
raise ValueError("SOURCE_ACTIVATION_PLAN_CAPABILITIES_MISMATCH")
if not isinstance(plan.get("task_id"), str) or not plan["task_id"]:
raise ValueError("SOURCE_ACTIVATION_PLAN_TASK_REQUIRED")
guarded_runtime.service.expected_file_sha256 = dict(hashes)
guarded_runtime.service.expected_task_id = plan["task_id"]
return guarded_runtime
class AdmissionRuntimeRouter:
"""Route an exact branch/epoch without widening the ordinary policy."""
def __init__(self, ordinary: TaskAdmissionRuntimeBundle, source: TaskAdmissionRuntimeBundle):
self.ordinary = ordinary
self.source = source
self.guard = source.service
self.store = self.guard.bootstrap.store
def _source_request(self, operation: str, request: Mapping[str, Any]) -> bool:
if operation == "getTaskAdmissionEnvelope":
return request.get("target_branch") == self.guard.artifact.payload["target_branch"]
envelope_id = str(request.get("envelope_id") or "")
row = self.store.get_envelope(envelope_id)
if row is None:
# Never fall through after finding a source envelope, including
# when it has expired or been revoked.
return False
envelope = self.guard.bootstrap._load_envelope(envelope_id)
return envelope.target_branch == self.guard.artifact.payload["target_branch"]
def handlers(self) -> dict[str, Any]:
def dispatch(operation: str, request: Mapping[str, Any]):
runtime = self.source if self._source_request(operation, request) else self.ordinary
return runtime.handlers[operation](request)
return {op: (lambda request, op=op: dispatch(op, request)) for op in self.source.handlers}
def readiness(self) -> Mapping[str, Any]:
return {"mode": "scoped-admission-router", "ordinary": self.ordinary.principal_verifier.readiness(),
"source_publication": self.source.principal_verifier.readiness()}
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]):
grant = self.store.get_grant_by_task_id(str(request.get("task_id") or ""))
provider = (self.source.principal_verifier if grant is not None
and grant.target_branch == self.guard.artifact.payload["target_branch"]
else self.ordinary.principal_verifier)
return provider(transport, request)
def bundle(self) -> TaskAdmissionRuntimeBundle:
return TaskAdmissionRuntimeBundle(handlers=self.handlers(), principal_verifier=self)
class ExistingPrincipalPolicyFence:
"""Add a C5 policy fence while retaining the registered signed verifier.
Layered staging hosts use a signed TaskPrincipal verifier with their own
storage scope. They must not be migrated to a server attestation merely to
add source admission. Every request still reaches the original verifier.
"""
def __init__(self, delegate: Any, source_runtime: TaskAdmissionRuntimeBundle):
self.delegate = delegate
self.guard = source_runtime.service
def __getattr__(self, name: str):
return getattr(self.delegate, name)
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]):
grant = self.guard.bootstrap.store.get_grant_by_task_id(str(request.get("task_id") or ""))
if grant is not None and grant.target_branch == self.guard.artifact.payload["target_branch"]:
self.guard.status({"envelope_id": grant.envelope_id,
"delegation_epoch_id": grant.parent_epoch_id})
if transport.get("operation") in {"commitFilesGoverned", "commitFileGoverned"}:
expected = getattr(self.guard, "expected_file_sha256", None)
if not isinstance(expected, dict) or not expected:
raise TaskAdmissionError("SOURCE_COMMIT_FILE_HASHES_REQUIRED", 403)
try:
raw = request.get("files")
if raw is None:
raw = {request["path"]: request["content_base64"]}
pairs = list(raw.items()) if isinstance(raw, dict) else [(item["path"], item["content_base64"]) for item in raw]
if len({path for path, _ in pairs}) != len(pairs):
raise ValueError("duplicate path")
actual = {path: hashlib.sha256(base64.b64decode(data, validate=True)).hexdigest()
for path, data in pairs}
except Exception as exc:
raise TaskAdmissionError("SOURCE_COMMIT_FILE_PAYLOAD_INVALID", 403) from exc
if actual != expected:
raise TaskAdmissionError("SOURCE_COMMIT_CONTENT_SCOPE_MISMATCH", 403)
return self.delegate(transport, request)
def expired_admission_runtime() -> TaskAdmissionRuntimeBundle:
"""Keep a verified-expired ordinary epoch denied while serving other routes."""
def deny(*_args: Any, **_kwargs: Any):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_EXPIRED", 403)
class ClosedPrincipal:
__call__ = staticmethod(deny)
def readiness(self):
return {"ready": False, "error": "TASK_ADMISSION_EPOCH_EXPIRED"}
from aios_task_admission_bridge_contract import TASK_ADMISSION_OPERATION_REQUIRED_FIELDS
return TaskAdmissionRuntimeBundle(
handlers={op: deny for op in TASK_ADMISSION_OPERATION_REQUIRED_FIELDS},
principal_verifier=ClosedPrincipal(),
)
+70 -166
View File
@@ -105,28 +105,6 @@ pre { white-space: pre-wrap; word-break: break-word; }
</div>
</div>
<script>
// Security: all dynamic values (API/git/agent/owner-gate) are rendered with
// textContent / DOM APIs. innerHTML is never used to inject untrusted data.
function clearNode(node) {
if (!node) return;
while (node.firstChild) node.removeChild(node.firstChild);
}
function setText(id, text) {
const n = document.getElementById(id);
if (n) n.textContent = text;
}
function addClass(node, cls) { if (node) node.classList.add(cls); }
function div(cls) { const d = document.createElement('div'); if (cls) addClass(d, cls); return d; }
function span(cls, text) { const s = document.createElement('span'); if (cls) addClass(s, cls); if (text != null) s.textContent = text; return s; }
// Centralized CSRF token source: server-issued meta tag, or double-submit cookie.
function csrfToken() {
const meta = document.querySelector('meta[name="csrf-token"]');
if (meta && meta.content) return meta.content;
const m = document.cookie.match(/(?:^|;\s*)aios_csrf=([^;]+)/);
return m ? decodeURIComponent(m[1]) : '';
}
async function fetchData() {
const r = await fetch('/admin/ai-evolution/api/data');
const data = await r.json();
@@ -146,204 +124,130 @@ async function fetchData() {
async function loadVersions() {
const el = document.getElementById('versions-list');
clearNode(el);
const loading = div('loading'); loading.textContent = 'Loading versions...'; el.appendChild(loading);
el.innerHTML = '<div class="loading">Loading versions...</div>';
try {
const r = await fetch('/admin/ai-evolution/api/versions');
const data = await r.json();
clearNode(el);
if (data.error) { const e = div('error'); e.textContent = data.error; el.appendChild(e); return; }
const grid = div('grid');
if (data.error) { el.innerHTML = '<div class="error">' + data.error + '</div>'; return; }
let html = '<div class="grid">';
for (const v of data.versions || []) {
const card = div('card');
const t = div('card-title'); t.textContent = v.tag || '';
const val = div('card-value'); val.textContent = v.version || v.tag || '';
card.appendChild(t); card.appendChild(val); grid.appendChild(card);
html += '<div class="card"><div class="card-title">' + v.tag + '</div><div class="card-value">' + (v.version || v.tag) + '</div></div>';
}
el.appendChild(grid);
} catch (e) {
const err = div('error'); err.textContent = 'Failed to load versions: ' + e.message; el.appendChild(err);
html += '</div>';
el.innerHTML = html;
} catch(e) {
el.innerHTML = '<div class="error">Failed to load versions: ' + e.message + '</div>';
}
}
async function loadCommands() {
const el = document.getElementById('commands-list');
clearNode(el);
const loading = div('loading'); loading.textContent = 'Loading commands...'; el.appendChild(loading);
el.innerHTML = '<div class="loading">Loading commands...</div>';
try {
const r = await fetch('/admin/ai-evolution/api/data');
const data = await r.json();
const cmds = data.commands || [];
clearNode(el);
const wrapper = div('');
wrapper.style.display = 'flex'; wrapper.style.gap = '24px'; wrapper.style.marginBottom = '16px';
const totalCard = div('card');
const tcTitle = div('card-title'); tcTitle.textContent = 'Total';
const tcVal = div('card-value'); tcVal.textContent = String(cmds.length);
totalCard.appendChild(tcTitle); totalCard.appendChild(tcVal); wrapper.appendChild(totalCard);
const catCard = div('card');
const ccTitle = div('card-title'); ccTitle.textContent = 'By Category';
catCard.appendChild(ccTitle);
let cmds = data.commands || [];
let html = '<div style="display:flex;gap:24px;margin-bottom:16px;">';
html += '<div><div class="card"><div class="card-title">Total</div><div class="card-value">' + cmds.length + '</div></div></div>';
html += '<div><div class="card"><div class="card-title">By Category</div>';
const cats = {};
for (const c of cmds) { const k = c.category || 'uncategorized'; cats[k] = (cats[k] || 0) + 1; }
for (const [k, v] of Object.entries(cats)) {
const line = div(''); line.style.fontSize = '14px'; line.style.color = '#8b949e'; line.style.margin = '4px 0';
line.textContent = k + ': ' + v; catCard.appendChild(line);
}
wrapper.appendChild(catCard); el.appendChild(wrapper);
for (const [k, v] of Object.entries(cats)) html += '<div style="font-size:14px;color:#8b949e;margin:4px 0;">' + k + ': ' + v + '</div>';
html += '</div></div></div>';
for (const cmd of cmds.slice().reverse()) {
const item = div('command-item');
const meta = div('meta'); meta.textContent = (cmd.category || '') + ' | ' + (cmd.filename || '');
const name = div('name'); name.textContent = cmd.title || '';
const summary = div(''); summary.style.fontSize = '13px'; summary.style.color = '#c9d1d9'; summary.style.marginTop = '4px';
summary.textContent = cmd.summary || '';
item.appendChild(meta); item.appendChild(name); item.appendChild(summary);
el.appendChild(item);
html += '<div class="command-item"><div class="meta">' + cmd.category + ' | ' + cmd.filename + '</div><div class="name">' + cmd.title + '</div><div style="font-size:13px;color:#c9d1d9;margin-top:4px;">' + cmd.summary + '</div></div>';
}
} catch (e) {
const err = div('error'); err.textContent = 'Failed to load commands: ' + e.message; el.appendChild(err);
el.innerHTML = html;
} catch(e) {
el.innerHTML = '<div class="error">Failed to load commands: ' + e.message + '</div>';
}
}
async function loadAgents() {
const el = document.getElementById('agents-list');
clearNode(el);
const loading = div('loading'); loading.textContent = 'Loading agent runs...'; el.appendChild(loading);
el.innerHTML = '<div class="loading">Loading agent runs...</div>';
try {
const r = await fetch('/admin/ai-evolution/api/agents');
const data = await r.json();
clearNode(el);
if (data.error) { const e = div('error'); e.textContent = data.error; el.appendChild(e); return; }
const agents = data.agents || [];
if (!agents.length) {
const none = div(''); none.style.padding = '24px'; none.style.color = '#8b949e';
none.textContent = 'No agent runs recorded yet.'; el.appendChild(none); return;
if (data.error) { el.innerHTML = '<div class="error">' + data.error + '</div>'; return; }
let html = '';
for (const agent of data.agents || []) {
html += '<div class="agent-item"><div class="agent-title">' + agent.name + ' <span class="state state-' + (agent.status === 'running' ? 'running' : (agent.status === 'ok' ? 'completed' : 'failed')) + '">' + agent.status + '</span></div><div class="agent-meta">' + (agent.runs || 0) + ' runs | last: ' + (agent.last_run || '-') + '</div></div>';
}
for (const agent of agents) {
const item = div('agent-item');
const title = div('agent-title');
title.appendChild(document.createTextNode(agent.name || ''));
const st = (agent.status === 'running') ? 'running' : ((agent.status === 'ok') ? 'completed' : 'failed');
const badge = span('state state-' + st, agent.status || '');
title.appendChild(badge);
const meta = div('agent-meta');
meta.textContent = (agent.runs || 0) + ' runs | last: ' + (agent.last_run || '-');
item.appendChild(title); item.appendChild(meta); el.appendChild(item);
}
} catch (e) {
const err = div('error'); err.textContent = 'Failed to load agents: ' + e.message; el.appendChild(err);
el.innerHTML = html || '<div style="padding:24px;color:#8b949e;">No agent runs recorded yet.</div>';
} catch(e) {
el.innerHTML = '<div class="error">Failed to load agents: ' + e.message + '</div>';
}
}
function showPanel(name) {
async function loadOwnerGate() {
const el = document.getElementById('arch-owner-content');
clearNode(el);
const loading = div('loading'); loading.textContent = 'Loading owner gate data...'; el.appendChild(loading);
el.innerHTML = '<div class="loading">Loading owner gate data...</div>';
try {
const r = await fetch('/admin/ai-evolution/api/owner-gate');
const data = await r.json();
clearNode(el);
const row = div('row');
const cols = [['Pending Requests', data.pending_count], ['Approved', data.approved_count], ['Rejected', data.rejected_count]];
for (const [label, value] of cols) {
const col = div('col'); const card = div('card');
const t = div('card-title'); t.textContent = label;
const val = div('card-value'); val.textContent = value;
card.appendChild(t); card.appendChild(val); col.appendChild(card); row.appendChild(col);
}
el.appendChild(row);
const listCard = div('card');
const lt = div('card-title'); lt.textContent = 'Pending Approval Requests'; listCard.appendChild(lt);
const reqs = data.pending_requests || [];
if (reqs.length) {
for (const req of reqs) {
const item = div('command-item');
const name = div('name'); name.textContent = req.title || req.change_id || '';
const meta = div('meta'); meta.textContent = 'ID: ' + (req.change_id || '') + ' | Risk: ' + (req.risk || '');
const reason = div(''); reason.style.fontSize = '13px'; reason.style.color = '#c9d1d9'; reason.style.marginTop = '4px';
reason.textContent = req.reason || '';
item.appendChild(name); item.appendChild(meta); item.appendChild(reason);
listCard.appendChild(item);
let html = '<div class="row">';
html += '<div class="col"><div class="card"><div class="card-title">Pending Requests</div><div class="card-value">' + data.pending_count + '</div></div></div>';
html += '<div class="col"><div class="card"><div class="card-title">Approved</div><div class="card-value">' + data.approved_count + '</div></div></div>';
html += '<div class="col"><div class="card"><div class="card-title">Rejected</div><div class="card-value">' + data.rejected_count + '</div></div></div>';
html += '</div>';
html += '<div class="card"><div class="card-title">Pending Approval Requests</div>';
if (data.pending_requests && data.pending_requests.length) {
for (const req of data.pending_requests) {
html += '<div class="command-item">';
html += '<div class="name">' + (req.title || req.change_id) + '</div>';
html += '<div class="meta">ID: ' + req.change_id + ' | Risk: ' + req.risk + '</div>';
html += '<div style="font-size:13px;color:#c9d1d9;margin-top:4px;">' + (req.reason || '') + '</div>';
html += '</div>';
}
} else {
const none = div(''); none.style.color = '#8b949e'; none.style.padding = '12px';
none.textContent = 'No pending requests.'; listCard.appendChild(none);
html += '<div style="color:#8b949e;padding:12px;">No pending requests.</div>';
}
el.appendChild(listCard);
} catch (e) {
const err = div('error'); err.textContent = 'Failed to load: ' + e.message; el.appendChild(err);
}
}
// Real governed approve: posts the decision to the server-side governed endpoint.
async function approveDecision(changeId, decision, btn) {
if (btn) btn.disabled = true;
try {
const res = await fetch('/admin/ai-evolution/api/owner-gate/decision', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken() },
body: JSON.stringify({ change_id: changeId, decision: decision })
});
const payload = await res.json();
if (!res.ok) throw new Error(payload.error || ('HTTP ' + res.status));
await loadApprove();
} catch (e) {
if (btn) btn.disabled = false;
const out = document.getElementById('arch-approve-content');
const err = div('error'); err.textContent = 'Decision failed: ' + e.message;
if (out) out.appendChild(err);
html += '</div>';
el.innerHTML = html;
} catch(e) {
el.innerHTML = '<div class="error">Failed to load: ' + e.message + '</div>';
}
}
async function loadApprove() {
const el = document.getElementById('arch-approve-content');
clearNode(el);
const loading = div('loading'); loading.textContent = 'Loading pending approvals...'; el.appendChild(loading);
el.innerHTML = '<div class="loading">Loading pending approvals...</div>';
try {
const r = await fetch('/admin/ai-evolution/api/owner-gate');
const data = await r.json();
clearNode(el);
const reqs = data.pending_requests || [];
if (reqs.length) {
const card = div('card');
const t = div('card-title'); t.textContent = 'Pending changes requiring your approval'; card.appendChild(t);
for (const req of reqs) {
const item = div('command-item');
const name = div('name'); name.textContent = req.title || req.change_id || '';
const meta = div('meta');
const strongRisk = document.createElement('strong'); strongRisk.textContent = req.risk || '';
meta.appendChild(document.createTextNode('Risk: ')); meta.appendChild(strongRisk);
const reason = div(''); reason.style.fontSize = '13px'; reason.style.color = '#c9d1d9'; reason.style.margin = '8px 0';
reason.textContent = req.reason || '';
const actions = div(''); actions.style.display = 'flex'; actions.style.gap = '8px';
const btn = document.createElement('button');
btn.textContent = '✓ Approve';
btn.style.padding = '6px 16px'; btn.style.background = '#238636'; btn.style.color = '#fff';
btn.style.border = 'none'; btn.style.borderRadius = '6px'; btn.style.cursor = 'pointer'; btn.style.fontSize = '13px';
const changeId = req.change_id;
btn.addEventListener('click', function () { approveDecision(changeId, 'approved', btn); });
actions.appendChild(btn);
item.appendChild(name); item.appendChild(meta); item.appendChild(reason); item.appendChild(actions);
card.appendChild(item);
let html = '';
if (data.pending_requests && data.pending_requests.length) {
html += '<div class="card"><div class="card-title">Pending changes requiring your approval</div>';
for (const req of data.pending_requests) {
html += '<div class="command-item">';
html += '<div class="name">' + (req.title || req.change_id) + '</div>';
html += '<div class="meta">Risk: <strong>' + req.risk + '</strong></div>';
html += '<div style="font-size:13px;color:#c9d1d9;margin:8px 0;">' + (req.reason || '') + '</div>';
html += '<div style="display:flex;gap:8px;">';
html += '<button onclick="ownerAction.classList.add('\'approved\');this.disabled=true" style="padding:6px 16px;background:#238636;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:13px;">✓ Approve</button>';
html += '</div></div>';
}
el.appendChild(card);
html += '</div>';
} else {
const done = div('card');
const inner = div(''); inner.style.padding = '24px'; inner.style.textAlign = 'center'; inner.style.color = '#8b949e';
inner.textContent = '✅ All changes processed. No pending approval requests.';
done.appendChild(inner); el.appendChild(done);
html += '<div class="card"><div style="padding:24px;text-align:center;color:#8b949e;">✅ All changes processed. No pending approval requests.</div></div>';
}
} catch (e) {
const err = div('error'); err.textContent = 'Failed to load: ' + e.message; el.appendChild(err);
el.innerHTML = html;
} catch(e) {
el.innerHTML = '<div class="error">Failed to load: ' + e.message + '</div>';
}
}
function showPanel(name) {
document.querySelectorAll('.panel').forEach(p => p.style.display = 'none');
document.getElementById('panel-' + name).style.display = '';
document.querySelectorAll('.sidebar-nav li').forEach(li => li.classList.remove('active'));
document.querySelector('.sidebar-nav li:nth-child(' + ({overview:1,versions:2,commands:3,agents:4,['arch-owner']:5,['arch-approve']:6}[name] || 1) + ')').classList.add('active');
if (name === 'overview') fetchData();
document.querySelector('.sidebar-nav li:nth-child(' + ({overview:1,versions:2,commands:3,agents:4}[name] || 1) + ')').classList.add('active');
if (name === 'overview') {
document.getElementById('panel-overview').innerHTML = '<div class="main-header"><h2>System Overview</h2><div id="header-status" class="status-badge">Loading...</div></div><div id="loading" class="loading">Loading...</div><div id="error" class="error" style="display:none;"></div><div id="content"><div class="row"><div class="col"><div class="card"><div class="card-title">Current Baseline</div><div id="base" class="card-value">-</div></div></div><div class="col"><div class="card"><div class="card-title">Git Branch</div><div id="branch" class="card-value">-</div></div></div><div class="col"><div class="card"><div class="card-title">Uptime</div><div id="uptime" class="card-value">-</div></div></div></div><div class="card" style="margin-top:16px"><div class="card-title">Event Bus Status</div><pre id="bus-status">-</pre></div></div>';
fetchData();
}
const loaders = {
'arch-owner': loadOwnerGate,
'arch-approve': loadApprove,
+1 -25
View File
@@ -12,29 +12,6 @@ from .task_lifecycle import TaskLifecycle
lifecycle = TaskLifecycle()
# Role must come from an authenticated principal, never from the client body.
_ROLE_WHITELIST = {"user", "agent", "executor", "dispatcher", "notifier", "group_admin", "owner"}
_authenticator = None # callable(payload) -> {"role": ...} | None, injected by host
def set_authenticator(fn):
"""Install a server-side authenticator used to derive the actor role.
`fn` receives the raw request payload and must return either None (or a
dict without a whitelisted "role") for anonymous, or a dict containing a
server-verified "role". The client-supplied `payload["role"]` is ignored.
"""
global _authenticator
_authenticator = fn
def effective_role(payload: dict[str, Any]) -> str:
if _authenticator is not None:
principal = _authenticator(payload)
if isinstance(principal, dict) and principal.get("role") in _ROLE_WHITELIST:
return principal["role"]
return "user"
def get_task_actions(task: dict[str, Any], role: str) -> dict[str, Any]:
return {
@@ -54,8 +31,7 @@ def build_router():
@router.post("/{task_id}/actions/{action}", operation_id="executeTaskAction")
def action(task_id: str, action: str, payload: dict[str, Any]):
# Role is derived from the authenticated principal; body role is ignored.
return execute_task_action(payload.get("task", {}), action, effective_role(payload), payload.get("reason"))
return execute_task_action(payload.get("task", {}), action, payload.get("role", "user"), payload.get("reason"))
return router
+9 -271
View File
@@ -1,257 +1,20 @@
"""Authenticated read-only Task Center projections and event transport."""
"""Task Center API."""
from __future__ import annotations
import json
from collections.abc import Callable, Mapping
from typing import Any
try:
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import StreamingResponse
except Exception: # pragma: no cover - importable without FastAPI for model tests
APIRouter = HTTPException = Request = StreamingResponse = None
from fastapi import APIRouter
except Exception:
APIRouter = None
from .task_center import TaskCenter
from .task_dashboard_view import ProjectionCursorExpired, ProjectionCursorTransportLimit, TaskDashboardView
from .task_plane.errors import ForbiddenError, UnauthorizedError
class ProjectionUnavailable(RuntimeError):
"""The host did not install the source and identity bindings required to read."""
task_center = TaskCenter()
dashboard = TaskDashboardView()
_projection_reader: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None = None
_principal_resolver: Callable[[Any], Mapping[str, Any] | None] | None = None
def set_task_projection_reader(reader: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None) -> None:
"""Install a host-owned reader that returns already-authorized task/event data."""
global _projection_reader
_projection_reader = reader
def set_task_projection_principal_resolver(
resolver: Callable[[Any], Mapping[str, Any] | None] | None,
) -> None:
"""Install the host's authenticated-principal resolver for all projection reads."""
global _principal_resolver
_principal_resolver = resolver
def get_task_overview(
tasks: list[dict[str, Any]] | None = None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None = None,
) -> dict[str, Any]:
"""Project provided durable records; route callers use ``_read_projection``."""
summary = task_center.summarize(tasks)
projection = dashboard.build(tasks, events_by_task)
return {
**summary,
"tasks": projection["tasks"],
"views": projection["views"],
"snapshot_cursor": dashboard.snapshot_cursor(tasks, events_by_task),
}
def get_task_events(
cursor: str | None,
*,
principal: Mapping[str, Any],
limit: int = 100,
) -> dict[str, Any]:
source = _read_projection(principal)
feed = dashboard.event_feed(source["tasks"], source["events_by_task"], cursor, limit=limit)
cards = {card["task_id"]: card for card in dashboard.build(source["tasks"], source["events_by_task"])["tasks"]}
for event in feed["events"]:
card = cards.get(event["entity_id"])
if card is None:
continue
# Event sequence and task revision have different authority domains.
# The client fences entity snapshots by task revision, while resumable
# transport remains governed by the per-task append sequence.
event["event_sequence"] = event["sequence"]
event["event_revision"] = event.get("revision")
event["task_revision"] = card.get("revision")
event["revision"] = card.get("revision")
event["task"] = card
event["related_tasks"] = _ancestor_cards(card, cards)
return feed
def _ancestor_cards(card: Mapping[str, Any], cards: Mapping[str, dict[str, Any]]) -> list[dict[str, Any]]:
related: list[dict[str, Any]] = []
parent_id = card.get("lineage", {}).get("parent_task_id") if isinstance(card.get("lineage"), Mapping) else None
visited: set[str] = set()
while isinstance(parent_id, str) and parent_id and parent_id not in visited:
visited.add(parent_id)
parent = cards.get(parent_id)
if parent is None:
break
related.append(parent)
lineage = parent.get("lineage")
parent_id = lineage.get("parent_task_id") if isinstance(lineage, Mapping) else None
return related
def configure_task_plane_projection(service: Any, principal_resolver: Callable[[Any], Mapping[str, Any] | None]) -> None:
"""Bind this FastAPI router to an existing authorized ``TaskPlaneService``.
The resolver must return a host-authenticated principal containing the
server-derived ``task_plane_identity``. No browser-provided role or scope
participates in these reads.
"""
def reader(principal: Mapping[str, Any]) -> Mapping[str, Any]:
actor = principal.get("task_plane_identity")
if not isinstance(actor, str) or not actor:
raise PermissionError("task projection identity is missing")
return _read_task_plane_snapshot(service, actor)
set_task_projection_principal_resolver(principal_resolver)
set_task_projection_reader(reader)
_TASKPLANE_DISPLAY_STATUSES = {
"CREATED": "pending",
"QUEUED": "pending",
"CLAIMED": "running",
"RUNNING": "running",
"WAITING_CHILD": "running",
"VERIFYING": "running",
"COMPLETED": "completed",
"FAILED_RECOVERABLE": "blocked",
"FAILED_FINAL": "blocked",
"CANCELLED": "blocked",
"MANUAL_REVIEW_REQUIRED": "waiting_approval",
}
def _read_task_plane_snapshot(service: Any, actor: str) -> Mapping[str, Any]:
"""Read an authorized, bounded-consistency TaskPlane projection.
``list_tasks`` and ``get_events`` are separate authorized reads. We only
publish their result when a second task and event-position read confirms
that no visible task revision, membership, or per-task append position
moved while the first snapshot was collected. A busy plane is retried
once and then fails closed rather than emitting a mixed snapshot.
"""
for _attempt in range(2):
raw_tasks = _visible_tasks(service.list_tasks(actor))
task_ids = [task["task_id"] for task in raw_tasks]
events_by_task = {task_id: service.get_events(task_id, actor) for task_id in task_ids}
verification_events = {task_id: service.get_events(task_id, actor) for task_id in task_ids}
verified_tasks = _visible_tasks(service.list_tasks(actor))
if _task_revisions(raw_tasks) == _task_revisions(verified_tasks) and all(
_event_positions(events_by_task[task_id]) == _event_positions(verification_events[task_id])
for task_id in task_ids
):
return {"tasks": raw_tasks, "events_by_task": events_by_task}
raise ProjectionUnavailable("task projection changed during snapshot")
def _visible_tasks(records: Any) -> list[dict[str, Any]]:
if not isinstance(records, list):
raise ProjectionUnavailable("task projection source returned invalid tasks")
tasks: list[dict[str, Any]] = []
for raw in records:
if not isinstance(raw, Mapping) or not isinstance(raw.get("task_id"), str) or not raw["task_id"]:
raise ProjectionUnavailable("task projection source returned invalid task")
source_status = str(raw.get("status") or "").upper()
if source_status not in _TASKPLANE_DISPLAY_STATUSES:
raise ProjectionUnavailable("task projection source returned unknown TaskPlane status")
tasks.append({
**raw,
"source_status": source_status or None,
"status": _TASKPLANE_DISPLAY_STATUSES[source_status],
})
return tasks
def _task_revisions(tasks: list[Mapping[str, Any]]) -> tuple[tuple[str, Any], ...]:
return tuple(sorted((task["task_id"], task.get("revision")) for task in tasks))
def _event_positions(events: Any) -> tuple[tuple[str, int], ...]:
if not isinstance(events, list):
raise ProjectionUnavailable("task projection source returned invalid events")
positions: list[tuple[str, int]] = []
for event in events:
if not isinstance(event, Mapping) or not isinstance(event.get("event_id"), str):
raise ProjectionUnavailable("task projection source returned invalid event")
sequence = event.get("seq")
if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence < 0:
raise ProjectionUnavailable("task projection event has no durable sequence")
positions.append((event["event_id"], sequence))
return tuple(positions)
def _read_projection(principal: Mapping[str, Any]) -> Mapping[str, Any]:
if _projection_reader is None:
raise ProjectionUnavailable("task projection source is not configured")
source = _projection_reader(principal)
if not isinstance(source, Mapping):
raise ProjectionUnavailable("task projection source returned invalid data")
tasks, events = source.get("tasks"), source.get("events_by_task")
if not isinstance(tasks, list) or not isinstance(events, Mapping):
raise ProjectionUnavailable("task projection source returned incomplete data")
return {"tasks": tasks, "events_by_task": events}
def _principal(request: Any) -> Mapping[str, Any]:
if _principal_resolver is None:
raise ProjectionUnavailable("task projection authentication is not configured")
principal = _principal_resolver(request)
if not isinstance(principal, Mapping):
raise PermissionError("task projection authentication failed")
return principal
def _sse(payload: Mapping[str, Any]):
# The bounded response is deliberately resumable. The browser reconnects
# with the returned cursor; polling remains available when it cannot.
for event in payload["events"]:
yield f"id: {event['cursor']}\ndata: {json.dumps(event, ensure_ascii=False, separators=(',', ':'))}\n\n"
yield ": end-of-batch\n\n"
def _http(call: Callable[[], Any]) -> Any:
try:
return call()
except ProjectionCursorTransportLimit as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=413, detail="TASK_EVENT_CURSOR_TOO_LARGE") from exc
except ProjectionCursorExpired as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=410, detail="EVENT_CURSOR_EXPIRED") from exc
except ProjectionUnavailable as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=503, detail="TASK_PROJECTION_UNAVAILABLE") from exc
except UnauthorizedError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=401, detail="TASK_PROJECTION_UNAUTHORIZED") from exc
except ForbiddenError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=403, detail="TASK_PROJECTION_DENIED") from exc
except PermissionError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=403, detail="TASK_PROJECTION_DENIED") from exc
except ValueError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=400, detail=str(exc)) from exc
def _overview(principal: Mapping[str, Any]) -> dict[str, Any]:
source = _read_projection(principal)
return get_task_overview(source["tasks"], source["events_by_task"])
def get_task_overview(tasks: list[dict[str, Any]] | None = None):
return task_center.summarize(tasks)
def build_router():
@@ -260,37 +23,12 @@ def build_router():
router = APIRouter(prefix="/admin/ai-evolution/tasks", tags=["task-center"])
@router.get("/overview", operation_id="getTaskCenterOverview")
def overview(request: Request):
return _http(lambda: _overview(_principal(request)))
@router.get("/events", operation_id="streamTaskCenterEvents")
def events(request: Request, cursor: str | None = None, after_cursor: str | None = None, limit: int = 100):
payload = _http(lambda: get_task_events(
_effective_cursor(request, cursor, after_cursor), principal=_principal(request), limit=limit,
))
return StreamingResponse(_sse(payload), media_type="text/event-stream")
@router.get("/events/poll", operation_id="pollTaskCenterEvents")
def poll(request: Request, cursor: str | None = None, after_cursor: str | None = None, limit: int = 100):
return _http(lambda: get_task_events(
_effective_cursor(request, cursor, after_cursor), principal=_principal(request), limit=limit,
))
def overview():
return get_task_overview()
return router
router = build_router()
__all__ = [
"router", "get_task_overview", "get_task_events", "set_task_projection_reader",
"set_task_projection_principal_resolver", "configure_task_plane_projection",
"ProjectionUnavailable",
]
def _effective_cursor(request: Any, cursor: str | None, after_cursor: str | None) -> str | None:
header_cursor = request.headers.get("Last-Event-ID") if request is not None else None
supplied = [value for value in (cursor, after_cursor, header_cursor) if value]
if len(set(supplied)) > 1:
raise ValueError("conflicting event cursors")
return supplied[0] if supplied else None
__all__ = ["router", "get_task_overview"]
+49 -406
View File
@@ -1,438 +1,81 @@
"""Read-only Task Center projections built from durable task and event records."""
"""Task Center visualization model."""
from __future__ import annotations
import base64
import json
import zlib
from collections import defaultdict
from typing import Any, Mapping
class ProjectionCursorExpired(ValueError):
"""The requested event cursor no longer names a visible durable event."""
class ProjectionCursorTransportLimit(ValueError):
"""The authorized per-task watermark cannot fit a request/header cursor."""
_MAX_CURSOR_BYTES = 8192
_MAX_CURSOR_PAYLOAD_BYTES = 32768
_INITIAL_INTERACTION_KIND = "initial_request"
_ADDITIONAL_INTERACTION_KINDS = frozenset({"additional_interaction", "follow_up"})
from typing import Any
class TaskDashboardView:
"""Build the Task Center read model without maintaining another state store."""
def build(
self,
tasks: list[dict[str, Any]] | None = None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None = None,
) -> dict[str, Any]:
raw_tasks = [dict(task) for task in tasks or [] if isinstance(task, Mapping)]
by_id = {
task_id: task for task in raw_tasks
if isinstance((task_id := task.get("task_id")), str) and task_id
}
children = self._children_by_parent(by_id)
cards = [
self._task_card(task, by_id, children, events_by_task)
for task in raw_tasks
]
def build(self, tasks: list[dict[str, Any]] | None = None) -> dict[str, Any]:
tasks = tasks or []
return {
"module": "task-center",
"route": "/admin/ai-evolution/tasks",
"title": "AIOS Task Center",
"views": {
"kanban": self._kanban(cards),
"timeline": self._timeline(cards),
"flow": self._flow(cards),
"statistics": self._statistics(cards),
"kanban": self._kanban(tasks),
"timeline": self._timeline(tasks),
"flow": self._flow(tasks),
"statistics": self._statistics(tasks),
},
"tasks": cards,
"tasks": [self._task_card(task) for task in tasks],
}
def event_feed(
self,
tasks: list[dict[str, Any]] | None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
cursor: str | None = None,
*,
limit: int = 100,
) -> dict[str, Any]:
"""Return an authorization-filtered, resumable page of durable events."""
if not 1 <= limit <= 100:
raise ValueError("invalid event page limit")
rows = self._event_rows(tasks, events_by_task)
watermarks = self._decode_cursor(cursor) if cursor else {}
self._validate_watermarks(rows, watermarks)
pending = [
row for row in rows
if row["sequence"] > watermarks.get(row["entity_id"], -1)
]
page = pending[:limit]
next_watermarks = dict(watermarks)
emitted_events: list[dict[str, Any]] = []
for row in page:
next_watermarks[row["entity_id"]] = max(next_watermarks.get(row["entity_id"], -1), row["sequence"])
# ``Last-Event-ID`` holds the cursor from the final SSE frame. It
# must therefore include every preceding entity watermark, not just
# the frame's own task, or a reconnect would replay another task.
emitted_events.append({**row, "cursor": self._encode_cursor(next_watermarks)})
next_cursor = self._encode_cursor(next_watermarks) if next_watermarks else None
def _task_card(self, task: dict[str, Any]) -> dict[str, Any]:
return {
"events": emitted_events,
"cursor": cursor,
"next_cursor": next_cursor,
"has_more": len(pending) > len(page),
}
def snapshot_cursor(
self,
tasks: list[dict[str, Any]] | None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
) -> str | None:
rows = self._event_rows(tasks, events_by_task)
watermarks: dict[str, int] = {}
for row in rows:
watermarks[row["entity_id"]] = max(watermarks.get(row["entity_id"], -1), row["sequence"])
return self._encode_cursor(watermarks) if watermarks else None
def _task_card(
self,
task: dict[str, Any],
by_id: Mapping[str, dict[str, Any]],
children: Mapping[str, list[str]],
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
) -> dict[str, Any]:
task_id = task.get("task_id")
progress = task.get("progress") if isinstance(task.get("progress"), Mapping) else {}
parent_task_id = task.get("parent_task_id")
event_result = self._project_events(
task_id,
events_by_task,
complete=task.get("interaction_history_coverage") == "complete",
)
direct_children = list(children.get(task_id, ())) if isinstance(task_id, str) else []
grandchildren = [grandchild for child in direct_children for grandchild in children.get(child, ())]
authorization = self._authorization(task)
return {
"task_id": task_id,
"title": task.get("title") or task.get("display_name"),
"root_task_id": task.get("root_task_id"),
"created_at": task.get("created_at"),
"updated_at": task.get("updated_at"),
"task_id": task.get("task_id"),
"title": task.get("title"),
"assigned_by": task.get("assigned_by"),
"executor": task.get("executor"),
"status": task.get("status"),
"source_status": task.get("source_status", task.get("status")),
"revision": task.get("revision"),
"progress": progress,
"elapsed_seconds": self._known_nonnegative_int(task.get("elapsed_seconds")),
"remaining_steps": progress.get("remaining_steps", []),
"progress": task.get("progress", {}),
"elapsed_seconds": task.get("elapsed_seconds", 0),
"remaining_steps": task.get("progress", {}).get("remaining_steps", []),
"notification": task.get("notification", {}),
"lineage": {
"parent_task_id": parent_task_id if isinstance(parent_task_id, str) and parent_task_id in by_id else None,
"child_task_ids": direct_children,
"grandchild_task_ids": grandchildren,
},
"responsibility": {
"assigned_by": task.get("assigned_by"),
"executor": task.get("executor"),
"owner_principal_id": task.get("owner_principal_id"),
},
"authorization": authorization,
"actions": self._actions(task, authorization),
"interaction_history": event_result,
"actions": [
"start",
"update_progress",
"block",
"request_approval",
"approve",
"reject",
"retry",
"complete",
"notify",
"close",
],
}
@staticmethod
def _known_nonnegative_int(value: Any) -> int | None:
return value if isinstance(value, int) and not isinstance(value, bool) and value >= 0 else None
@staticmethod
def _children_by_parent(by_id: Mapping[str, dict[str, Any]]) -> dict[str, list[str]]:
result: dict[str, list[str]] = defaultdict(list)
for task_id, task in by_id.items():
parent_id = task.get("parent_task_id")
if isinstance(parent_id, str) and parent_id in by_id:
result[parent_id].append(task_id)
return {parent: sorted(child_ids) for parent, child_ids in result.items()}
def _project_events(
self,
task_id: Any,
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
*,
complete: bool = False,
) -> dict[str, Any]:
if not isinstance(task_id, str) or events_by_task is None or task_id not in events_by_task:
return self._unknown_history()
raw_events = events_by_task[task_id]
if not isinstance(raw_events, list):
return self._unknown_history()
seen: dict[str, dict[str, Any]] = {}
semantic_ids: dict[tuple[str, str], dict[str, Any]] = {}
events: list[dict[str, Any]] = []
conflicts: list[dict[str, str]] = []
for raw in raw_events:
if not isinstance(raw, Mapping) or not isinstance(raw.get("event_id"), str):
conflicts.append({"kind": "invalid_event", "event_id": ""})
continue
event = dict(raw)
event_id = event["event_id"]
previous = seen.get(event_id)
if previous is not None:
if previous != event:
conflicts.append({"kind": "duplicate_event_id_conflict", "event_id": event_id})
continue
seen[event_id] = event
item = self._event_item(event)
if self._malformed_human_interaction(event):
conflicts.append({"kind": "invalid_human_interaction_evidence", "event_id": event_id})
if self._malformed_metric_evidence(event):
conflicts.append({"kind": "invalid_metric_evidence", "event_id": event_id})
semantic_id = item.get("semantic_id")
if semantic_id:
semantic_namespace = "human_interaction" if item["category"] in {"initial_request", "additional_interaction"} else item["category"]
previous_semantic = semantic_ids.get((semantic_namespace, semantic_id))
if previous_semantic is not None:
if self._semantic_content(previous_semantic) != self._semantic_content(item):
conflicts.append({"kind": "duplicate_semantic_id_conflict", "event_id": event_id})
continue
semantic_ids[(semantic_namespace, semantic_id)] = item
events.append(item)
events.sort(key=lambda event: (event.get("created_at") or "", event["event_id"]))
interactions = sum(event["category"] in {"initial_request", "additional_interaction"} for event in events)
is_exact = complete and not conflicts
return {
"history_status": "partial" if not complete else ("conflicted" if conflicts else "complete"),
"interaction_count": interactions if is_exact else None,
"initial_request_count": sum(event["category"] == "initial_request" for event in events) if is_exact else None,
"additional_interaction_count": sum(event["category"] == "additional_interaction" for event in events) if is_exact else None,
"decision_count": sum(event["category"] == "decision" for event in events) if is_exact else None,
"offered_choice_count": sum(event.get("choice_count", 0) for event in events if event["category"] == "offered_choice") if is_exact else None,
"selection_submission_count": sum(event["category"] == "selection_submission" for event in events) if is_exact else None,
"events": events,
"source_conflicts": conflicts,
}
@staticmethod
def _unknown_history() -> dict[str, Any]:
return {
"history_status": "partial",
"interaction_count": None,
"initial_request_count": None,
"additional_interaction_count": None,
"decision_count": None,
"offered_choice_count": None,
"selection_submission_count": None,
"events": [],
"source_conflicts": [],
}
@staticmethod
def _semantic_content(event: Mapping[str, Any]) -> dict[str, Any]:
return {
key: value for key, value in event.items()
if key not in {"event_id", "sequence", "revision", "created_at"}
}
@staticmethod
def _malformed_human_interaction(event: Mapping[str, Any]) -> bool:
data = event.get("data") if isinstance(event.get("data"), Mapping) else {}
source = str(data.get("source") or event.get("source") or "").upper()
if source != "HUMAN_INTERACTION":
return False
interaction_id = data.get("interaction_id")
kind = data.get("kind")
return not (
isinstance(interaction_id, str) and interaction_id
and isinstance(kind, str)
and (kind == _INITIAL_INTERACTION_KIND or kind in _ADDITIONAL_INTERACTION_KINDS)
)
@staticmethod
def _malformed_metric_evidence(event: Mapping[str, Any]) -> bool:
data = event.get("data") if isinstance(event.get("data"), Mapping) else {}
event_type = str(event.get("type") or "").upper()
if event_type in {"DECISION", "TASK_DECISION", "APPROVAL_DECIDED"}:
return not isinstance(data.get("decision_id"), str) or not data["decision_id"]
if event_type in {"CHOICE_OFFERED", "OPTIONS_OFFERED"}:
choice_count = data.get("choice_count")
return not (
isinstance(data.get("choice_offer_id"), str) and data["choice_offer_id"]
and isinstance(choice_count, int) and not isinstance(choice_count, bool) and choice_count >= 0
)
if event_type in {"CHOICE_SELECTED", "SELECTION_SUBMITTED"}:
return not isinstance(data.get("selection_submission_id"), str) or not data["selection_submission_id"]
return False
@staticmethod
def _event_item(event: Mapping[str, Any]) -> dict[str, Any]:
event_type = str(event.get("type") or "UNKNOWN")
data = event.get("data") if isinstance(event.get("data"), Mapping) else {}
normalized = event_type.upper()
source = str(data.get("source") or event.get("source") or "").upper()
interaction_id = data.get("interaction_id")
decision_id = data.get("decision_id")
selection_id = data.get("selection_submission_id")
kind = data.get("kind")
if source == "HUMAN_INTERACTION" and isinstance(interaction_id, str) and interaction_id and kind == _INITIAL_INTERACTION_KIND:
category = "initial_request"
semantic_id = interaction_id
elif source == "HUMAN_INTERACTION" and isinstance(interaction_id, str) and interaction_id and isinstance(kind, str) and kind in _ADDITIONAL_INTERACTION_KINDS:
category = "additional_interaction"
semantic_id = interaction_id
elif normalized in {"DECISION", "TASK_DECISION", "APPROVAL_DECIDED"} and isinstance(decision_id, str) and decision_id:
category = "decision"
semantic_id = decision_id
elif normalized in {"CHOICE_OFFERED", "OPTIONS_OFFERED"} and isinstance(data.get("choice_offer_id"), str) and isinstance(data.get("choice_count"), int) and not isinstance(data["choice_count"], bool) and data["choice_count"] >= 0:
category = "offered_choice"
semantic_id = data["choice_offer_id"]
elif normalized in {"CHOICE_SELECTED", "SELECTION_SUBMITTED"} and isinstance(selection_id, str) and selection_id:
category = "selection_submission"
semantic_id = selection_id
else:
category = "other"
semantic_id = None
return {
"event_id": event["event_id"],
"sequence": event.get("seq"),
"revision": event.get("revision", event.get("seq")),
"type": event_type,
"category": category,
"semantic_id": semantic_id,
"subject": event.get("subject"),
"created_at": event.get("created_at"),
"decision": data.get("decision") if category == "decision" else None,
"choice_id": data.get("choice_id") if category in {"offered_choice", "selection_submission"} else None,
"choice_count": data.get("choice_count", 0) if category == "offered_choice" else 0,
}
@staticmethod
def _authorization(task: Mapping[str, Any]) -> dict[str, Any]:
raw = task.get("authorization")
raw = raw if isinstance(raw, Mapping) else {}
capabilities = raw.get("capabilities", task.get("capabilities", []))
return {
"owner_gate_required": bool(task.get("owner_gate_required", raw.get("owner_gate_required", False))),
"owner_gate_status": task.get("owner_gate_status", raw.get("owner_gate_status")),
"decision": raw.get("decision"),
"capabilities": list(capabilities) if isinstance(capabilities, (list, tuple, set, frozenset)) else [],
}
@staticmethod
def _actions(task: Mapping[str, Any], authorization: Mapping[str, Any]) -> list[str]:
explicit = task.get("authorized_actions")
if not isinstance(explicit, (list, tuple, set, frozenset)):
return []
allowed = [action for action in explicit if isinstance(action, str)]
capabilities = set(authorization.get("capabilities", []))
decision = authorization.get("decision")
if "approve" in allowed and not (
{"approve", "decision.approve", "task.approve"} & capabilities
and isinstance(decision, Mapping)
and decision.get("status") in {"pending", "eligible"}
):
allowed.remove("approve")
return allowed
@staticmethod
def _kanban(tasks: list[dict[str, Any]]) -> dict[str, list[str]]:
def _kanban(self, tasks):
columns = {"pending": [], "running": [], "blocked": [], "waiting_approval": [], "completed": []}
for task in tasks:
columns.setdefault(task.get("status", "pending"), []).append(task.get("task_id"))
return columns
@staticmethod
def _timeline(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [{"task_id": task.get("task_id"), "status": task.get("status"), "revision": task.get("revision")} for task in tasks]
def _timeline(self, tasks):
return [
{
"task_id": task.get("task_id"),
"updated_at": task.get("updated_at"),
"status": task.get("status"),
}
for task in tasks
]
@staticmethod
def _flow(tasks: list[dict[str, Any]]) -> dict[str, Any]:
return {"nodes": ["created", "assigned", "running", "blocked", "approval", "completed", "closed"], "task_count": len(tasks)}
@staticmethod
def _statistics(tasks: list[dict[str, Any]]) -> dict[str, int]:
def _flow(self, tasks):
return {
"total": len(tasks),
"running": sum(task.get("status") == "running" for task in tasks),
"blocked": sum(task.get("status") == "blocked" for task in tasks),
"waiting_approval": sum(task.get("status") == "waiting_approval" for task in tasks),
"completed": sum(task.get("status") == "completed" for task in tasks),
"nodes": ["created", "assigned", "running", "blocked", "approval", "completed", "closed"],
"task_count": len(tasks),
}
def _event_rows(self, tasks, events_by_task) -> list[dict[str, Any]]:
visible_ids = {task.get("task_id") for task in tasks or [] if isinstance(task, Mapping)}
result: list[dict[str, Any]] = []
for task_id, events in (events_by_task or {}).items():
if task_id not in visible_ids or not isinstance(events, list):
continue
# The stream carries every durable append. Semantic deduplication
# belongs only to human-history metrics: a later conflicting event
# must reach the client so its current task card becomes conflicted.
for event in events:
if not isinstance(event, Mapping) or not isinstance(event.get("event_id"), str):
continue
sequence = event.get("seq")
if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence < 0:
continue
result.append({"event_id": event["event_id"], "entity_id": task_id, "sequence": sequence, "revision": event.get("revision"), "type": event.get("type", "UNKNOWN"), "created_at": event.get("created_at")})
# A per-task append sequence is the cursor authority. Timestamp order
# can be displayed by clients, but cannot drive paging: a backdated
# seq=6 must never be emitted before seq=5 for the same task.
result.sort(key=lambda event: (event["sequence"], event["entity_id"], event["event_id"]))
return result
@staticmethod
def _encode_cursor(watermarks: Mapping[str, int]) -> str:
raw = json.dumps({"v": 2, "after": dict(watermarks)}, sort_keys=True, separators=(",", ":")).encode("utf-8")
if len(raw) > _MAX_CURSOR_PAYLOAD_BYTES:
raise ProjectionCursorTransportLimit("event cursor exceeds transport limit")
token = base64.urlsafe_b64encode(zlib.compress(raw, level=9)).decode("ascii").rstrip("=")
if len(token) > _MAX_CURSOR_BYTES:
raise ProjectionCursorTransportLimit("event cursor exceeds transport limit")
return token
@staticmethod
def _decode_cursor(cursor: str) -> dict[str, int]:
if not isinstance(cursor, str) or not cursor or len(cursor) > _MAX_CURSOR_BYTES:
raise ProjectionCursorExpired("event cursor expired")
try:
padded = cursor + "=" * (-len(cursor) % 4)
encoded = base64.urlsafe_b64decode(padded.encode("ascii"))
decompressor = zlib.decompressobj()
raw = decompressor.decompress(encoded, _MAX_CURSOR_PAYLOAD_BYTES + 1)
if len(raw) > _MAX_CURSOR_PAYLOAD_BYTES:
raise ValueError("cursor payload invalid")
raw += decompressor.flush(_MAX_CURSOR_PAYLOAD_BYTES + 1 - len(raw))
if len(raw) > _MAX_CURSOR_PAYLOAD_BYTES or decompressor.unconsumed_tail or not decompressor.eof:
raise ValueError("cursor payload invalid")
value = json.loads(raw)
except (UnicodeEncodeError, ValueError, zlib.error, json.JSONDecodeError) as exc:
raise ProjectionCursorExpired("event cursor expired") from exc
after = value.get("after") if isinstance(value, Mapping) and value.get("v") == 2 else None
if not isinstance(after, Mapping) or not after or any(
not isinstance(task_id, str) or not task_id or not isinstance(sequence, int)
or isinstance(sequence, bool) or sequence < 0
for task_id, sequence in after.items()
):
raise ProjectionCursorExpired("event cursor expired")
return dict(after)
@staticmethod
def _validate_watermarks(rows: list[dict[str, Any]], watermarks: Mapping[str, int]) -> None:
positions_by_task: dict[str, set[int]] = defaultdict(set)
for row in rows:
positions_by_task[row["entity_id"]].add(row["sequence"])
# A retained sequence floor is not otherwise exposed by TaskPlane.
# Requiring the acknowledged position itself prevents silently skipping
# purged seqs 4..9 when a stale cursor still says seq=3.
if any(sequence not in positions_by_task.get(task_id, set()) for task_id, sequence in watermarks.items()):
raise ProjectionCursorExpired("event cursor expired")
def _statistics(self, tasks):
return {
"total": len(tasks),
"running": len([t for t in tasks if t.get("status") == "running"]),
"blocked": len([t for t in tasks if t.get("status") == "blocked"]),
"waiting_approval": len([t for t in tasks if t.get("status") == "waiting_approval"]),
"completed": len([t for t in tasks if t.get("status") == "completed"]),
}
__all__ = ["ProjectionCursorExpired", "ProjectionCursorTransportLimit", "TaskDashboardView"]
__all__ = ["TaskDashboardView"]
+41 -452
View File
@@ -8,7 +8,6 @@ import time
import uuid
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from datetime import datetime, timezone
from typing import Any, Callable
from .delegation import DelegationEngine
@@ -24,8 +23,6 @@ from .errors import (
from .evidence import EvidenceService
from .journal import TaskJournal
from .lease import LeaseManager
from .lineage_store import TaskLineagePolicy, TaskPlaneLineageCoordinator
from .mission import MissionAuthority
from .models import (
EvidenceEnvelope,
ResultEnvelope,
@@ -35,12 +32,7 @@ from .models import (
contains_secret_text,
validate_identifier,
)
from .policy import IdentityVerifier, PolicyEngine
from .root_action_consumer import (
RootActionConsumer,
RootActionExecutor,
RootActionRunResult,
)
from .policy import PolicyEngine
from .store import TaskPlaneStore
from .worker_registry import WorkerRegistry
@@ -61,19 +53,9 @@ def _bounded_int(value: Any, name: str, minimum: int, maximum: int) -> int:
class TaskPlaneService:
def __init__(
self,
store: TaskPlaneStore,
policy: PolicyEngine | None = None,
*,
lineage_policy: TaskLineagePolicy | None = None,
mission_authority: MissionAuthority | None = None,
root_action_executor: RootActionExecutor | None = None,
):
def __init__(self, store: TaskPlaneStore, policy: PolicyEngine | None = None):
self.store = store
self.c6_runtime = None
self.policy = policy or PolicyEngine(
identity_verifier=IdentityVerifier({"anon"}),
execution_enabled=False,
identity_capabilities={
"anon": {"task.creator", "task.read", "task.audit"}
@@ -84,260 +66,30 @@ class TaskPlaneService:
self.workers = WorkerRegistry(store)
self.evidence = EvidenceService(store)
self.delegation = DelegationEngine(store)
if mission_authority is not None and mission_authority.store is not store:
raise ValueError("mission authority must use the TaskPlane store")
self.missions = mission_authority
effective_lineage_policy = lineage_policy
if mission_authority is not None:
effective_lineage_policy = (
lineage_policy
or TaskLineagePolicy(
principal_bindings=(), acceptance_evaluators={}
)
).with_acceptance_evaluator_resolver(
mission_authority.resolve_acceptance_evaluator
)
self.lineage = (
TaskPlaneLineageCoordinator(store, effective_lineage_policy)
if effective_lineage_policy is not None
else None
)
self.root_actions = (
RootActionConsumer(
store,
plan_builder=lambda review: mission_authority.build_root_action_plan(
review=review, lineage=self.lineage
),
rereviewer=lambda plan, receipt: mission_authority.rereview_root_action(
plan=plan,
execution_receipt=receipt,
lineage=self.lineage,
),
executor=root_action_executor,
)
if mission_authority is not None and self.lineage is not None
else None
)
self.store.probe_attestation_verifier = self.policy.probe_attestation_verifier
self.store.configure_evidence_attestation_verifier(
self.policy.evidence_attestation_verifier
)
def consume_one_root_action(
self,
*,
consumer_id: str,
now: datetime | None = None,
lease_seconds: int = 60,
) -> RootActionRunResult:
if self.root_actions is None:
raise ForbiddenError("root action consumer is not configured")
return self.root_actions.run_once(
consumer_id=consumer_id,
now=now or datetime.now(tz=timezone.utc),
lease_seconds=lease_seconds,
)
def create_mission(
self,
body: dict[str, Any],
idempotency_key: str | None,
*,
actor_identity: str | None,
) -> dict[str, Any]:
if self.missions is None or self.lineage is None:
raise ForbiddenError("mission authority is not configured")
if actor_identity is None:
raise ForbiddenError("mission creation requires authenticated identity")
if set(body) != {"goal_slug", "original_acceptance"}:
raise ValidationError(
"mission body requires only goal_slug and original_acceptance"
)
self.policy.assert_allowed("create", actor_identity)
mission = self.missions.create_mission(
goal_slug=body["goal_slug"],
original_acceptance=body["original_acceptance"],
idempotency_key=idempotency_key,
authenticated_identity=actor_identity,
lineage=self.lineage,
)
return {
**mission.to_dict(),
"root_task": self.store.get_task(mission.root_task_id).to_dict(),
}
def get_mission(
self, mission_id: str, *, actor_identity: str | None
) -> dict[str, Any]:
if self.missions is None:
raise ForbiddenError("mission authority is not configured")
if actor_identity is None:
raise ForbiddenError("mission read requires authenticated identity")
self.policy.assert_allowed("read", actor_identity)
mission = self.missions.get_mission(
mission_id, authenticated_identity=actor_identity
)
return {
**mission.to_dict(),
"root_task": self.store.get_task(mission.root_task_id).to_dict(),
}
def delegate_mission(
self,
parent_task_id: str,
body: dict[str, Any],
idempotency_key: str | None,
*,
actor_identity: str | None,
) -> dict[str, Any]:
"""Admit one server-identified child under an owned canonical Mission."""
if self.missions is None or self.lineage is None:
raise ForbiddenError("mission authority is not configured")
if actor_identity is None:
raise ForbiddenError("mission delegation requires authenticated identity")
# Mission delegation is an admission operation. It deliberately does
# not enable the worker execution actions guarded by execution_enabled.
self.policy.assert_allowed("create", actor_identity)
granted = set(
self.policy.identity_capabilities.get(
actor_identity,
self.policy.identity_capabilities.get("*", set()),
)
)
if "task.delegate" not in granted:
raise ForbiddenError("action delegate not permitted")
child = self.missions.delegate_child(
parent_task_id=parent_task_id,
body=body,
idempotency_key=idempotency_key,
authenticated_identity=actor_identity,
lineage=self.lineage,
delegation=self.delegation,
)
return child.to_dict()
def attach_legacy_attachment(
self,
mission_id: str,
body: dict[str, Any],
idempotency_key: str | None,
*,
actor_identity: str | None,
) -> dict[str, Any]:
if self.missions is None:
raise ForbiddenError("mission authority is not configured")
if actor_identity is None:
raise ForbiddenError("legacy attachment requires authenticated identity")
self.policy.assert_allowed("create", actor_identity)
granted = set(
self.policy.identity_capabilities.get(
actor_identity, self.policy.identity_capabilities.get("*", set())
)
)
if "task.delegate" not in granted:
raise ForbiddenError("action delegate not permitted")
return self.missions.attach_legacy(
mission_id=mission_id,
body=body,
idempotency_key=idempotency_key,
authenticated_identity=actor_identity,
)
def get_legacy_attachment(
self,
mission_id: str,
attachment_id: str,
*,
actor_identity: str | None,
) -> dict[str, Any]:
if self.missions is None:
raise ForbiddenError("mission authority is not configured")
if actor_identity is None:
raise ForbiddenError("legacy attachment read requires authenticated identity")
self.policy.assert_allowed("read", actor_identity)
return self.missions.get_legacy_attachment(
mission_id=mission_id,
attachment_id=attachment_id,
authenticated_identity=actor_identity,
)
def create_task(
self,
body: dict[str, Any],
idempotency_key: str | None = None,
*,
actor_identity: str | None = None,
) -> dict[str, Any]:
def create_task(self, body: dict[str, Any], idempotency_key: str | None = None) -> dict[str, Any]:
if body.get("parent_task_id"):
raise ForbiddenError("child tasks must use the delegate endpoint")
issuer = body.get("issuer", {})
if actor_identity is not None:
if issuer and (
not isinstance(issuer, dict) or issuer.get("id") != actor_identity
):
raise ForbiddenError("issuer identity mismatch")
body = dict(body)
body["issuer"] = {"type": "authenticated", "id": actor_identity}
actor = actor_identity or (
issuer.get("id", "anon") if isinstance(issuer, dict) else "anon"
)
actor = issuer.get("id", "anon") if isinstance(issuer, dict) else "anon"
self.policy.assert_allowed("create", actor)
try:
plan = TaskPlan.from_dict(body)
except (TypeError, ValueError) as exc:
raise ValidationError("invalid task plan") from exc
if self.lineage is not None:
if actor_identity is None:
raise ForbiddenError("lineage admission requires authenticated identity")
task = self.lineage.create_root(
plan=plan,
authenticated_identity=actor_identity,
request_digest=self.lineage._request_digest(body),
idempotency_key=idempotency_key,
)
else:
task = self.store.create_task_with_event(
plan, idempotency_key=idempotency_key
)
task = self.store.create_task_with_event(plan, idempotency_key=idempotency_key)
return task.to_dict()
def _can_read_mission_task(self, task, actor: str | None) -> bool:
# Every service sharing the store must honor persisted Mission ownership,
# including a legacy launcher that has no Mission authority configured.
if self.missions is not None:
return self.missions.can_read_task(
task, authenticated_identity=actor or ""
)
with self.store._lock:
return self.store.conn.execute(
"SELECT 1 FROM task_missions WHERE root_task_id=?",
(task.root_task_id,),
).fetchone() is None
def _assert_mission_read(self, task, actor: str | None) -> None:
if not self._can_read_mission_task(task, actor):
raise ForbiddenError("mission task requires its configured owner authority")
def list_tasks(self, actor: str | None = None) -> list[dict[str, Any]]:
if actor is not None:
self.policy.assert_allowed("read", actor)
tasks = self.store.list_tasks()
tasks = [task for task in tasks if self._can_read_mission_task(task, actor)]
return [task.to_dict(include_task=False) for task in tasks]
return [task.to_dict(include_task=False) for task in self.store.list_tasks()]
def get_task(self, task_id: str, actor: str | None = None) -> dict[str, Any]:
task = self.store.get_task(task_id)
self._assert_mission_read(task, actor)
if actor is not None:
self.policy.assert_allowed("read", actor, task=task)
if self.missions is not None and self.missions.manages_root(
task.root_task_id
):
self.missions.assert_task_read(
task, authenticated_identity=actor
)
return task.to_dict()
self.policy.assert_allowed("read", actor, task=self.store.get_task(task_id))
return self.store.get_task(task_id).to_dict()
def get_worker(self, worker_id: str, actor: str | None = None) -> dict[str, Any]:
if actor is not None:
@@ -417,11 +169,6 @@ class TaskPlaneService:
lease_seconds = _bounded_int(
body.get("lease_seconds", 30), "lease_seconds", 1, 3600
)
if self.c6_runtime is not None and self.c6_runtime.bindings.enabled:
return self.c6_runtime.claim_task(
task_id=task_id, worker_id=worker_id, actor_identity=actor,
lease_seconds=lease_seconds,
).to_dict()
claim = self.lease.claim(
task_id=task_id,
worker_id=worker_id,
@@ -446,10 +193,6 @@ class TaskPlaneService:
lease_seconds = _bounded_int(
body.get("lease_seconds", 30), "lease_seconds", 1, 3600
)
if self.c6_runtime is not None and self.c6_runtime.bindings.enabled:
return self.c6_runtime.renew_claim(task_id=task_id, claim_id=claim_id,
actor_identity=actor, receipt=body.get("claim_receipt"),
lease_seconds=lease_seconds)
claim = self.lease.heartbeat(task_id, claim_id, lease_seconds)
return {"claim_id": claim_id, "lease_until": claim.lease_until}
@@ -552,79 +295,29 @@ class TaskPlaneService:
previous = self.store._last_evidence_hash(task_id)
evidence_entries = []
for evidence in evidences:
if "schema_version" in evidence:
try:
envelope = EvidenceEnvelope.from_dict(evidence)
except ValueError as exc:
raise ValidationError("invalid evidence envelope") from exc
else:
envelope = EvidenceEnvelope.create(
task_id=task_id,
claim_id=claim_id,
kind=str(
evidence.get("kind") or evidence.get("type") or "generic"
),
worker_identity=actor,
payload=evidence.get("payload", evidence),
previous_evidence_hash=previous,
evidence_id=evidence.get("evidence_id"),
created_at=evidence.get("created_at"),
expires_at=evidence.get("expires_at"),
signature=evidence.get("signature"),
attestation_ref=evidence.get("attestation_ref"),
signature_algorithm=evidence.get("signature_algorithm"),
signing_key_id=evidence.get("signing_key_id"),
verifier_identity=evidence.get("verifier_identity"),
authority_domain=evidence.get("authority_domain"),
sequence=evidence.get("sequence"),
)
envelope = EvidenceEnvelope.create(
task_id=task_id,
claim_id=claim_id,
kind=str(evidence.get("kind") or evidence.get("type") or "generic"),
worker_identity=actor,
payload=evidence,
previous_evidence_hash=previous,
signature=evidence.get("signature"),
attestation_ref=evidence.get("attestation_ref"),
)
evidence_entries.append(envelope)
previous = envelope.evidence_digest
final_status = (
self.store.complete_claim_with_evidence_and_event(
task_id,
claim_id,
status
if status
in {"COMPLETED", "FAILED_FINAL", "FAILED_RECOVERABLE", "CANCELLED"}
else "COMPLETED"
else "COMPLETED",
result.to_dict(),
evidence_entries,
)
lineage_metadata = task.task_plan.metadata.get("aios_task_lineage")
is_lineage_root = bool(
self.lineage is not None
and isinstance(lineage_metadata, dict)
and lineage_metadata.get("relation_type") == "ROOT"
)
if is_lineage_root and final_status == "COMPLETED":
raise ForbiddenError("lineage root completion requires root acceptance review")
review_outcome = None
with self.store._transaction():
self.store.complete_claim_with_evidence_and_event(
task_id,
claim_id,
final_status,
result.to_dict(),
evidence_entries,
)
is_lineage_child = bool(
self.lineage is not None
and isinstance(lineage_metadata, dict)
and lineage_metadata.get("relation_type") != "ROOT"
)
if is_lineage_child and final_status in {"COMPLETED", "FAILED_FINAL", "CANCELLED"}:
review_outcome = self.lineage.review_proven_terminal_child(
child_task_id=task_id,
claim_id=str(claim_id),
result_digest=str(result.result_digest),
evidence_refs=[entry.evidence_id for entry in evidence_entries],
)
response = result.to_dict()
if review_outcome is not None:
response["root_review"] = {
"review_id": review_outcome.review.review_id,
"root_task_id": review_outcome.review.root_task_id,
"root_complete": review_outcome.review.root_complete,
"next_legal_action": review_outcome.review.next_legal_action.value,
"dispatch_status": review_outcome.dispatch.status,
}
return response
return result.to_dict()
def fail(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
claim_id = body.get("claim_id")
@@ -653,38 +346,14 @@ class TaskPlaneService:
)
return updated.to_dict()
def delegate(
self,
task_id: str,
body: dict[str, Any],
idempotency_key: str | None = None,
) -> dict[str, Any]:
def delegate(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
actor = body.get("actor_identity") or "owner"
parent = self.store.get_task(task_id)
mission_managed = bool(
self.lineage is not None
and self.missions is not None
and self.missions.manages_root(parent.root_task_id)
)
if mission_managed:
plan_body = body.get("task") if "task" in body else {
key: value
for key, value in body.items()
if key != "actor_identity"
}
return self.delegate_mission(
task_id,
plan_body,
idempotency_key,
actor_identity=str(actor),
)
self.policy.assert_allowed("delegate", actor)
parent = self.store.get_task(task_id)
self.policy.assert_allowed("delegate", actor, task=parent)
if "task" in body:
plan_body = body["task"]
try:
child_plan = TaskPlan.from_dict(plan_body)
child_plan = TaskPlan.from_dict(body["task"])
except (TypeError, ValueError) as exc:
raise ValidationError("invalid child task plan") from exc
else:
@@ -695,67 +364,25 @@ class TaskPlaneService:
except (TypeError, ValueError) as exc:
raise ValidationError("invalid child task plan") from exc
child_plan.parent_task_id = task_id
if self.lineage is not None:
if not isinstance(plan_body, dict):
raise ValidationError("invalid child task plan")
if "task_id" not in plan_body:
raise ValidationError("lineage delegation requires a stable task_id")
plan_body = dict(plan_body)
issuer = plan_body.get("issuer")
if issuer and (
not isinstance(issuer, dict) or issuer.get("id") != actor
):
raise ForbiddenError("child issuer identity mismatch")
plan_body["issuer"] = {"type": "authenticated", "id": actor}
child_plan.issuer = dict(plan_body["issuer"])
child = self.lineage.delegate_child(
parent_task_id=task_id,
plan=child_plan,
authenticated_identity=str(actor),
request_digest=self.lineage._request_digest(plan_body),
delegation=self.delegation,
)
else:
child = self.delegation.delegate(task_id, child_plan, caller=actor)
child = self.delegation.delegate(task_id, child_plan, caller=actor)
return child.to_dict()
def get_events(self, task_id: str, actor: str | None = None) -> list[dict[str, Any]]:
task = self.store.get_task(task_id)
self._assert_mission_read(task, actor)
if actor is not None:
self.policy.assert_allowed("audit", actor, task=task)
if self.missions is not None and self.missions.manages_root(
task.root_task_id
):
self.missions.assert_task_read(
task, authenticated_identity=actor
)
self.policy.assert_allowed("audit", actor, task=self.store.get_task(task_id))
self.store.get_task(task_id)
return self.store.list_events(task_id)
def get_evidence(self, task_id: str, actor: str | None = None) -> list[dict[str, Any]]:
task = self.store.get_task(task_id)
self._assert_mission_read(task, actor)
if actor is not None:
self.policy.assert_allowed("audit", actor, task=task)
if self.missions is not None and self.missions.manages_root(
task.root_task_id
):
self.missions.assert_task_read(
task, authenticated_identity=actor
)
self.policy.assert_allowed("audit", actor, task=self.store.get_task(task_id))
self.store.get_task(task_id)
return self.evidence.list(task_id)
def get_children(self, task_id: str, actor: str | None = None) -> list[str]:
task = self.store.get_task(task_id)
self._assert_mission_read(task, actor)
if actor is not None:
self.policy.assert_allowed("read", actor, task=task)
if self.missions is not None and self.missions.manages_root(
task.root_task_id
):
self.missions.assert_task_read(
task, authenticated_identity=actor
)
self.policy.assert_allowed("read", actor, task=self.store.get_task(task_id))
self.store.get_task(task_id)
return self.store.list_children(task_id)
@@ -847,10 +474,6 @@ class TaskPlaneRequestHandler(BaseHTTPRequestHandler):
if len(parts) == 1 and parts[0] == "tasks":
return 200, self.service.list_tasks(identity)
if len(parts) == 2 and parts[0] == "missions":
return 200, self.service.get_mission(
parts[1], actor_identity=identity
)
if len(parts) == 2 and parts[0] == "tasks":
task_id = parts[1]
return 200, self.service.get_task(task_id, identity)
@@ -876,13 +499,6 @@ class TaskPlaneRequestHandler(BaseHTTPRequestHandler):
parts = [p for p in path.split("/") if p]
headers = {k.lower(): v for k, v in self.headers.items()}
if len(parts) == 1 and parts[0] == "missions":
return 201, self.service.create_mission(
body or {},
headers.get("idempotency-key"),
actor_identity=identity,
)
if len(parts) == 1 and parts[0] == "tasks":
issuer = body.get("issuer")
if issuer and (
@@ -890,11 +506,7 @@ class TaskPlaneRequestHandler(BaseHTTPRequestHandler):
):
raise ForbiddenError("issuer identity mismatch")
body["issuer"] = {"type": "authenticated", "id": identity}
return 201, self.service.create_task(
body or {},
headers.get("idempotency-key"),
actor_identity=identity,
)
return 201, self.service.create_task(body or {}, headers.get("idempotency-key"))
if body.get("actor_identity") not in {None, identity}:
raise ForbiddenError("actor identity mismatch")
body["actor_identity"] = identity
@@ -911,9 +523,7 @@ class TaskPlaneRequestHandler(BaseHTTPRequestHandler):
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "cancel":
return 200, self.service.cancel(parts[1], body or {})
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "delegate":
return 200, self.service.delegate(
parts[1], body or {}, headers.get("idempotency-key")
)
return 200, self.service.delegate(parts[1], body or {})
if len(parts) == 2 and parts[0] == "workers" and parts[1] == "register":
if (
@@ -940,29 +550,8 @@ def run_server(
*,
authenticator: Callable[[str], str | None] | None = None,
):
# A separate handler class prevents one server/tenant from overwriting
# another concurrently running server's service or authenticator.
handler = type("BoundTaskPlaneRequestHandler", (TaskPlaneRequestHandler,), {
"service": service, "authenticator": authenticator,
})
handler = TaskPlaneRequestHandler
handler.service = service
handler.authenticator = authenticator
server = ThreadingHTTPServer((host, port), handler)
runtime = service.c6_runtime
stop = __import__("threading").Event()
runner = None
if runtime is not None and runtime.bindings.enabled:
import threading
def run_ticks():
while not stop.is_set():
runtime.tick()
stop.wait(1.0)
runner = threading.Thread(target=run_ticks, name="c6-host-tick", daemon=True)
runner.start()
try:
server.serve_forever()
finally:
stop.set()
if runtime is not None:
runtime.close()
if runner is not None:
runner.join(timeout=2.0)
server.server_close()
server.serve_forever()
@@ -1,666 +0,0 @@
"""C6 ARO Phase A: bounded read-only audit evaluation and immutable reports."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
import hashlib
import json
import math
import os
from pathlib import Path
import re
import tempfile
from typing import Literal, Protocol
from .operation_view import DomainStamp, validate_digest
class Dimension(str, Enum):
FUNCTION = "function"
PERFORMANCE = "performance"
EXCEPTION = "exception"
USAGE = "usage"
CODE_DEFECT = "code_defect"
HISTORY = "history"
DEADLOCK = "deadlock"
LONG_RUNNING = "long_running"
_DIMENSIONS = tuple(Dimension)
_SAFE_REF_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,255}$")
_METRICS: dict[Dimension, frozenset[str]] = {
Dimension.FUNCTION: frozenset({"required_checks", "passed_checks"}),
Dimension.PERFORMANCE: frozenset({"duration_p95_ms"}),
Dimension.EXCEPTION: frozenset({"unhandled_exception_count"}),
Dimension.USAGE: frozenset({"operational_prompt_count", "manual_continue_count"}),
Dimension.CODE_DEFECT: frozenset({"validated_open_defect_count"}),
Dimension.HISTORY: frozenset({"same_fingerprint_failure_count"}),
Dimension.DEADLOCK: frozenset({"wait_cycle_count", "oldest_no_progress_seconds"}),
Dimension.LONG_RUNNING: frozenset({"oldest_nonterminal_age_seconds"}),
}
_COUNT_METRICS = frozenset(
{
"required_checks",
"passed_checks",
"unhandled_exception_count",
"operational_prompt_count",
"manual_continue_count",
"validated_open_defect_count",
"same_fingerprint_failure_count",
"wait_cycle_count",
}
)
_THRESHOLDS = frozenset(
{
"max_duration_p95_ms",
"repeated_failure_threshold",
"deadlock_seconds",
"max_nonterminal_age_seconds",
}
)
def _text(value: object, name: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{name} must be a canonical non-empty string")
return value
def _safe_ref(value: object) -> str:
ref = _text(value, "evidence_ref")
lowered = ref.casefold()
if (
_SAFE_REF_RE.fullmatch(ref) is None
or ".." in ref
or ref.startswith(("/", "\\"))
or "://" in ref
or any(
marker in lowered
for marker in (
"token=",
"secret=",
"password=",
"cookie=",
"authorization=",
"private_key=",
"bearer ",
)
)
):
raise ValueError("evidence_ref is not an allowed opaque reference")
return ref
def _aware(value: object, name: str) -> datetime:
if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
raise ValueError(f"{name} must be timezone-aware")
return value
def _number(value: object, name: str, *, non_negative: bool = True) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"{name} must be numeric")
result = float(value)
if not math.isfinite(result) or (non_negative and result < 0):
raise ValueError(f"{name} must be finite and non-negative")
return result
@dataclass(frozen=True)
class Sample:
metric: str
value: float
observed_at: datetime
evidence_ref: str
def __post_init__(self) -> None:
_text(self.metric, "metric")
_number(self.value, "sample value")
_aware(self.observed_at, "observed_at")
_safe_ref(self.evidence_ref)
@dataclass(frozen=True)
class DimensionInput:
dimension: Dimension
samples: tuple[Sample, ...]
stamp: DomainStamp
captured_at: datetime
expires_at: datetime
def __post_init__(self) -> None:
if not isinstance(self.dimension, Dimension):
raise ValueError("dimension must be Dimension")
if not isinstance(self.samples, tuple):
raise ValueError("samples must be a tuple")
if any(not isinstance(sample, Sample) for sample in self.samples):
raise ValueError("samples must contain Sample values")
if not isinstance(self.stamp, DomainStamp):
raise ValueError("stamp must be DomainStamp")
captured = _aware(self.captured_at, "captured_at")
expires = _aware(self.expires_at, "expires_at")
if expires <= captured:
raise ValueError("expires_at must be after captured_at")
if any(sample.observed_at > captured for sample in self.samples):
raise ValueError("sample cannot be observed after capture")
def _canonical_datetime(value: datetime) -> str:
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
def _snapshot_payload(
*,
window_start: datetime,
window_end: datetime,
dimensions: tuple[DimensionInput, ...],
rules_revision: str,
) -> dict[str, object]:
return {
"window_start": _canonical_datetime(window_start),
"window_end": _canonical_datetime(window_end),
"rules_revision": rules_revision,
"dimensions": [
{
"dimension": item.dimension.value,
"stamp": {
"domain": item.stamp.domain,
"object_id": item.stamp.object_id,
"generation": item.stamp.generation,
"revision": item.stamp.revision,
"event_id": item.stamp.event_id,
},
"captured_at": _canonical_datetime(item.captured_at),
"expires_at": _canonical_datetime(item.expires_at),
"samples": [
{
"metric": sample.metric,
"value": sample.value,
"observed_at": _canonical_datetime(sample.observed_at),
"evidence_ref": sample.evidence_ref,
}
for sample in sorted(
item.samples,
key=lambda value: (
value.metric,
value.observed_at,
value.evidence_ref,
),
)
],
}
for item in sorted(
dimensions,
key=lambda value: (
value.dimension.value,
value.stamp.domain,
value.stamp.object_id,
value.stamp.generation,
value.stamp.revision,
value.stamp.event_id,
),
)
],
}
def _digest(value: Mapping[str, object]) -> str:
encoded = json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
@dataclass(frozen=True)
class AuditSnapshot:
snapshot_id: str
window_start: datetime
window_end: datetime
dimensions: tuple[DimensionInput, ...]
rules_revision: str
def __post_init__(self) -> None:
validate_digest(self.snapshot_id)
start = _aware(self.window_start, "window_start")
end = _aware(self.window_end, "window_end")
if end <= start:
raise ValueError("audit window must be increasing")
if not isinstance(self.dimensions, tuple):
raise ValueError("dimensions must be a tuple")
if any(not isinstance(item, DimensionInput) for item in self.dimensions):
raise ValueError("dimensions must contain DimensionInput values")
_text(self.rules_revision, "rules_revision")
expected = _digest(
_snapshot_payload(
window_start=start,
window_end=end,
dimensions=self.dimensions,
rules_revision=self.rules_revision,
)
)
if self.snapshot_id != expected:
raise ValueError("snapshot_id does not match complete canonical inputs")
@classmethod
def create(
cls,
*,
window_start: datetime,
window_end: datetime,
dimensions: tuple[DimensionInput, ...],
rules_revision: str,
) -> "AuditSnapshot":
payload = _snapshot_payload(
window_start=_aware(window_start, "window_start"),
window_end=_aware(window_end, "window_end"),
dimensions=dimensions,
rules_revision=_text(rules_revision, "rules_revision"),
)
return cls(
snapshot_id=_digest(payload),
window_start=window_start,
window_end=window_end,
dimensions=dimensions,
rules_revision=rules_revision,
)
@dataclass(frozen=True)
class RuleLimits:
rules_revision: str
max_snapshot_skew_seconds: int
threshold_by_metric: tuple[tuple[str, float], ...]
def __post_init__(self) -> None:
_text(self.rules_revision, "rules_revision")
if (
isinstance(self.max_snapshot_skew_seconds, bool)
or not isinstance(self.max_snapshot_skew_seconds, int)
or self.max_snapshot_skew_seconds < 0
):
raise ValueError("max_snapshot_skew_seconds must be non-negative int")
if not isinstance(self.threshold_by_metric, tuple):
raise ValueError("threshold_by_metric must be a tuple")
names: list[str] = []
for item in self.threshold_by_metric:
if not isinstance(item, tuple) or len(item) != 2:
raise ValueError("threshold entries must be pairs")
name = _text(item[0], "threshold metric")
if name not in _THRESHOLDS:
raise ValueError("unknown threshold metric")
_number(item[1], "threshold")
names.append(name)
if len(names) != len(set(names)):
raise ValueError("duplicate threshold metric")
@property
def thresholds(self) -> dict[str, float]:
return {name: float(value) for name, value in self.threshold_by_metric}
@dataclass(frozen=True)
class Finding:
dimension: Dimension
status: Literal["CLEAR", "FINDING", "UNKNOWN"]
reason: str
evidence_refs: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.dimension, Dimension):
raise ValueError("dimension must be Dimension")
if self.status not in ("CLEAR", "FINDING", "UNKNOWN"):
raise ValueError("invalid finding status")
_text(self.reason, "reason")
if not isinstance(self.evidence_refs, tuple):
raise ValueError("evidence_refs must be a tuple")
for ref in self.evidence_refs:
_safe_ref(ref)
@dataclass(frozen=True)
class AuditReport:
report_id: str
snapshot_id: str
rules_revision: str
findings: tuple[Finding, ...]
mutation_count: Literal[0]
def __post_init__(self) -> None:
validate_digest(self.report_id)
validate_digest(self.snapshot_id)
_text(self.rules_revision, "rules_revision")
if not isinstance(self.findings, tuple) or any(
not isinstance(item, Finding) for item in self.findings
):
raise ValueError("findings must be a tuple of Finding")
if type(self.mutation_count) is not int or self.mutation_count != 0:
raise ValueError("ARO Phase A mutation_count must be exactly zero")
def _unknown(dimension: Dimension, reason: str, refs: tuple[str, ...] = ()) -> Finding:
return Finding(dimension, "UNKNOWN", reason, refs)
def _metric_values(item: DimensionInput) -> tuple[dict[str, float] | None, tuple[str, ...]]:
expected = _METRICS[item.dimension]
if not item.samples:
return None, ()
names = [sample.metric for sample in item.samples]
refs = tuple(dict.fromkeys(sample.evidence_ref for sample in item.samples))
if set(names) != expected or len(names) != len(set(names)):
return None, refs
values = {sample.metric: float(sample.value) for sample in item.samples}
for name in _COUNT_METRICS & expected:
if not values[name].is_integer():
return None, refs
return values, refs
def _evaluate_dimension(
item: DimensionInput, thresholds: Mapping[str, float]
) -> Finding:
values, refs = _metric_values(item)
if values is None:
return _unknown(item.dimension, "METRICS_MISSING_DUPLICATE_OR_UNKNOWN", refs)
dimension = item.dimension
if dimension == Dimension.FUNCTION:
required = values["required_checks"]
passed = values["passed_checks"]
if required <= 0 or passed > required:
return _unknown(dimension, "FUNCTION_COUNTS_INVALID", refs)
status = "FINDING" if passed < required else "CLEAR"
return Finding(dimension, status, "REQUIRED_CHECKS_INCOMPLETE" if status == "FINDING" else "ALL_REQUIRED_CHECKS_PASSED", refs)
if dimension == Dimension.PERFORMANCE:
threshold = thresholds.get("max_duration_p95_ms")
if threshold is None:
return _unknown(dimension, "THRESHOLD_MISSING", refs)
status = "FINDING" if values["duration_p95_ms"] > threshold else "CLEAR"
return Finding(dimension, status, "P95_DURATION_EXCEEDED" if status == "FINDING" else "P95_DURATION_WITHIN_LIMIT", refs)
if dimension == Dimension.EXCEPTION:
status = "FINDING" if values["unhandled_exception_count"] > 0 else "CLEAR"
return Finding(dimension, status, "UNHANDLED_EXCEPTIONS_PRESENT" if status == "FINDING" else "NO_UNHANDLED_EXCEPTIONS", refs)
if dimension == Dimension.USAGE:
status = "FINDING" if any(value > 0 for value in values.values()) else "CLEAR"
return Finding(dimension, status, "OPERATIONAL_PROMPT_OR_CONTINUE_PRESENT" if status == "FINDING" else "NO_OPERATIONAL_PROMPT_FRICTION", refs)
if dimension == Dimension.CODE_DEFECT:
status = "FINDING" if values["validated_open_defect_count"] > 0 else "CLEAR"
return Finding(dimension, status, "VALIDATED_OPEN_DEFECTS_PRESENT" if status == "FINDING" else "NO_VALIDATED_OPEN_DEFECTS", refs)
if dimension == Dimension.HISTORY:
threshold = thresholds.get("repeated_failure_threshold")
if threshold is None or threshold <= 0 or not threshold.is_integer():
return _unknown(dimension, "THRESHOLD_MISSING_OR_INVALID", refs)
status = "FINDING" if values["same_fingerprint_failure_count"] >= threshold else "CLEAR"
return Finding(dimension, status, "FAILURE_FINGERPRINT_REPEATED" if status == "FINDING" else "FAILURE_FINGERPRINT_BELOW_LIMIT", refs)
if dimension == Dimension.DEADLOCK:
threshold = thresholds.get("deadlock_seconds")
if threshold is None:
return _unknown(dimension, "THRESHOLD_MISSING", refs)
status = "FINDING" if (
values["wait_cycle_count"] > 0
and values["oldest_no_progress_seconds"] > threshold
) else "CLEAR"
return Finding(dimension, status, "WAIT_CYCLE_WITHOUT_PROGRESS" if status == "FINDING" else "NO_STALE_WAIT_CYCLE", refs)
if dimension == Dimension.LONG_RUNNING:
threshold = thresholds.get("max_nonterminal_age_seconds")
if threshold is None:
return _unknown(dimension, "THRESHOLD_MISSING", refs)
status = "FINDING" if values["oldest_nonterminal_age_seconds"] > threshold else "CLEAR"
return Finding(dimension, status, "NONTERMINAL_AGE_EXCEEDED" if status == "FINDING" else "NONTERMINAL_AGE_WITHIN_LIMIT", refs)
raise AssertionError("unhandled dimension")
def evaluate_snapshot(
snapshot: AuditSnapshot, *, limits: RuleLimits, now: datetime
) -> AuditReport:
"""Evaluate all eight dimensions; missing or inconsistent evidence is UNKNOWN."""
if not isinstance(snapshot, AuditSnapshot) or not isinstance(limits, RuleLimits):
raise TypeError("snapshot and limits must be typed values")
now = _aware(now, "now")
grouped: dict[Dimension, list[DimensionInput]] = {dimension: [] for dimension in _DIMENSIONS}
for item in snapshot.dimensions:
grouped[item.dimension].append(item)
global_reason: str | None = None
if snapshot.rules_revision != limits.rules_revision:
global_reason = "RULES_REVISION_UNKNOWN"
elif snapshot.window_end > now:
global_reason = "AUDIT_WINDOW_NOT_CLOSED"
else:
observed = [sample.observed_at for item in snapshot.dimensions for sample in item.samples]
if observed and (max(observed) - min(observed)).total_seconds() > limits.max_snapshot_skew_seconds:
global_reason = "CROSS_DOMAIN_SNAPSHOT_SKEW"
findings: list[Finding] = []
for dimension in _DIMENSIONS:
items = grouped[dimension]
if global_reason is not None:
refs = tuple(
dict.fromkeys(sample.evidence_ref for item in items for sample in item.samples)
)
findings.append(_unknown(dimension, global_reason, refs))
continue
if len(items) != 1:
findings.append(
_unknown(
dimension,
"DIMENSION_MISSING" if not items else "DIMENSION_CONFLICT",
)
)
continue
item = items[0]
refs = tuple(dict.fromkeys(sample.evidence_ref for sample in item.samples))
if item.captured_at > now or now >= item.expires_at:
findings.append(_unknown(dimension, "DIMENSION_STALE_OR_FUTURE", refs))
continue
if any(
sample.observed_at < snapshot.window_start
or sample.observed_at > snapshot.window_end
for sample in item.samples
):
findings.append(_unknown(dimension, "SAMPLE_OUTSIDE_WINDOW", refs))
continue
findings.append(_evaluate_dimension(item, limits.thresholds))
report_payload = {
"window_start": _canonical_datetime(snapshot.window_start),
"window_end": _canonical_datetime(snapshot.window_end),
"snapshot_id": snapshot.snapshot_id,
"rules_revision": limits.rules_revision,
}
return AuditReport(
report_id=_digest(report_payload),
snapshot_id=snapshot.snapshot_id,
rules_revision=limits.rules_revision,
findings=tuple(findings),
mutation_count=0,
)
class ReadonlyAuditSource(Protocol):
def collect(
self, *, window_start: datetime, window_end: datetime
) -> AuditSnapshot: ...
class AuditReportSink(Protocol):
def put_if_absent(self, report: AuditReport) -> str: ...
def _report_bytes(report: AuditReport) -> bytes:
payload = {
"schema": "aios.aro-readonly-report.v1",
"report_id": report.report_id,
"snapshot_id": report.snapshot_id,
"rules_revision": report.rules_revision,
"mutation_count": report.mutation_count,
"findings": [
{
"dimension": finding.dimension.value,
"status": finding.status,
"reason": finding.reason,
"evidence_refs": list(finding.evidence_refs),
}
for finding in report.findings
],
}
return (
json.dumps(
payload,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
)
+ "\n"
).encode("utf-8")
class FilesystemAuditReportSink:
"""Write one immutable, deterministic report under a host-fixed root."""
def __init__(self, root: Path) -> None:
if not isinstance(root, Path) or not root.is_absolute() or ".." in root.parts:
raise ValueError("audit root must be an absolute fixed path")
self.root = root
self._prepare_root()
def _prepare_root(self) -> None:
existing = self.root
while not existing.exists() and existing != existing.parent:
existing = existing.parent
cursor = existing
while True:
if cursor.is_symlink():
raise ValueError("audit root ancestry must not contain symlinks")
if cursor == cursor.parent:
break
cursor = cursor.parent
self.root.mkdir(parents=True, exist_ok=True)
if self.root.is_symlink() or not self.root.is_dir():
raise ValueError("audit root must be a real directory")
if self.root.resolve(strict=True) != self.root.absolute():
raise ValueError("audit root must not traverse reparse points or symlinks")
def put_if_absent(self, report: AuditReport) -> str:
if not isinstance(report, AuditReport):
raise TypeError("report must be AuditReport")
self._prepare_root()
validate_digest(report.report_id)
body = _report_bytes(report)
target = self.root / f"{report.report_id}.json"
if target.parent != self.root or target.is_symlink():
raise ValueError("REPORT_PATH_INVALID")
if target.exists():
if not target.is_file() or target.read_bytes() != body:
raise ValueError("REPORT_CONFLICT")
return f"aro:{report.report_id}"
file_descriptor, temporary_name = tempfile.mkstemp(
prefix=f".{report.report_id}.", suffix=".tmp", dir=self.root
)
temporary = Path(temporary_name)
try:
with os.fdopen(file_descriptor, "wb") as handle:
handle.write(body)
handle.flush()
os.fsync(handle.fileno())
try:
os.link(temporary, target)
except FileExistsError:
if target.is_symlink() or not target.is_file() or target.read_bytes() != body:
raise ValueError("REPORT_CONFLICT")
if target.read_bytes() != body:
raise ValueError("REPORT_CONFLICT")
finally:
try:
temporary.unlink()
except FileNotFoundError:
pass
return f"aro:{report.report_id}"
class AroReadonlyTick:
"""Default-off in-process tick over the most recently closed UTC window."""
def __init__(
self,
source: ReadonlyAuditSource,
sink: AuditReportSink,
limits: RuleLimits,
interval_seconds: int,
clock: Callable[[], datetime],
*,
enabled: bool = False,
) -> None:
if not hasattr(source, "collect") or not callable(source.collect):
raise ValueError("source must implement read-only collect")
if not hasattr(sink, "put_if_absent") or not callable(sink.put_if_absent):
raise ValueError("sink must implement put_if_absent")
if not isinstance(limits, RuleLimits):
raise ValueError("limits must be RuleLimits")
if isinstance(interval_seconds, bool) or not isinstance(interval_seconds, int) or interval_seconds <= 0:
raise ValueError("interval_seconds must be positive int")
if not callable(clock) or type(enabled) is not bool:
raise ValueError("clock must be callable and enabled must be bool")
self.source = source
self.sink = sink
self.limits = limits
self.interval_seconds = interval_seconds
self.clock = clock
self.enabled = enabled
self._last_window_end: datetime | None = None
def run_due(self) -> str | None:
if not self.enabled:
return None
now = _aware(self.clock(), "clock result").astimezone(timezone.utc)
end_timestamp = int(now.timestamp()) // self.interval_seconds * self.interval_seconds
window_end = datetime.fromtimestamp(end_timestamp, tz=timezone.utc)
window_start = window_end - timedelta(seconds=self.interval_seconds)
if self._last_window_end == window_end:
return None
try:
snapshot = self.source.collect(
window_start=window_start, window_end=window_end
)
if (
not isinstance(snapshot, AuditSnapshot)
or snapshot.window_start != window_start
or snapshot.window_end != window_end
):
raise ValueError("source returned a snapshot for the wrong window")
except Exception:
snapshot = AuditSnapshot.create(
window_start=window_start,
window_end=window_end,
dimensions=(),
rules_revision=self.limits.rules_revision,
)
report = evaluate_snapshot(snapshot, limits=self.limits, now=now)
try:
reference = self.sink.put_if_absent(report)
except Exception:
return None
self._last_window_end = window_end
return reference
__all__ = [
"AroReadonlyTick",
"AuditReport",
"AuditReportSink",
"AuditSnapshot",
"Dimension",
"DimensionInput",
"FilesystemAuditReportSink",
"Finding",
"ReadonlyAuditSource",
"RuleLimits",
"Sample",
"evaluate_snapshot",
]
@@ -1,173 +0,0 @@
"""Narrow production seam from a validated C6 plan to existing acquisition."""
from __future__ import annotations
from dataclasses import replace
import math
import time
from typing import Protocol
from .authorization_planner import (
AuthorizationPlan,
AuthorizationPlanInput,
CapabilityNamespace,
GitManifestRegistry,
RuntimeObservation,
StaticCapabilityManifest,
VerifiedRootSnapshot,
VerifiedRootSnapshotReader,
authorization_input_digest,
authorization_scope_digest,
build_authorization_plan,
verified_root_snapshot_digest,
)
from .governed_writer_automation import (
AuthorizationDenied,
EpochStatus,
GovernedWriterAdapters,
GovernedWriterAutomation,
GovernedWriterRequest,
GovernedWriterSession,
GovernedWriteReadiness,
RecoveryAuthorization,
)
class RuntimeObservationReader(Protocol):
"""Host-owned fresh runtime authority used at writer acquisition."""
def read_runtime_observation(
self,
*,
plan_input: AuthorizationPlanInput,
manifest: StaticCapabilityManifest,
root: VerifiedRootSnapshot | None,
now: float,
) -> RuntimeObservation | None: ...
def acquire_authorized_writer(
*,
automation: GovernedWriterAutomation,
request: GovernedWriterRequest,
authorization_ref: str,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
plan: AuthorizationPlan,
plan_input: AuthorizationPlanInput,
root_reader: VerifiedRootSnapshotReader,
runtime_reader: RuntimeObservationReader,
registry: GitManifestRegistry,
expected_registry_oid: str,
expected_manifest_sha256: str,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
) -> GovernedWriterSession:
"""Re-read existing verified Root immediately before `acquire_verified`.
This function deliberately has no signer, claim, lease, or direct
`automation.acquire` path. The existing automation remains the sole
composition root for effects.
"""
supplied_now = time.time() if now is None else now
if isinstance(supplied_now, bool):
raise AuthorizationDenied("TIME_INVALID")
try:
current = float(supplied_now)
except (TypeError, ValueError):
raise AuthorizationDenied("TIME_INVALID") from None
if not math.isfinite(current):
raise AuthorizationDenied("TIME_INVALID")
input_digest = authorization_input_digest(plan_input)
scope_digest = authorization_scope_digest(plan_input.scope)
if (plan_input.root_approval_ref != authorization_ref
or plan.input_digest != input_digest
or plan.scope_digest != scope_digest):
raise AuthorizationDenied("PLAN_INPUT_DRIFT")
if (request.subject != plan_input.actor_subject or request.task_id != plan_input.task_id
or request.repository != plan_input.scope.repository
or request.target_branch != plan_input.scope.target_branch
or request.base_oid != plan_input.scope.source_oid
or request.risk != plan_input.scope.risk_level
or request.file_allowlist != plan_input.scope.file_allowlist
or request.plan_digest != input_digest):
raise AuthorizationDenied("WRITER_REQUEST_PLAN_DRIFT")
if any(ref.namespace != CapabilityNamespace.AGENT_TASK for ref in plan_input.required_capabilities):
raise AuthorizationDenied("WRITER_REQUEST_CAPABILITY_NAMESPACE_INVALID")
requested_capabilities = frozenset(ref.token for ref in plan_input.required_capabilities)
if request.required_capabilities != requested_capabilities:
raise AuthorizationDenied("WRITER_REQUEST_CAPABILITY_DRIFT")
# This is a second, explicit frozen-runtime read. The existing
# acquire_verified path repeats it before effects as its own invariant.
automation.manifest_store.assert_current(
manifest_id=request.manifest_id,
runtime_fingerprint=request.runtime_fingerprint,
process_start_identity=request.process_start_identity,
)
manifest = registry.read_manifest(
expected_registry_oid=expected_registry_oid,
expected_manifest_sha256=expected_manifest_sha256,
)
if manifest.manifest_id != request.manifest_id or manifest.declaration_digest != plan.policy_digest:
raise AuthorizationDenied("POLICY_MANIFEST_DRIFT")
root = root_reader.read_verified_root(
authorization_ref=authorization_ref,
root_request_sha256=plan_input.root_request_sha256,
now=current,
)
if root is None or root.status != "VALID" or root.approval_ref != authorization_ref:
raise AuthorizationDenied("ROOT_REVOKED_OR_UNAVAILABLE")
if (root.root_request_sha256 != plan_input.root_request_sha256
or root.subject != plan_input.actor_subject
or root.audience != plan_input.audience
or root.mission_id != plan_input.mission_id
or root.scope != plan_input.scope):
raise AuthorizationDenied("ROOT_SCOPE_DRIFT")
if verified_root_snapshot_digest(root) != plan.root_snapshot_digest:
raise AuthorizationDenied("ROOT_SNAPSHOT_DRIFT")
if not (root.not_before <= current and root.observed_at <= current < root.snapshot_expires_at <= root.expires_at):
raise AuthorizationDenied("ROOT_EXPIRED")
reader = getattr(runtime_reader, "read_runtime_observation", None)
if not callable(reader):
raise AuthorizationDenied("RUNTIME_OBSERVATION_UNAVAILABLE")
try:
runtime = reader(
plan_input=plan_input,
manifest=manifest,
root=root,
now=current,
)
except Exception:
raise AuthorizationDenied("RUNTIME_OBSERVATION_UNAVAILABLE") from None
if not isinstance(runtime, RuntimeObservation):
raise AuthorizationDenied("RUNTIME_OBSERVATION_UNAVAILABLE")
fresh_input = replace(plan_input, now=current)
fresh_plan = build_authorization_plan(
request=fresh_input,
manifest=manifest,
runtime=runtime,
root=root,
)
if fresh_plan.next_action != "EXECUTE_GOVERNED_PLAN":
raise AuthorizationDenied(
fresh_plan.reason_code or "authorization plan is not executable"
)
if (
plan.policy_digest != fresh_plan.policy_digest
or plan.required_authorization != fresh_plan.required_authorization
or plan.scope_digest != fresh_plan.scope_digest
or plan.root_snapshot_digest != fresh_plan.root_snapshot_digest
):
raise AuthorizationDenied("AUTHORIZATION_PLAN_DRIFT")
# `acquire_verified` repeats the existing Owner authority verification
# before its own effect path. Do not substitute a locally reconstructed
# policy or call its test-only `acquire` method here.
return automation.acquire_verified(
request=request,
authorization_ref=authorization_ref,
readiness=readiness,
epoch=epoch,
adapters=adapters,
recovery_authorization=recovery_authorization,
now=current,
)
@@ -1,325 +0,0 @@
"""Read-only C6 authorization planning over existing authority boundaries."""
from __future__ import annotations
from dataclasses import asdict, dataclass
from enum import Enum
import hashlib
import json
import math
from pathlib import Path, PurePosixPath
import re
import subprocess
from typing import Any, Literal, Mapping, Protocol
from aios_capability_contract import normalize_required_capabilities
from .governed_writer_automation import RISK_ORDER
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA = re.compile(r"^[0-9a-f]{64}$")
_REGISTRY_PATH = "governance/capabilities/registry.v1.json"
class RegistryReadError(ValueError):
pass
class CapabilityNamespace(str, Enum):
AGENT_TASK = "agent-task"
TASK_PLANE_WILDCARD = "task-plane-wildcard"
def _text(value: object, field: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{field}_INVALID")
return value
def _oid(value: object, field: str) -> str:
value = _text(value, field)
if _OID.fullmatch(value) is None:
raise ValueError(f"{field}_INVALID")
return value
def _digest(value: object, field: str) -> str:
value = _text(value, field)
if _SHA.fullmatch(value) is None:
raise ValueError(f"{field}_INVALID")
return value
def _finite(value: object, field: str) -> float:
if isinstance(value, bool) or not isinstance(value, (float, int)) or not math.isfinite(float(value)):
raise ValueError(f"{field}_INVALID")
return float(value)
def _canonical(value: object) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode("utf-8")
def authorization_scope_digest(scope: "ScopeSpec") -> str:
return hashlib.sha256(_canonical({
"repository": scope.repository, "target_branch": scope.target_branch,
"source_oid": scope.source_oid, "file_allowlist": list(scope.file_allowlist),
"budget_units": scope.budget_units, "risk_level": scope.risk_level,
})).hexdigest()
@dataclass(frozen=True)
class CapabilityRef:
namespace: CapabilityNamespace
token: str
def __post_init__(self) -> None:
if not isinstance(self.namespace, CapabilityNamespace):
raise ValueError("CAPABILITY_NAMESPACE_INVALID")
_text(self.token, "capability")
@dataclass(frozen=True)
class ScopeSpec:
repository: str
target_branch: str
source_oid: str
file_allowlist: tuple[str, ...]
budget_units: int
risk_level: str
def __post_init__(self) -> None:
_text(self.repository, "repository"); _text(self.target_branch, "target_branch")
_oid(self.source_oid, "source_oid"); _text(self.risk_level, "risk_level")
if type(self.budget_units) is not int or self.budget_units < 0:
raise ValueError("budget_units_INVALID")
if not self.file_allowlist or len(set(self.file_allowlist)) != len(self.file_allowlist):
raise ValueError("file_allowlist_INVALID")
for path in self.file_allowlist:
if not isinstance(path, str) or not path or path != PurePosixPath(path).as_posix() or PurePosixPath(path).is_absolute() or ".." in PurePosixPath(path).parts:
raise ValueError("file_allowlist_INVALID")
@dataclass(frozen=True)
class RuntimeObservation:
observation_id: str; observed_at: float; expires_at: float; source_revision: str
actor_subject: str; audience: str; operation_id: str; scope_digest: str
manifest_revision: str; manifest_digest: str; executor_installed: bool | None
executor_callable: bool | None; authorization_live: bool | None
preconditions_pass: bool | None; revocation_epoch: int | None
def __post_init__(self) -> None:
for name in ("observation_id", "source_revision", "actor_subject", "audience", "operation_id", "manifest_revision"):
_text(getattr(self, name), name)
_finite(self.observed_at, "observed_at"); _finite(self.expires_at, "expires_at")
if self.expires_at <= self.observed_at: raise ValueError("OBSERVATION_EXPIRY_INVALID")
_digest(self.scope_digest, "scope_digest"); _digest(self.manifest_digest, "manifest_digest")
for name in ("executor_installed", "executor_callable", "authorization_live", "preconditions_pass"):
if getattr(self, name) is not None and type(getattr(self, name)) is not bool: raise ValueError(f"{name}_INVALID")
if self.revocation_epoch is not None and (type(self.revocation_epoch) is not int or self.revocation_epoch < 0): raise ValueError("revocation_epoch_INVALID")
@dataclass(frozen=True)
class VerifiedRootSnapshot:
approval_ref: str; root_request_sha256: str; subject: str; audience: str; mission_id: str
scope: ScopeSpec; capabilities: tuple[CapabilityRef, ...]; nonce: str; not_before: float
expires_at: float; revocation_epoch: int; receipt_ref: str; observed_at: float
snapshot_expires_at: float; status: Literal["VALID", "REVOKED"]
def __post_init__(self) -> None:
for name in ("approval_ref", "subject", "audience", "mission_id", "nonce", "receipt_ref"):
_text(getattr(self, name), name)
_digest(self.root_request_sha256, "root_request_sha256")
for name in ("not_before", "expires_at", "observed_at", "snapshot_expires_at"): _finite(getattr(self, name), name)
if not self.not_before <= self.snapshot_expires_at <= self.expires_at: raise ValueError("ROOT_TIME_INVALID")
if type(self.revocation_epoch) is not int or self.revocation_epoch < 0: raise ValueError("revocation_epoch_INVALID")
if self.status not in ("VALID", "REVOKED"): raise ValueError("ROOT_STATUS_INVALID")
@dataclass(frozen=True)
class StaticCapabilityManifest:
manifest_id: str; revision: str; capabilities: tuple[CapabilityRef, ...]
executor_refs: tuple[str, ...]; source_oid: str; declaration_digest: str
allowed_scopes: tuple[ScopeSpec, ...]
def __post_init__(self) -> None:
_text(self.manifest_id, "manifest_id"); _text(self.revision, "revision")
_oid(self.source_oid, "source_oid"); _digest(self.declaration_digest, "declaration_digest")
if len(set(self.capabilities)) != len(self.capabilities): raise ValueError("CAPABILITY_DUPLICATE")
for ref in self.executor_refs: _text(ref, "executor_ref")
@dataclass(frozen=True)
class AuthorizationPlanInput:
operation_id: str; actor_subject: str; audience: str; mission_id: str; task_id: str
scope: ScopeSpec; required_capabilities: tuple[CapabilityRef, ...]
root_approval_ref: str; root_request_sha256: str; now: float
def __post_init__(self) -> None:
for name in ("operation_id", "actor_subject", "audience", "mission_id", "task_id", "root_approval_ref"):
_text(getattr(self, name), name)
_digest(self.root_request_sha256, "root_request_sha256"); _finite(self.now, "now")
if not self.required_capabilities: raise ValueError("REQUIRED_CAPABILITIES_EMPTY")
@dataclass(frozen=True)
class AuthorizationPlan:
input_digest: str; scope_digest: str; policy_digest: str; required_authorization: str
root_snapshot_digest: str | None
reason_code: str | None; next_action: Literal["EXECUTE_GOVERNED_PLAN", "WAITING_ROOT_AUTHORIZATION", "AUTHORIZATION_REQUIRED", "CAPABILITY_UNAVAILABLE", "PRECONDITION_FAILED", "UNKNOWN"]
evidence_refs: tuple[str, ...]
class GitManifestRegistry(Protocol):
def read_manifest(self, *, expected_registry_oid: str, expected_manifest_sha256: str) -> StaticCapabilityManifest: ...
def read_supersession(self, *, manifest_id: str, revision: str) -> str | None: ...
class VerifiedRootSnapshotReader(Protocol):
def read_verified_root(self, *, authorization_ref: str, root_request_sha256: str, now: float) -> VerifiedRootSnapshot | None: ...
def verified_root_snapshot_digest(root: VerifiedRootSnapshot | None) -> str | None:
return None if root is None else hashlib.sha256(_canonical(asdict(root))).hexdigest()
def authorization_input_digest(request: AuthorizationPlanInput) -> str:
return hashlib.sha256(_canonical(asdict(request))).hexdigest()
def _plan(request: AuthorizationPlanInput, manifest: StaticCapabilityManifest, action: str, reason: str | None, refs: tuple[str, ...], root: VerifiedRootSnapshot | None) -> AuthorizationPlan:
return AuthorizationPlan(
input_digest=authorization_input_digest(request), scope_digest=authorization_scope_digest(request.scope),
policy_digest=manifest.declaration_digest, required_authorization=request.root_approval_ref,
root_snapshot_digest=verified_root_snapshot_digest(root),
reason_code=reason, next_action=action, evidence_refs=refs,
)
def _capabilities_cover(granted: tuple[CapabilityRef, ...], required: tuple[CapabilityRef, ...]) -> tuple[bool, str | None]:
for capability in required:
if capability.namespace == CapabilityNamespace.AGENT_TASK:
if "*" in capability.token:
return False, "CAPABILITY_NAMESPACE_MISMATCH"
normalized = normalize_required_capabilities([capability.token])
if any(value.startswith("exact:") for value in normalized): return False, "CAPABILITY_UNKNOWN"
same_namespace = [item.token for item in granted if item.namespace == CapabilityNamespace.AGENT_TASK]
if not same_namespace and granted:
return False, "CAPABILITY_NAMESPACE_MISMATCH"
effective_grants = normalize_required_capabilities(same_namespace)
if any(value.startswith("exact:") for value in effective_grants):
effective_grants = frozenset(value for value in effective_grants if not value.startswith("exact:"))
if not normalized.issubset(effective_grants): return False, "CAPABILITY_UNAVAILABLE"
elif capability.namespace == CapabilityNamespace.TASK_PLANE_WILDCARD:
if not any(item.namespace == capability.namespace and item.token == capability.token for item in granted): return False, "CAPABILITY_UNAVAILABLE"
return True, None
def _scope_covers(parent: ScopeSpec, child: ScopeSpec) -> bool:
# Use the same risk vocabulary as the authority that will issue the lease.
rank = RISK_ORDER
return (parent.repository == child.repository and parent.target_branch == child.target_branch and parent.source_oid == child.source_oid
and set(child.file_allowlist).issubset(parent.file_allowlist) and child.budget_units <= parent.budget_units
and rank.get(child.risk_level.lower()) is not None
and rank.get(parent.risk_level.lower(), 0) >= rank[child.risk_level.lower()])
def build_authorization_plan(*, request: AuthorizationPlanInput, manifest: StaticCapabilityManifest, runtime: RuntimeObservation, root: VerifiedRootSnapshot | None) -> AuthorizationPlan:
refs = () if root is None else (root.receipt_ref,)
if manifest.source_oid != request.scope.source_oid or not any(_scope_covers(scope, request.scope) for scope in manifest.allowed_scopes):
return _plan(request, manifest, "UNKNOWN", "REGISTRY_DRIFT", refs, root)
allowed, reason = _capabilities_cover(manifest.capabilities, request.required_capabilities)
if not allowed: return _plan(request, manifest, "CAPABILITY_UNAVAILABLE", reason, refs, root)
if root is None: return _plan(request, manifest, "WAITING_ROOT_AUTHORIZATION", "ROOT_MISSING", refs, root)
if root.status == "REVOKED": return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_REVOKED", refs, root)
if root.approval_ref != request.root_approval_ref or root.root_request_sha256 != request.root_request_sha256: return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_DIGEST_MISMATCH", refs, root)
if root.subject != request.actor_subject or root.audience != request.audience: return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_SUBJECT_AUDIENCE_MISMATCH", refs, root)
if root.mission_id != request.mission_id or not _scope_covers(root.scope, request.scope): return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_SCOPE_EXCEEDED", refs, root)
allowed, reason = _capabilities_cover(root.capabilities, request.required_capabilities)
if not allowed: return _plan(request, manifest, "AUTHORIZATION_REQUIRED", reason, refs, root)
if not (root.observed_at <= request.now < root.snapshot_expires_at <= root.expires_at) or request.now < root.not_before: return _plan(request, manifest, "WAITING_ROOT_AUTHORIZATION", "ROOT_EXPIRED", refs, root)
if runtime.expires_at <= request.now or runtime.observed_at > request.now: return _plan(request, manifest, "UNKNOWN", "PRECONDITION_UNKNOWN", refs, root)
if runtime.revocation_epoch is None or runtime.revocation_epoch != root.revocation_epoch: return _plan(request, manifest, "UNKNOWN", "EPOCH_REVOKED", refs, root)
if (runtime.actor_subject != request.actor_subject or runtime.audience != request.audience or runtime.operation_id != request.operation_id
or runtime.scope_digest != authorization_scope_digest(request.scope) or runtime.manifest_revision != manifest.revision
or runtime.manifest_digest != manifest.declaration_digest): return _plan(request, manifest, "UNKNOWN", "PLAN_INPUT_DRIFT", refs, root)
if any(value is None for value in (runtime.executor_installed, runtime.executor_callable, runtime.authorization_live, runtime.preconditions_pass)):
return _plan(request, manifest, "UNKNOWN", "PRECONDITION_UNKNOWN", refs, root)
if not all((runtime.executor_installed, runtime.executor_callable, runtime.authorization_live, runtime.preconditions_pass)):
return _plan(request, manifest, "PRECONDITION_FAILED", "PRECONDITION_FAILED", refs, root)
return _plan(request, manifest, "EXECUTE_GOVERNED_PLAN", None, refs, root)
class GitManifestRegistryReader:
"""Fixed-path, immutable Git blob reader; intentionally has no write API."""
def __init__(self, *, configured_repo_root: Path, manifest_id: str) -> None:
self.root = Path(configured_repo_root); self.manifest_id = _text(manifest_id, "manifest_id")
self._last_registry: Mapping[str, Any] | None = None
def _blob(self, oid: str) -> bytes:
_oid(oid, "expected_registry_oid")
if self.root.is_symlink() or not self.root.is_dir(): raise RegistryReadError("REGISTRY_ROOT_INVALID")
result = subprocess.run(["git", "-C", str(self.root), "show", f"{oid}:{_REGISTRY_PATH}"], capture_output=True, timeout=15)
if result.returncode: raise RegistryReadError("REGISTRY_UNAVAILABLE")
return result.stdout
def _registry(self, *, expected_registry_oid: str, expected_manifest_sha256: str) -> Mapping[str, Any]:
raw = self._blob(expected_registry_oid)
if hashlib.sha256(raw).hexdigest() != _digest(expected_manifest_sha256, "expected_manifest_sha256"): raise RegistryReadError("REGISTRY_DRIFT")
try: value = json.loads(raw)
except (UnicodeError, json.JSONDecodeError) as exc: raise RegistryReadError("REGISTRY_INVALID") from exc
if not isinstance(value, Mapping) or set(value) != {"schema", "records", "supersessions"} or value["schema"] != "aios.capability-registry.v1": raise RegistryReadError("REGISTRY_INVALID")
return value
def read_manifest(self, *, expected_registry_oid: str, expected_manifest_sha256: str) -> StaticCapabilityManifest:
registry = self._registry(expected_registry_oid=expected_registry_oid, expected_manifest_sha256=expected_manifest_sha256)
records = registry["records"]
if not isinstance(records, list): raise RegistryReadError("REGISTRY_INVALID")
supersessions = registry["supersessions"]
if not isinstance(supersessions, list): raise RegistryReadError("REGISTRY_INVALID")
record_index: dict[tuple[str, str], Mapping[str, Any]] = {}
for record in records:
if not isinstance(record, Mapping) or not isinstance(record.get("manifest_id"), str) or not isinstance(record.get("revision"), str):
raise RegistryReadError("REGISTRY_INVALID")
key = (record["manifest_id"], record["revision"])
if key in record_index: raise RegistryReadError("REGISTRY_DRIFT")
record_index[key] = record
replaced: dict[tuple[str, str], tuple[str, str]] = {}
for item in supersessions:
if not isinstance(item, Mapping) or set(item) != {"manifest_id", "prior_revision", "new_revision", "evidence_ref"}:
raise RegistryReadError("REGISTRY_INVALID")
try:
manifest_id = _text(item["manifest_id"], "manifest_id")
prior = _text(item["prior_revision"], "prior_revision")
newer = _text(item["new_revision"], "new_revision")
_text(item["evidence_ref"], "evidence_ref")
except ValueError as exc:
raise RegistryReadError("REGISTRY_INVALID") from exc
prior_key, new_key = (manifest_id, prior), (manifest_id, newer)
if prior_key not in record_index or new_key not in record_index or prior_key == new_key or prior_key in replaced:
raise RegistryReadError("REGISTRY_DRIFT")
replaced[prior_key] = new_key
candidates = [record for record in records if isinstance(record, Mapping) and record.get("manifest_id") == self.manifest_id]
if not candidates: raise RegistryReadError("REGISTRY_DRIFT")
current = [record for record in candidates if (self.manifest_id, record.get("revision")) not in replaced]
if len(current) != 1: raise RegistryReadError("REGISTRY_DRIFT")
record = current[0]; required = {"manifest_id", "revision", "capabilities", "executor_refs", "source_oid", "declaration_digest", "allowed_scopes"}
if set(record) != required: raise RegistryReadError("REGISTRY_INVALID")
unsigned = {key: value for key, value in record.items() if key != "declaration_digest"}
if hashlib.sha256(_canonical(unsigned)).hexdigest() != _digest(record["declaration_digest"], "declaration_digest"): raise RegistryReadError("REGISTRY_DRIFT")
try:
manifest = StaticCapabilityManifest(str(record["manifest_id"]), str(record["revision"]), tuple(CapabilityRef(CapabilityNamespace(item["namespace"]), item["token"]) for item in record["capabilities"]), tuple(record["executor_refs"]), str(record["source_oid"]), str(record["declaration_digest"]), tuple(ScopeSpec(**item) for item in record["allowed_scopes"]))
except (KeyError, TypeError, ValueError) as exc: raise RegistryReadError("REGISTRY_INVALID") from exc
self._last_registry = registry
return manifest
def read_supersession(self, *, manifest_id: str, revision: str) -> str | None:
_text(manifest_id, "manifest_id"); _text(revision, "revision")
if self._last_registry is None:
raise RegistryReadError("REGISTRY_READ_REQUIRES_PINNED_DIGEST")
matches = [
item["new_revision"] for item in self._last_registry["supersessions"]
if item["manifest_id"] == manifest_id and item["prior_revision"] == revision
]
if len(matches) > 1:
raise RegistryReadError("REGISTRY_DRIFT")
return matches[0] if matches else None
@@ -1,94 +0,0 @@
"""Agent discovery/claim coordinator over diagnostic schedulability evidence."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping
from .schedulability import (
DiscoveryDiagnostics,
SchedulabilityProof,
SchedulabilityService,
)
@dataclass(frozen=True)
class DiscoveryResult:
status: str
proof: SchedulabilityProof | None
blocker_codes: tuple[str, ...] = ()
candidate_count: int = 0
def to_dict(self) -> dict[str, Any]:
value: dict[str, Any] = {
"status": self.status,
"blocker_codes": list(self.blocker_codes),
"candidate_count": self.candidate_count,
"proof": None,
}
if self.proof is not None:
value["proof"] = {
"schema": self.proof.schema,
"proof_id": self.proof.proof_id,
"task_id": self.proof.task_id,
"worker_id": self.proof.worker_id,
"created_at": self.proof.created_at,
"expires_at": self.proof.expires_at,
"blockers": [b.to_dict() for b in self.proof.blockers],
"proof_digest": self.proof.proof_digest,
}
return value
def _waiting_status(diag: DiscoveryDiagnostics) -> str:
codes = set(diag.blocker_codes)
if diag.candidate_count == 0:
return "NO_CANDIDATES"
if not codes:
return "NO_CLAIMABLE_TASK"
if codes <= {"CAPACITY_EXHAUSTED"}:
return "WAITING_CAPACITY"
if "AUTHORIZATION_BLOCKED" in codes or "EXACT_SCOPE_MISMATCH" in codes:
return "WAITING_AUTHORIZATION"
if "DEPENDENCY_BLOCKED" in codes:
return "WAITING_DEPENDENCY"
if "FREEZE_BLOCKED" in codes:
return "WAITING_FREEZE"
if "CONFLICTING_LEASE" in codes:
return "WAITING_LEASE"
if {"STALE_OR_INACTIVE_WORKER", "MISSING_CAPABILITY", "INVALID_AUTHORITY_VALUE"} & codes:
return "WORKER_NOT_ELIGIBLE"
if "TASK_STATE_NOT_CLAIMABLE" in codes:
return "NO_CLAIMABLE_TASK"
return "BLOCKED"
class AutoClaimCoordinator:
"""No implicit credential derivation; authenticated identity is supplied by host."""
def __init__(self, schedulability: SchedulabilityService) -> None:
self.schedulability = schedulability
def discover(self, *, worker_id: str, actor_identity: str) -> DiscoveryResult:
proof, diag = self.schedulability.discover_with_diagnostics(
worker_id=worker_id, actor_identity=actor_identity
)
if proof is not None:
return DiscoveryResult(
"CLAIMABLE", proof, diag.blocker_codes, diag.candidate_count
)
return DiscoveryResult(
_waiting_status(diag), None, diag.blocker_codes, diag.candidate_count
)
def claim(
self,
*,
proof_id: str,
worker_id: str,
actor_identity: str,
lease_seconds: int = 30,
) -> Mapping[str, Any]:
return self.schedulability.claim_with_recheck(
proof_id=proof_id,
worker_id=worker_id,
actor_identity=actor_identity,
lease_seconds=lease_seconds,
)
File diff suppressed because it is too large Load Diff
@@ -1,972 +0,0 @@
"""AIOS-native binding layer for the unattended resume controller.
This module intentionally contains no HTTP client, Unix-socket client, database,
systemd integration, scheduler loop, secret handling, or privileged identity
mutation.
It converts existing AIOS authorities into the generic callbacks defined by
``autonomous_resume_integration``. Field code supplies the current authoritative
implementations for:
- durable Owner authorization/grant projection;
- canonical governed-worker activation/status;
- governed task admission;
- task/claim/TaskLease/TaskPrincipal/WriterLease/CAS ownership;
- bounded source-lane execution and integration;
- Gate0;
- ReleaseReady/deployability;
- PromotionController;
- final production acceptance;
- mission resume lease/checkpoint/effect reconciliation.
The adapter is deterministic and fail-closed. It never fabricates any credential,
principal, lease, fence, task, commit, receipt, approval, or deployment result.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import hashlib
import json
from typing import Mapping, Protocol, Sequence
from .autonomous_resume_controller import (
EffectState,
MissionCheckpoint,
MissionObservation,
MissionPhase,
ResumeLease,
StepResult,
StepStatus,
)
from .autonomous_resume_integration import (
AuthorityCallbacks,
CallableAutonomousExecutionPort,
PhaseCallbacks,
)
def _canonical(value) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _digest(value) -> str:
return hashlib.sha256(_canonical(value)).hexdigest()
class WorkerState(str, Enum):
ACTIVE = "ACTIVE"
STARTING = "STARTING"
INACTIVE = "INACTIVE"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class WorkState(str, Enum):
PENDING = "PENDING"
RUNNING = "RUNNING"
COMPLETE = "COMPLETE"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class GateState(str, Enum):
PASS = "PASS"
HOLD = "HOLD"
UNKNOWN = "UNKNOWN"
class ReadyState(str, Enum):
READY = "READY"
NOT_READY = "NOT_READY"
UNKNOWN = "UNKNOWN"
class PromotionState(str, Enum):
IN_PROGRESS = "IN_PROGRESS"
VERIFIED = "VERIFIED"
ROLLED_BACK = "ROLLED_BACK"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class AcceptanceState(str, Enum):
PASS = "PASS"
FAIL_ROLLED_BACK = "FAIL_ROLLED_BACK"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
@dataclass(frozen=True)
class DurableGrantView:
grant_id: str
active: bool
repository: str
allowed_target_prefixes: tuple[str, ...]
max_task_risk: str
expires_at: float | None = None
owner_action_required: bool = False
evidence_refs: tuple[str, ...] = ()
def permits(self, *, repository: str, branch: str, risk: str) -> bool:
if not self.active:
return False
if self.repository != repository:
return False
if not any(branch.startswith(prefix) for prefix in self.allowed_target_prefixes):
return False
rank = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
if risk not in rank or self.max_task_risk not in rank:
return False
return rank[risk] <= rank[self.max_task_risk]
@dataclass(frozen=True)
class TaskLaneSpec:
lane: str
task_id: str
repository: str
target_branch: str
risk: str
description: str
write_set: tuple[str, ...]
def __post_init__(self) -> None:
if not self.lane or not self.task_id or not self.repository:
raise ValueError("lane_task_repository_required")
if not self.target_branch.startswith("agent/"):
raise ValueError("target_branch_must_use_agent_prefix")
if not self.write_set:
raise ValueError("write_set_required")
@dataclass(frozen=True)
class TaskAdmissionRequest:
task_id: str
idempotency_key: str
repository: str
base_oid: str
target_branch: str
expected_branch_oid: str
risk: str
description: str
@dataclass(frozen=True)
class TaskAdmissionReceipt:
task_id: str
generation: int
target_branch: str
persisted: bool
claimable: bool
receipt_ref: str
def __post_init__(self) -> None:
if self.generation < 1:
raise ValueError("task_generation_must_be_positive")
if not self.receipt_ref:
raise ValueError("task_admission_receipt_required")
@dataclass(frozen=True)
class WorkerActivationReceipt:
state: WorkerState
receipt_ref: str | None = None
retry_after_seconds: int | None = None
owner_action_required: bool = False
code: str = "WORKER_STATE"
@dataclass(frozen=True)
class LaneExecutionView:
lane: str
state: WorkState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "LANE_STATE"
@dataclass(frozen=True)
class IntegrationView:
state: WorkState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "INTEGRATION_STATE"
@dataclass(frozen=True)
class GateView:
state: GateState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "GATE_STATE"
@dataclass(frozen=True)
class ReleaseReadyView:
state: ReadyState
condition_fingerprint: str
material_digest: str | None
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "READY_STATE"
@dataclass(frozen=True)
class PromotionView:
state: PromotionState
condition_fingerprint: str
material_digest: str | None
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "PROMOTION_STATE"
@dataclass(frozen=True)
class AcceptanceView:
state: AcceptanceState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
code: str = "ACCEPTANCE_STATE"
@dataclass(frozen=True)
class MissionEffectView:
effect_state: EffectState
condition_fingerprint: str
requires_reconcile: bool
receipt_refs: tuple[str, ...] = ()
class AiosMissionAuthorities(Protocol):
"""Existing AIOS authority surface consumed by this adapter."""
# Live truth.
def read_head_oid(self, repository: str) -> str: ...
def read_material_digest(self, mission_id: str) -> str: ...
def read_generation(self, mission_id: str) -> int: ...
def read_effect_view(self, mission_id: str) -> MissionEffectView: ...
def owner_action_resolved(self, mission_id: str) -> bool: ...
# Durable mission state / one-winner resume authority.
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None: ...
def acquire_resume_lease(
self,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None: ...
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> None: ...
def persist_owner_action_required(
self,
payload: Mapping[str, object],
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str: ...
def release_resume_lease(
self,
lease: ResumeLease,
outcome_code: str,
) -> None: ...
def reconcile_effects(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
# Admission.
def read_durable_grant(self, mission_id: str) -> DurableGrantView: ...
def ensure_canonical_worker(
self,
mission_id: str,
generation: int,
idempotency_key: str,
) -> WorkerActivationReceipt: ...
def create_or_resume_task(
self,
request: TaskAdmissionRequest,
) -> TaskAdmissionReceipt: ...
def read_task_admission(
self,
lane: TaskLaneSpec,
) -> TaskAdmissionReceipt | None: ...
# Phase execution.
def read_lane_execution(
self,
lane: TaskLaneSpec,
task: TaskAdmissionReceipt,
) -> LaneExecutionView: ...
def drive_integration(
self,
lanes: Sequence[TaskLaneSpec],
tasks: Sequence[TaskAdmissionReceipt],
) -> IntegrationView: ...
def run_gate0(self, mission_id: str) -> GateView: ...
def build_release_ready(self, mission_id: str) -> ReleaseReadyView: ...
def run_promotion(self, mission_id: str) -> PromotionView: ...
def run_final_acceptance(self, mission_id: str) -> AcceptanceView: ...
@dataclass(frozen=True)
class AiosUnattendedMissionConfig:
mission_id: str
repository: str
lanes: tuple[TaskLaneSpec, ...]
admission_risk: str = "medium"
def __post_init__(self) -> None:
if not self.mission_id or not self.repository:
raise ValueError("mission_and_repository_required")
if not self.lanes:
raise ValueError("at_least_one_lane_required")
names = [lane.lane for lane in self.lanes]
task_ids = [lane.task_id for lane in self.lanes]
branches = [lane.target_branch for lane in self.lanes]
if len(names) != len(set(names)):
raise ValueError("duplicate_lane")
if len(task_ids) != len(set(task_ids)):
raise ValueError("duplicate_task_id")
if len(branches) != len(set(branches)):
raise ValueError("duplicate_target_branch")
owners: dict[str, str] = {}
for lane in self.lanes:
if lane.repository != self.repository:
raise ValueError("lane_repository_mismatch")
for resource in lane.write_set:
if resource in owners:
raise ValueError(
f"overlapping_lane_resource:{resource}:"
f"{owners[resource]}:{lane.lane}"
)
owners[resource] = lane.lane
class AiosUnattendedCallbackFactory:
"""Construct generic controller callbacks from current AIOS authorities."""
def __init__(
self,
authority: AiosMissionAuthorities,
config: AiosUnattendedMissionConfig,
) -> None:
self.authority = authority
self.config = config
self._admissions: dict[str, TaskAdmissionReceipt] = {}
def build_port(self) -> CallableAutonomousExecutionPort:
return CallableAutonomousExecutionPort(
AuthorityCallbacks(
read_observation=self.read_observation,
load_checkpoint=self.authority.load_checkpoint,
acquire_resume_lease=self.authority.acquire_resume_lease,
persist_checkpoint=self.authority.persist_checkpoint,
persist_owner_action_required=self.authority.persist_owner_action_required,
release_resume_lease=self.authority.release_resume_lease,
reconcile=self.authority.reconcile_effects,
),
PhaseCallbacks(
admission=self.admission,
source_execution=self.source_execution,
gate0=self.gate0,
release_ready=self.release_ready,
promotion=self.promotion,
final_acceptance=self.final_acceptance,
),
)
def read_observation(self, mission_id: str) -> MissionObservation:
if mission_id != self.config.mission_id:
raise ValueError("mission_id_mismatch")
generation = self.authority.read_generation(mission_id)
head_oid = self.authority.read_head_oid(self.config.repository)
material = self.authority.read_material_digest(mission_id)
effect = self.authority.read_effect_view(mission_id)
fingerprint = _digest({
"schema": "aios.unattended-condition.v1",
"mission_id": mission_id,
"generation": generation,
"head_oid": head_oid,
"material_digest": material,
"effect_state": effect.effect_state.value,
"effect_condition": effect.condition_fingerprint,
"requires_reconcile": effect.requires_reconcile,
})
return MissionObservation(
mission_id=mission_id,
generation=generation,
head_oid=head_oid,
material_digest=material,
condition_fingerprint=fingerprint,
effect_state=effect.effect_state,
requires_reconcile=effect.requires_reconcile,
owner_action_resolved=self.authority.owner_action_resolved(mission_id),
evidence_refs=effect.receipt_refs,
)
def admission(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
grant = self.authority.read_durable_grant(observation.mission_id)
grant_refs = tuple(grant.evidence_refs)
if not grant.active:
return StepResult(
StepStatus.BLOCKED_OWNER if grant.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
"DURABLE_GRANT_NOT_ACTIVE",
self._phase_fingerprint(
"admission", observation, {"grant_id": grant.grant_id, "active": False}
),
receipt_refs=grant_refs,
owner_action_payload=(
{
"boundary": "DURABLE_GRANT_NOT_ACTIVE",
"grant_id": grant.grant_id,
}
if grant.owner_action_required else None
),
)
for lane in self.config.lanes:
if not grant.permits(
repository=lane.repository,
branch=lane.target_branch,
risk=lane.risk,
):
return StepResult(
StepStatus.BLOCKED_OWNER if grant.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
"DURABLE_GRANT_SCOPE_MISMATCH",
self._phase_fingerprint(
"admission",
observation,
{
"grant_id": grant.grant_id,
"lane": lane.lane,
"branch": lane.target_branch,
"risk": lane.risk,
},
),
receipt_refs=grant_refs,
owner_action_payload=(
{
"boundary": "DURABLE_GRANT_SCOPE_MISMATCH",
"grant_id": grant.grant_id,
"lane": lane.lane,
}
if grant.owner_action_required else None
),
)
worker = self.authority.ensure_canonical_worker(
observation.mission_id,
observation.generation,
self._idempotency(
"worker",
observation.mission_id,
observation.generation,
),
)
worker_refs = self._refs(grant_refs, (worker.receipt_ref or "",))
if worker.state is WorkerState.UNKNOWN:
return StepResult(
StepStatus.RECONCILE,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
)
if worker.state is WorkerState.BLOCKED:
return StepResult(
StepStatus.BLOCKED_OWNER if worker.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
owner_action_payload=(
{"boundary": worker.code}
if worker.owner_action_required else None
),
)
if worker.state in {WorkerState.STARTING, WorkerState.INACTIVE}:
return StepResult(
StepStatus.IN_PROGRESS,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
retry_after_seconds=worker.retry_after_seconds,
)
if worker.state is WorkerState.ACTIVE and not worker.receipt_ref:
return StepResult(
StepStatus.RECONCILE,
"WORKER_ACTIVE_WITHOUT_RECEIPT",
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
)
task_refs: list[str] = list(worker_refs)
admissions: dict[str, TaskAdmissionReceipt] = {}
for lane in self.config.lanes:
request = TaskAdmissionRequest(
task_id=lane.task_id,
idempotency_key=self._idempotency(
"task",
observation.generation,
lane.task_id,
observation.head_oid,
),
repository=lane.repository,
base_oid=observation.head_oid,
target_branch=lane.target_branch,
expected_branch_oid=observation.head_oid,
risk=lane.risk,
description=lane.description,
)
receipt = self.authority.create_or_resume_task(request)
task_refs.append(receipt.receipt_ref)
receipt_error = self._admission_receipt_error(
lane,
receipt,
observation=observation,
)
if receipt_error is not None:
return StepResult(
StepStatus.RECONCILE,
receipt_error,
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
if not receipt.persisted:
return StepResult(
StepStatus.RECONCILE,
"TASK_PERSISTENCE_UNKNOWN",
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
if not receipt.claimable:
return StepResult(
StepStatus.IN_PROGRESS,
"TASK_NOT_CLAIMABLE_YET",
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
admissions[lane.lane] = receipt
self._admissions = admissions
return StepResult(
StepStatus.SUCCEEDED,
"GOVERNED_TASKS_ADMITTED",
self._phase_fingerprint(
"admission",
observation,
{"tasks": sorted(r.task_id for r in admissions.values())},
),
receipt_refs=tuple(task_refs),
next_phase=MissionPhase.S2_EXECUTION,
)
def source_execution(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
recovery_refs: list[str] = []
for lane in self.config.lanes:
if lane.lane in self._admissions:
continue
receipt = self.authority.read_task_admission(lane)
if receipt is None:
return StepResult(
StepStatus.RECONCILE,
"TASK_ADMISSION_NOT_RESOLVED",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
recovery_refs.append(receipt.receipt_ref)
receipt_error = self._admission_receipt_error(
lane,
receipt,
observation=observation,
)
if receipt_error is not None:
return StepResult(
StepStatus.RECONCILE,
receipt_error,
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
if not receipt.persisted:
return StepResult(
StepStatus.RECONCILE,
"TASK_PERSISTENCE_UNKNOWN",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
if not receipt.claimable:
return StepResult(
StepStatus.IN_PROGRESS,
"TASK_NOT_CLAIMABLE_YET",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
self._admissions[lane.lane] = receipt
views: list[LaneExecutionView] = []
refs: list[str] = list(recovery_refs)
for lane in self.config.lanes:
view = self.authority.read_lane_execution(
lane,
self._admissions[lane.lane],
)
if view.lane != lane.lane:
return StepResult(
StepStatus.RECONCILE,
"LANE_IDENTITY_MISMATCH",
self._phase_fingerprint(
"source",
observation,
{"expected": lane.lane, "actual": view.lane},
),
receipt_refs=tuple(refs),
)
if view.state is WorkState.COMPLETE and not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"LANE_COMPLETE_WITHOUT_RECEIPT",
view.condition_fingerprint,
receipt_refs=tuple(refs),
)
views.append(view)
refs.extend(view.receipt_refs)
unknown = [v for v in views if v.state is WorkState.UNKNOWN]
if unknown:
return StepResult(
StepStatus.RECONCILE,
unknown[0].code,
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
)
blocked = [v for v in views if v.state is WorkState.BLOCKED]
if blocked:
return StepResult(
StepStatus.BLOCKED_DETERMINISTIC,
blocked[0].code,
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
)
pending = [
v for v in views
if v.state in {WorkState.PENDING, WorkState.RUNNING}
]
if pending:
retry_values = [
v.retry_after_seconds
for v in pending
if v.retry_after_seconds is not None
]
return StepResult(
StepStatus.IN_PROGRESS,
"SOURCE_LANES_IN_PROGRESS",
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
retry_after_seconds=min(retry_values) if retry_values else None,
)
integration = self.authority.drive_integration(
self.config.lanes,
tuple(self._admissions[lane.lane] for lane in self.config.lanes),
)
refs.extend(integration.receipt_refs)
if integration.state is WorkState.UNKNOWN:
return StepResult(
StepStatus.RECONCILE,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
if integration.state is WorkState.BLOCKED:
return StepResult(
StepStatus.BLOCKED_DETERMINISTIC,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
if integration.state in {WorkState.PENDING, WorkState.RUNNING}:
return StepResult(
StepStatus.IN_PROGRESS,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
retry_after_seconds=integration.retry_after_seconds,
)
if integration.state is WorkState.COMPLETE and not integration.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"INTEGRATION_COMPLETE_WITHOUT_RECEIPT",
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
return StepResult(
StepStatus.SUCCEEDED,
"SOURCE_INTEGRATION_COMPLETE",
integration.condition_fingerprint,
receipt_refs=tuple(refs),
next_phase=MissionPhase.GATE0,
)
def gate0(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_gate0(observation.mission_id)
if view.state is GateState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is GateState.HOLD:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"GATE0_PASS_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.RELEASE_READY
if status is StepStatus.SUCCEEDED else None
),
)
def release_ready(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.build_release_ready(observation.mission_id)
if view.state is ReadyState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is ReadyState.NOT_READY:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
if not view.material_digest:
return StepResult(
StepStatus.RECONCILE,
"READY_WITHOUT_MATERIAL_DIGEST",
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
)
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"READY_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.PROMOTION
if status is StepStatus.SUCCEEDED else None
),
material_digest=view.material_digest,
)
def promotion(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_promotion(observation.mission_id)
if view.state is PromotionState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is PromotionState.IN_PROGRESS:
status = StepStatus.IN_PROGRESS
elif view.state is PromotionState.VERIFIED:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"PROMOTION_VERIFIED_WITHOUT_RECEIPT",
view.condition_fingerprint,
material_digest=view.material_digest,
)
status = StepStatus.SUCCEEDED
elif view.state is PromotionState.ROLLED_BACK:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
status = StepStatus.BLOCKED_DETERMINISTIC
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.FINAL_ACCEPTANCE
if status is StepStatus.SUCCEEDED else None
),
material_digest=view.material_digest,
)
def final_acceptance(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_final_acceptance(observation.mission_id)
if view.state is AcceptanceState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is AcceptanceState.PASS:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"FINAL_ACCEPTANCE_PASS_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
else:
status = StepStatus.BLOCKED_DETERMINISTIC
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
next_phase=(
MissionPhase.COMPLETE
if status is StepStatus.SUCCEEDED else None
),
)
@staticmethod
def _admission_receipt_error(
lane: TaskLaneSpec,
receipt: TaskAdmissionReceipt,
*,
observation: MissionObservation,
) -> str | None:
if receipt.task_id != lane.task_id:
return "TASK_IDENTITY_MISMATCH"
if receipt.target_branch != lane.target_branch:
return "TASK_BRANCH_MISMATCH"
if receipt.generation != observation.generation:
return "TASK_GENERATION_MISMATCH"
return None
@staticmethod
def _refs(*groups: Sequence[str]) -> tuple[str, ...]:
seen: set[str] = set()
values: list[str] = []
for group in groups:
for item in group:
if item and item not in seen:
seen.add(item)
values.append(item)
return tuple(values)
@staticmethod
def _idempotency(*parts: object) -> str:
return "aios:auto:" + _digest(
{"schema": "aios.unattended-idempotency.v1", "parts": list(parts)}
)
@staticmethod
def _phase_fingerprint(
phase: str,
observation: MissionObservation,
details: Mapping[str, object],
) -> str:
return _digest({
"schema": "aios.unattended-phase-condition.v1",
"phase": phase,
"mission_id": observation.mission_id,
"generation": observation.generation,
"head_oid": observation.head_oid,
"material_digest": observation.material_digest,
"details": details,
})
@@ -1,877 +0,0 @@
"""Deterministic unattended mission-resume controller for AIOS.
The controller is deliberately small and does not create a second scheduler,
database, identity system, or release control plane.
It executes exactly one reconciliation/progress cycle per ``run_once`` call.
An existing AIOS scheduler/timer/worker loop should invoke it periodically.
All consequential effects are delegated through ``AutonomousExecutionPort`` and
must use the existing AIOS authorities: durable Owner authorization, task
admission, claim/TaskLease/TaskPrincipal, WriterLease/CAS/fencing, Gate0,
ReleaseReady, PromotionController and governed rollback.
The controller's responsibilities are limited to:
- one-winner resume lease acquisition;
- durable checkpoint progression;
- bounded retry/backoff;
- unchanged deterministic blocker suppression;
- UNKNOWN/ambiguous-effect reconciliation before retry;
- non-interactive owner-boundary recording;
- crash-safe phase resumption.
It never manufactures credentials, group membership, principals, leases,
signatures, branch OIDs, or release receipts.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field, replace
from enum import Enum
import hashlib
import json
import time
from typing import Any, Callable, Mapping, Protocol, Sequence
class MissionPhase(str, Enum):
S2_ADMISSION = "S2_ADMISSION"
S2_EXECUTION = "S2_EXECUTION"
GATE0 = "GATE0"
RELEASE_READY = "RELEASE_READY"
PROMOTION = "PROMOTION"
FINAL_ACCEPTANCE = "FINAL_ACCEPTANCE"
OWNER_ACTION_REQUIRED = "OWNER_ACTION_REQUIRED"
COMPLETE = "COMPLETE"
class EffectState(str, Enum):
ABSENT = "ABSENT"
ACTIVE = "ACTIVE"
COMPLETED = "COMPLETED"
UNKNOWN = "UNKNOWN"
class StepStatus(str, Enum):
SUCCEEDED = "SUCCEEDED"
IN_PROGRESS = "IN_PROGRESS"
RETRYABLE = "RETRYABLE"
RECONCILE = "RECONCILE"
BLOCKED_DETERMINISTIC = "BLOCKED_DETERMINISTIC"
BLOCKED_OWNER = "BLOCKED_OWNER"
class OutcomeStatus(str, Enum):
COMPLETE = "COMPLETE"
PROGRESSED = "PROGRESSED"
IN_PROGRESS = "IN_PROGRESS"
RETRY_SCHEDULED = "RETRY_SCHEDULED"
WAIT_CONDITION_CHANGE = "WAIT_CONDITION_CHANGE"
ACTIVE_ELSEWHERE = "ACTIVE_ELSEWHERE"
OWNER_ACTION_REQUIRED = "OWNER_ACTION_REQUIRED"
_PHASE_NEXT: dict[MissionPhase, MissionPhase] = {
MissionPhase.S2_ADMISSION: MissionPhase.S2_EXECUTION,
MissionPhase.S2_EXECUTION: MissionPhase.GATE0,
MissionPhase.GATE0: MissionPhase.RELEASE_READY,
MissionPhase.RELEASE_READY: MissionPhase.PROMOTION,
MissionPhase.PROMOTION: MissionPhase.FINAL_ACCEPTANCE,
MissionPhase.FINAL_ACCEPTANCE: MissionPhase.COMPLETE,
}
def _canonical(value: Any) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _sha256(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
@dataclass(frozen=True)
class ResumePolicy:
resume_lease_seconds: int = 120
default_poll_seconds: int = 30
deterministic_block_poll_seconds: int = 300
transient_max_attempts: int = 3
transient_base_delay_seconds: int = 2
transient_max_delay_seconds: int = 60
def __post_init__(self) -> None:
for name in (
"resume_lease_seconds",
"default_poll_seconds",
"deterministic_block_poll_seconds",
"transient_max_attempts",
"transient_base_delay_seconds",
"transient_max_delay_seconds",
):
if getattr(self, name) < 1:
raise ValueError(f"{name}_must_be_positive")
if self.transient_max_delay_seconds < self.transient_base_delay_seconds:
raise ValueError("transient_max_delay_seconds_too_small")
@dataclass(frozen=True)
class MissionObservation:
mission_id: str
generation: int
head_oid: str
material_digest: str
condition_fingerprint: str
effect_state: EffectState = EffectState.ABSENT
requires_reconcile: bool = False
owner_action_resolved: bool = False
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if not self.head_oid:
raise ValueError("head_oid_required")
if not self.material_digest:
raise ValueError("material_digest_required")
if not self.condition_fingerprint:
raise ValueError("condition_fingerprint_required")
@dataclass(frozen=True)
class ResumeLease:
lease_id: str
mission_id: str
generation: int
fencing_token: str
expires_at: float
def __post_init__(self) -> None:
if not self.lease_id:
raise ValueError("lease_id_required")
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if not self.fencing_token:
raise ValueError("fencing_token_required")
@dataclass(frozen=True)
class StepResult:
status: StepStatus
code: str
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
next_phase: MissionPhase | None = None
material_digest: str | None = None
owner_action_payload: Mapping[str, Any] | None = None
def __post_init__(self) -> None:
if not self.code:
raise ValueError("step_code_required")
if not self.condition_fingerprint:
raise ValueError("condition_fingerprint_required")
if self.retry_after_seconds is not None and self.retry_after_seconds < 0:
raise ValueError("retry_after_seconds_must_be_non_negative")
@dataclass(frozen=True)
class MissionCheckpoint:
schema: str
mission_id: str
generation: int
phase: MissionPhase
head_oid: str
material_digest: str
last_condition_fingerprint: str
attempt_count: int
blocked_condition_fingerprint: str | None
reconcile_required: bool
frozen_material_digest: str | None
resume_phase_after_owner: MissionPhase | None
receipt_refs: tuple[str, ...]
updated_at: float
revision: int = 1
@classmethod
def initial(
cls,
observation: MissionObservation,
*,
observed_at: float,
) -> "MissionCheckpoint":
return cls(
schema="aios.autonomous-resume-checkpoint.v1",
mission_id=observation.mission_id,
generation=observation.generation,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
last_condition_fingerprint=observation.condition_fingerprint,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=None,
resume_phase_after_owner=None,
receipt_refs=tuple(observation.evidence_refs),
updated_at=observed_at,
revision=1,
)
def __post_init__(self) -> None:
if self.schema != "aios.autonomous-resume-checkpoint.v1":
raise ValueError("unsupported_checkpoint_schema")
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if self.attempt_count < 0:
raise ValueError("attempt_count_must_be_non_negative")
if self.revision < 1:
raise ValueError("revision_must_be_positive")
@property
def digest(self) -> str:
payload = asdict(self)
payload["phase"] = self.phase.value
payload["resume_phase_after_owner"] = (
None
if self.resume_phase_after_owner is None
else self.resume_phase_after_owner.value
)
payload["receipt_refs"] = list(self.receipt_refs)
return _sha256(_canonical(payload))
@dataclass(frozen=True)
class ControllerOutcome:
status: OutcomeStatus
mission_id: str
phase: MissionPhase
code: str
next_wake_after_seconds: int | None
checkpoint_digest: str | None
receipt_refs: tuple[str, ...] = ()
class AutonomousExecutionPort(Protocol):
"""Adapter boundary to existing AIOS authorities and executors."""
def read_observation(self, mission_id: str) -> MissionObservation: ...
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None: ...
def acquire_resume_lease(
self,
*,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None: ...
def step(
self,
*,
phase: MissionPhase,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
def reconcile(
self,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
*,
lease: ResumeLease,
) -> None: ...
def persist_owner_action_required(
self,
payload: Mapping[str, Any],
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str: ...
def release_resume_lease(
self,
lease: ResumeLease,
*,
outcome_code: str,
) -> None: ...
class AutonomousResumeController:
"""One-cycle deterministic unattended mission controller."""
def __init__(
self,
port: AutonomousExecutionPort,
*,
policy: ResumePolicy | None = None,
clock: Callable[[], float] | None = None,
) -> None:
self.port = port
self.policy = policy or ResumePolicy()
self.clock = clock or time.time
def run_once(self, mission_id: str) -> ControllerOutcome:
if not mission_id:
raise ValueError("mission_id_required")
now = float(self.clock())
observation = self.port.read_observation(mission_id)
checkpoint = self.port.load_checkpoint(mission_id)
if checkpoint is None:
checkpoint = MissionCheckpoint.initial(observation, observed_at=now)
forced_reconcile = False
else:
checkpoint, forced_reconcile = self._validate_and_refresh_checkpoint(
checkpoint,
observation,
now=now,
)
if checkpoint.phase is MissionPhase.COMPLETE:
return self._outcome(
OutcomeStatus.COMPLETE,
checkpoint,
"MISSION_ALREADY_COMPLETE",
next_wake=None,
)
if checkpoint.phase is MissionPhase.OWNER_ACTION_REQUIRED:
if not observation.owner_action_resolved:
return self._outcome(
OutcomeStatus.OWNER_ACTION_REQUIRED,
checkpoint,
"OWNER_ACTION_STILL_REQUIRED",
next_wake=None,
)
resume_phase = (
checkpoint.resume_phase_after_owner
or MissionPhase.S2_ADMISSION
)
checkpoint = replace(
checkpoint,
phase=resume_phase,
resume_phase_after_owner=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
checkpoint.blocked_condition_fingerprint is not None
and checkpoint.blocked_condition_fingerprint
== observation.condition_fingerprint
):
return self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
checkpoint,
"UNCHANGED_DETERMINISTIC_BLOCKER",
next_wake=self.policy.deterministic_block_poll_seconds,
)
if (
checkpoint.blocked_condition_fingerprint is not None
and checkpoint.blocked_condition_fingerprint
!= observation.condition_fingerprint
):
checkpoint = replace(
checkpoint,
blocked_condition_fingerprint=None,
attempt_count=0,
updated_at=now,
revision=checkpoint.revision + 1,
)
lease = self.port.acquire_resume_lease(
mission_id=observation.mission_id,
generation=observation.generation,
idempotency_key=self._resume_idempotency_key(observation),
lease_seconds=self.policy.resume_lease_seconds,
)
if lease is None:
return self._outcome(
OutcomeStatus.ACTIVE_ELSEWHERE,
checkpoint,
"RESUME_LEASE_HELD_ELSEWHERE",
next_wake=self.policy.default_poll_seconds,
)
try:
self._validate_resume_lease(
lease,
observation=observation,
now=now,
)
except Exception:
self.port.release_resume_lease(
lease,
outcome_code="INVALID_RESUME_LEASE",
)
raise
outcome_code = "UNSET"
try:
reconciling = (
forced_reconcile
or checkpoint.reconcile_required
or observation.requires_reconcile
or observation.effect_state is EffectState.UNKNOWN
)
if reconciling:
result = self.port.reconcile(
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
else:
result = self.port.step(
phase=checkpoint.phase,
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
outcome, new_checkpoint = self._apply_result(
result=result,
observation=observation,
checkpoint=checkpoint,
now=now,
lease=lease,
reconciling=reconciling,
)
outcome_code = outcome.code
self.port.persist_checkpoint(new_checkpoint, lease=lease)
return replace(
outcome,
checkpoint_digest=new_checkpoint.digest,
)
finally:
self.port.release_resume_lease(
lease,
outcome_code=outcome_code,
)
def _validate_and_refresh_checkpoint(
self,
checkpoint: MissionCheckpoint,
observation: MissionObservation,
*,
now: float,
) -> tuple[MissionCheckpoint, bool]:
if checkpoint.mission_id != observation.mission_id:
raise ValueError("checkpoint_mission_mismatch")
if checkpoint.generation > observation.generation:
raise ValueError("checkpoint_generation_ahead_of_authority")
refreshed = checkpoint
forced_reconcile = False
if checkpoint.generation < observation.generation:
refreshed = replace(
checkpoint,
generation=observation.generation,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
last_condition_fingerprint=observation.condition_fingerprint,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=None,
resume_phase_after_owner=None,
receipt_refs=self._merge_refs(
checkpoint.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
refreshed.phase in {
MissionPhase.RELEASE_READY,
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and refreshed.frozen_material_digest is not None
and refreshed.frozen_material_digest
!= observation.material_digest
):
# Material changed after READY. Promotion/final may already have
# observable side effects, so reconcile before rebuilding PREPARE.
previous_phase = refreshed.phase
force_for_effect = (
previous_phase in {
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and observation.effect_state in {
EffectState.ACTIVE,
EffectState.UNKNOWN,
}
)
refreshed = replace(
refreshed,
phase=MissionPhase.RELEASE_READY,
material_digest=observation.material_digest,
head_oid=observation.head_oid,
frozen_material_digest=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=force_for_effect,
last_condition_fingerprint=observation.condition_fingerprint,
receipt_refs=self._merge_refs(
refreshed.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=refreshed.revision + 1,
)
forced_reconcile = forced_reconcile or force_for_effect
elif (
refreshed.phase in {
MissionPhase.S2_ADMISSION,
MissionPhase.S2_EXECUTION,
MissionPhase.GATE0,
}
and refreshed.head_oid != observation.head_oid
):
# Source moved before READY. Re-enter S2 admission/replan.
refreshed = replace(
refreshed,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
frozen_material_digest=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
last_condition_fingerprint=observation.condition_fingerprint,
receipt_refs=self._merge_refs(
refreshed.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=refreshed.revision + 1,
)
return refreshed, forced_reconcile
def _apply_result(
self,
*,
result: StepResult,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
now: float,
lease: ResumeLease,
reconciling: bool,
) -> tuple[ControllerOutcome, MissionCheckpoint]:
refs = self._merge_refs(
checkpoint.receipt_refs,
observation.evidence_refs,
result.receipt_refs,
)
base = replace(
checkpoint,
head_oid=observation.head_oid,
material_digest=(
result.material_digest
or observation.material_digest
),
last_condition_fingerprint=result.condition_fingerprint,
receipt_refs=refs,
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
result.status is StepStatus.SUCCEEDED
and checkpoint.phase in {
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and checkpoint.frozen_material_digest is not None
and result.material_digest is not None
and result.material_digest != checkpoint.frozen_material_digest
):
new_checkpoint = replace(
base,
phase=MissionPhase.RELEASE_READY,
material_digest=result.material_digest,
frozen_material_digest=None,
reconcile_required=True,
attempt_count=0,
blocked_condition_fingerprint=None,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
"RECONCILE_REQUIRED:MATERIAL_CHANGED_DURING_PROMOTION",
next_wake=0,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.SUCCEEDED:
next_phase = (
result.next_phase
or _PHASE_NEXT.get(checkpoint.phase, checkpoint.phase)
)
frozen = base.frozen_material_digest
if checkpoint.phase is MissionPhase.RELEASE_READY:
frozen = result.material_digest or observation.material_digest
new_checkpoint = replace(
base,
phase=next_phase,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=frozen,
)
status = (
OutcomeStatus.COMPLETE
if next_phase is MissionPhase.COMPLETE
else OutcomeStatus.PROGRESSED
)
code = (
"MISSION_COMPLETE"
if next_phase is MissionPhase.COMPLETE
else result.code
)
return (
self._outcome(
status,
new_checkpoint,
code,
next_wake=(
None
if status is OutcomeStatus.COMPLETE
else self.policy.default_poll_seconds
),
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.IN_PROGRESS:
new_checkpoint = replace(
base,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
result.code,
next_wake=(
result.retry_after_seconds
if result.retry_after_seconds is not None
else self.policy.default_poll_seconds
),
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.RETRYABLE:
attempts = checkpoint.attempt_count + 1
if attempts > self.policy.transient_max_attempts:
new_checkpoint = replace(
base,
attempt_count=attempts,
blocked_condition_fingerprint=result.condition_fingerprint,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
new_checkpoint,
"RETRY_BUDGET_EXHAUSTED",
next_wake=self.policy.deterministic_block_poll_seconds,
refs=result.receipt_refs,
),
new_checkpoint,
)
delay = (
result.retry_after_seconds
if result.retry_after_seconds is not None
else min(
self.policy.transient_base_delay_seconds
* (2 ** (attempts - 1)),
self.policy.transient_max_delay_seconds,
)
)
new_checkpoint = replace(
base,
attempt_count=attempts,
blocked_condition_fingerprint=None,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.RETRY_SCHEDULED,
new_checkpoint,
result.code,
next_wake=delay,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.BLOCKED_DETERMINISTIC:
new_checkpoint = replace(
base,
attempt_count=checkpoint.attempt_count,
blocked_condition_fingerprint=result.condition_fingerprint,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
new_checkpoint,
result.code,
next_wake=self.policy.deterministic_block_poll_seconds,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.BLOCKED_OWNER:
payload = dict(result.owner_action_payload or {})
payload.setdefault("mission_id", observation.mission_id)
payload.setdefault("generation", observation.generation)
payload.setdefault("phase", checkpoint.phase.value)
payload.setdefault("code", result.code)
record_ref = self.port.persist_owner_action_required(
payload,
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
if not record_ref:
raise ValueError("owner_action_record_ref_required")
new_checkpoint = replace(
base,
phase=MissionPhase.OWNER_ACTION_REQUIRED,
resume_phase_after_owner=checkpoint.phase,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
receipt_refs=self._merge_refs(refs, (record_ref,)),
)
return (
self._outcome(
OutcomeStatus.OWNER_ACTION_REQUIRED,
new_checkpoint,
result.code,
next_wake=None,
refs=(record_ref,),
),
new_checkpoint,
)
if result.status is StepStatus.RECONCILE:
# The next scheduler cycle must reconcile before any retry.
new_checkpoint = replace(
base,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=True,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
"RECONCILE_REQUIRED:" + result.code,
next_wake=0,
refs=result.receipt_refs,
),
new_checkpoint,
)
raise AssertionError(f"unsupported_step_status:{result.status}")
@staticmethod
def _validate_resume_lease(
lease: ResumeLease,
*,
observation: MissionObservation,
now: float,
) -> None:
if lease.mission_id != observation.mission_id:
raise ValueError("resume_lease_mission_mismatch")
if lease.generation != observation.generation:
raise ValueError("resume_lease_generation_mismatch")
if lease.expires_at <= now:
raise ValueError("resume_lease_expired")
def _resume_idempotency_key(
self,
observation: MissionObservation,
) -> str:
payload = {
"schema": "aios.resume-effect.v1",
"mission_id": observation.mission_id,
"generation": observation.generation,
}
return "resume:sha256:" + _sha256(_canonical(payload))
@staticmethod
def _merge_refs(*groups: Sequence[str]) -> tuple[str, ...]:
merged: list[str] = []
seen: set[str] = set()
for group in groups:
for item in group:
value = str(item)
if value and value not in seen:
seen.add(value)
merged.append(value)
return tuple(merged)
@staticmethod
def _outcome(
status: OutcomeStatus,
checkpoint: MissionCheckpoint,
code: str,
*,
next_wake: int | None,
refs: Sequence[str] = (),
) -> ControllerOutcome:
return ControllerOutcome(
status=status,
mission_id=checkpoint.mission_id,
phase=checkpoint.phase,
code=code,
next_wake_after_seconds=next_wake,
checkpoint_digest=None,
receipt_refs=tuple(refs),
)
@@ -1,209 +0,0 @@
"""Thin callable adapter for :mod:`autonomous_resume_controller`.
This module contains no transport, daemon, database or scheduler loop. It only
binds current AIOS runtime functions to the stable ``AutonomousExecutionPort``
contract and exposes one scheduler tick.
A field integrator should:
- resolve the live current APIs;
- supply callables that use existing governed authorities;
- register ``AutonomousSchedulerTick.run`` with the existing scheduler/timer.
No additional control plane is created here.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from .autonomous_resume_controller import (
AutonomousExecutionPort,
AutonomousResumeController,
ControllerOutcome,
MissionCheckpoint,
MissionObservation,
MissionPhase,
ResumeLease,
StepResult,
)
PhaseHandler = Callable[
[MissionObservation, MissionCheckpoint, ResumeLease],
StepResult,
]
ReconcileHandler = PhaseHandler
@dataclass(frozen=True)
class PhaseCallbacks:
admission: PhaseHandler
source_execution: PhaseHandler
gate0: PhaseHandler
release_ready: PhaseHandler
promotion: PhaseHandler
final_acceptance: PhaseHandler
def for_phase(self, phase: MissionPhase) -> PhaseHandler:
mapping = {
MissionPhase.S2_ADMISSION: self.admission,
MissionPhase.S2_EXECUTION: self.source_execution,
MissionPhase.GATE0: self.gate0,
MissionPhase.RELEASE_READY: self.release_ready,
MissionPhase.PROMOTION: self.promotion,
MissionPhase.FINAL_ACCEPTANCE: self.final_acceptance,
}
try:
return mapping[phase]
except KeyError as exc:
raise ValueError(f"phase_has_no_executor:{phase.value}") from exc
@dataclass(frozen=True)
class AuthorityCallbacks:
read_observation: Callable[[str], MissionObservation]
load_checkpoint: Callable[[str], MissionCheckpoint | None]
acquire_resume_lease: Callable[[str, int, str, int], ResumeLease | None]
persist_checkpoint: Callable[[MissionCheckpoint, ResumeLease], None]
persist_owner_action_required: Callable[
[dict, MissionObservation, MissionCheckpoint, ResumeLease],
str,
]
release_resume_lease: Callable[[ResumeLease, str], None]
reconcile: ReconcileHandler
class CallableAutonomousExecutionPort(AutonomousExecutionPort):
"""Runtime adapter composed from already-governed AIOS callables."""
def __init__(
self,
authority: AuthorityCallbacks,
phases: PhaseCallbacks,
) -> None:
self.authority = authority
self.phases = phases
def read_observation(self, mission_id: str) -> MissionObservation:
value = self.authority.read_observation(mission_id)
if not isinstance(value, MissionObservation):
raise TypeError("read_observation_must_return_MissionObservation")
return value
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None:
value = self.authority.load_checkpoint(mission_id)
if value is not None and not isinstance(value, MissionCheckpoint):
raise TypeError("load_checkpoint_must_return_checkpoint_or_none")
return value
def acquire_resume_lease(
self,
*,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None:
value = self.authority.acquire_resume_lease(
mission_id,
generation,
idempotency_key,
lease_seconds,
)
if value is not None and not isinstance(value, ResumeLease):
raise TypeError("acquire_resume_lease_must_return_lease_or_none")
return value
def step(
self,
*,
phase: MissionPhase,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
handler = self.phases.for_phase(phase)
value = handler(observation, checkpoint, lease)
if not isinstance(value, StepResult):
raise TypeError("phase_handler_must_return_StepResult")
return value
def reconcile(
self,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
value = self.authority.reconcile(
observation,
checkpoint,
lease,
)
if not isinstance(value, StepResult):
raise TypeError("reconcile_must_return_StepResult")
return value
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
*,
lease: ResumeLease,
) -> None:
self.authority.persist_checkpoint(checkpoint, lease)
def persist_owner_action_required(
self,
payload,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str:
value = self.authority.persist_owner_action_required(
dict(payload),
observation,
checkpoint,
lease,
)
if not isinstance(value, str) or not value:
raise ValueError("owner_action_record_ref_required")
return value
def release_resume_lease(
self,
lease: ResumeLease,
*,
outcome_code: str,
) -> None:
self.authority.release_resume_lease(lease, outcome_code)
@dataclass(frozen=True)
class SchedulerTickResult:
outcome: ControllerOutcome
next_wake_after_seconds: int | None
class AutonomousSchedulerTick:
"""One invocation for an existing scheduler/timer.
This object never sleeps or loops. The host scheduler owns cadence.
"""
def __init__(
self,
controller: AutonomousResumeController,
mission_id: str,
) -> None:
if not mission_id:
raise ValueError("mission_id_required")
self.controller = controller
self.mission_id = mission_id
def run(self) -> SchedulerTickResult:
outcome = self.controller.run_once(self.mission_id)
return SchedulerTickResult(
outcome=outcome,
next_wake_after_seconds=outcome.next_wake_after_seconds,
)
@@ -1,462 +0,0 @@
"""Explicit C6 composition in the existing TaskPlane host, default disabled.
All authorities are host-owned dependencies. No request can register an
executor, construct a worker identity, or turn an observation into authority.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import threading
from typing import Callable, Literal, Protocol
from aios_capability_contract import has_capabilities as formal_has_capabilities, normalize_required_capabilities
from .aro_readonly import AroReadonlyTick
from .authorization_planner import CapabilityNamespace, CapabilityRef, GitManifestRegistryReader, authorization_scope_digest
from .auto_claim import AutoClaimCoordinator, DiscoveryResult
from .errors import ForbiddenError
from .operation_readback import OperationReaders, collect_closure, collect_observation
from .operation_view import (ClosureObservation, OperationBinding, PreflightResult, ClosureResult,
binding_digest, derive_closure, derive_preflight, reconcile_effect, validate_digest, validate_oid)
from .schedulability import MemoryProofRepository, SchedulabilityService
from .schedulability_store_adapter import (TaskPlaneSchedulabilityAuthority,
TaskPlaneClaimReceiptV3, ScopedLeaseController, validate_task_plane_receipt)
@dataclass(frozen=True)
class AuthenticatedWorker:
worker_id: str
actor_identity: str
authority_receipt_ref: str
expires_at: datetime
def __post_init__(self):
if any(not isinstance(v, str) or not v.strip() for v in
(self.worker_id, self.actor_identity, self.authority_receipt_ref)):
raise ValueError("authenticated worker receipt required")
if not isinstance(self.expires_at, datetime) or self.expires_at.tzinfo is None:
raise ValueError("worker expiry must be timezone aware")
@dataclass(frozen=True)
class OperationCapabilityBinding:
operation_id: str
namespace: CapabilityNamespace
capability_id: str
registry_oid: str
manifest_id: str
manifest_revision: str
def __post_init__(self):
validate_oid(self.registry_oid)
if not isinstance(self.namespace, CapabilityNamespace):
raise ValueError("explicit capability namespace required")
if any(not isinstance(v, str) or not v.strip() for v in
(self.operation_id, self.capability_id, self.manifest_id, self.manifest_revision)):
raise ValueError("operation capability identity required")
@dataclass(frozen=True)
class DurableExecutionIntent:
binding_digest: str
effect_key: str
claim_id: str
receipt_ref: str
def __post_init__(self):
validate_digest(self.binding_digest)
if any(not isinstance(v, str) or not v.strip() for v in
(self.effect_key, self.claim_id, self.receipt_ref)):
raise ValueError("exact durable execution intent required")
class GovernedSourcePatchPilot(Protocol):
"""Existing governed writer/executor adapter, never a general shell port.
Implementation must revalidate Root, revocation, scope, generation and
WriterLease/CAS at write linearization, and persist intent before effect.
Readback uses the same immutable effect key through OperationReaders.
"""
def prepare_intent(self, *, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3) -> DurableExecutionIntent: ...
def execute(self, *, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3,
intent: DurableExecutionIntent) -> None: ...
def reconcile(self, *, binding: OperationBinding,
intent: DurableExecutionIntent) -> None: ...
@dataclass(frozen=True)
class C6Bindings:
claim_authority: TaskPlaneSchedulabilityAuthority
registry_reader: GitManifestRegistryReader
registry_oid: str
registry_digest: str
operation_capabilities: tuple[OperationCapabilityBinding, ...]
operation_readers: OperationReaders
worker_provider: Callable[[], AuthenticatedWorker | None]
operation_provider: Callable[[str, AuthenticatedWorker], OperationBinding]
pilot: GovernedSourcePatchPilot | None = None
aro_tick: AroReadonlyTick | None = None
enabled: bool = False
request_worker_provider: Callable[[str, str], AuthenticatedWorker | None] | None = None
# Readers are injected from the host's existing durable intent and worker
# authority. A provider error is UNKNOWN, never an absent record.
intent_reader: Callable[[OperationBinding], DurableExecutionIntent | None] | None = None
pending_intent_provider: Callable[
[], tuple[tuple[OperationBinding, DurableExecutionIntent], ...]
] | None = None
pending_intent_completion: Callable[
[OperationBinding, DurableExecutionIntent, ClosureObservation], None
] | None = None
worker_authority_verifier: Callable[[AuthenticatedWorker, OperationBinding], bool] | None = None
@dataclass(frozen=True)
class C6TickResult:
status: Literal["DISABLED", "NO_CANDIDATES", "CLAIMED", "OBSERVED", "BLOCKED", "UNKNOWN"]
reason_code: str
discovery: DiscoveryResult | None = None
claim: TaskPlaneClaimReceiptV3 | None = None
preflight: PreflightResult | None = None
closure: ClosureResult | None = None
audit_report_ref: str | None = None
evidence_refs: tuple[str, ...] = ()
class C6Runtime:
def __init__(self, service, bindings: C6Bindings, *, clock=None):
if not isinstance(bindings, C6Bindings) or type(bindings.enabled) is not bool:
raise ValueError("explicit C6 bindings required")
if not isinstance(bindings.claim_authority, TaskPlaneSchedulabilityAuthority) or bindings.claim_authority.store is not service.store:
raise ValueError("C6 must share the host's existing TaskPlane authority")
if not callable(bindings.worker_provider) or not callable(bindings.operation_provider):
raise ValueError("host authenticated worker and operation providers required")
if bindings.enabled and not service.policy.execution_enabled:
raise ValueError("execution policy and explicit trust must be enabled first")
if bindings.pilot is not None and not all(callable(getattr(bindings.pilot, method, None)) for method in ("prepare_intent", "execute", "reconcile")):
raise ValueError("governed source patch pilot required")
if bindings.enabled and bindings.pilot is not None and not all(
callable(value)
for value in (
bindings.intent_reader,
bindings.pending_intent_provider,
bindings.pending_intent_completion,
bindings.worker_authority_verifier,
)
):
raise ValueError(
"enabled C6 pilot requires intent, recovery, and worker authority providers"
)
validate_oid(bindings.registry_oid); validate_digest(bindings.registry_digest)
if not isinstance(bindings.operation_capabilities, tuple) or not bindings.operation_capabilities:
raise ValueError("immutable operation capability map required")
self.service, self.bindings = service, bindings
self.clock = clock or (lambda: datetime.now(timezone.utc))
self._lock = threading.Lock()
self._closed = False
self._pending: OperationBinding | None = None
self._validate_map()
self.schedulability = SchedulabilityService(bindings.claim_authority,
MemoryProofRepository(), clock=lambda: self.clock().timestamp(),
capability_matcher=bindings.claim_authority.capability_matcher)
self.coordinator = AutoClaimCoordinator(self.schedulability)
self.lease_controller = ScopedLeaseController(service.store, bindings.claim_authority.binding)
def _validate_map(self):
manifest = self.bindings.registry_reader.read_manifest(
expected_registry_oid=self.bindings.registry_oid,
expected_manifest_sha256=self.bindings.registry_digest)
seen = set()
for item in self.bindings.operation_capabilities:
key = (item.operation_id, item.namespace)
if key in seen or item.registry_oid != self.bindings.registry_oid or item.manifest_id != manifest.manifest_id or item.manifest_revision != manifest.revision:
raise ValueError("operation capability map revision conflict")
if CapabilityRef(item.namespace, item.capability_id) not in manifest.capabilities:
raise ValueError("operation capability not declared in authoritative manifest")
if item.namespace == CapabilityNamespace.AGENT_TASK and any(
value.startswith("exact:") for value in normalize_required_capabilities([item.capability_id])):
raise ValueError("unknown formal operation capability")
seen.add(key)
return manifest
def _worker(self, worker=None):
worker = self.bindings.worker_provider() if worker is None else worker
if not isinstance(worker, AuthenticatedWorker) or worker.expires_at <= self.clock():
raise ForbiddenError("fresh authenticated worker required")
registered = self.service.store.get_worker(worker.worker_id)
if registered.identity != worker.actor_identity or registered.disabled:
raise ForbiddenError("registered worker identity mismatch")
self.service.policy.assert_allowed("claim", worker.actor_identity)
return worker
def _prepare(self, task_id, worker):
manifest = self._validate_map()
binding = self.bindings.operation_provider(task_id, worker)
if not isinstance(binding, OperationBinding) or binding.task_id != task_id or binding.actor_id != worker.actor_identity or binding.source_oid != manifest.source_oid:
raise ForbiddenError("operation identity mismatch")
if binding.operation_id != "governed_source_patch_apply":
raise ForbiddenError("operation outside C6 pilot")
task = self.service.store.get_task(task_id)
metadata = task.task_plan.metadata
if (metadata.get("c6_operation_binding_digest") != binding_digest(binding)
or metadata.get("generation") != binding.generation
or binding.policy_digest != manifest.declaration_digest
or binding.scope_digest not in {authorization_scope_digest(scope) for scope in manifest.allowed_scopes}):
raise ForbiddenError("operation scope or immutable TaskPlan binding mismatch")
self.service.policy.assert_allowed("claim", worker.actor_identity, task=task)
required = task.task_plan.required_capabilities
if binding.capability_namespace == CapabilityNamespace.AGENT_TASK.value:
registered = self.service.store.get_worker(worker.worker_id)
if (not formal_has_capabilities(registered.capabilities, [binding.capability_id])
or not normalize_required_capabilities([binding.capability_id]).issubset(
normalize_required_capabilities(required))):
raise ForbiddenError("formal capability cannot use wildcard worker authority")
elif binding.capability_id not in required:
raise ForbiddenError("operation capability absent from TaskPlan")
verifier = self.bindings.worker_authority_verifier
if verifier is None or verifier(worker, binding) is not True:
raise ForbiddenError("worker receipt not bound to operation authority")
if not any(item.operation_id == binding.operation_id and item.namespace.value == binding.capability_namespace and item.capability_id == binding.capability_id for item in self.bindings.operation_capabilities):
raise ForbiddenError("operation capability namespace mismatch")
preflight = derive_preflight(collect_observation(binding, self.bindings.operation_readers, now=self.clock()), now=self.clock())
return binding, preflight
def _intent(self, binding):
reader = self.bindings.intent_reader
if reader is None:
raise ForbiddenError("durable execution intent reader required")
intent = reader(binding)
if intent is not None and (not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key):
raise ForbiddenError("durable execution intent binding mismatch")
return intent
def _durable_pending(self):
provider = self.bindings.pending_intent_provider
if provider is None:
raise ForbiddenError("durable pending intent provider required")
values = provider()
if not isinstance(values, tuple) or len(values) > 16:
raise ForbiddenError("bounded durable pending intent enumeration required")
result = []
seen = set()
for value in values:
if not isinstance(value, tuple) or len(value) != 2:
raise ForbiddenError("durable pending intent entry is invalid")
binding, intent = value
if (
not isinstance(binding, OperationBinding)
or not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
or binding.effect_key in seen
):
raise ForbiddenError("durable pending intent binding mismatch")
seen.add(binding.effect_key)
result.append((binding, intent))
return tuple(result)
def _reconcile_pending(self, binding, intent, worker):
current, _preflight = self._prepare(binding.task_id, worker)
if current != binding or self._intent(binding) != intent:
raise ForbiddenError("durable pending authority changed")
assert self.bindings.pilot is not None
try:
self.bindings.pilot.reconcile(binding=binding, intent=intent)
except Exception:
pass
observation = collect_closure(
binding, self.bindings.operation_readers, now=self.clock()
)
return observation, derive_closure(binding, observation, now=self.clock())
def renew_claim(self, *, task_id, claim_id, actor_identity, receipt, lease_seconds):
if self._closed or not self.bindings.enabled or not isinstance(receipt, dict):
raise ForbiddenError("C6 v3 claim receipt required")
if receipt.get("task_id") != task_id or receipt.get("claim_id") != claim_id:
raise ForbiddenError("C6 claim receipt identity mismatch")
provider = self.bindings.request_worker_provider
worker_id = receipt.get("worker_id")
candidate = provider(worker_id, actor_identity) if provider is not None else None
if candidate is None:
raise ForbiddenError("authenticated request worker required")
worker = self._worker(candidate)
if (worker.worker_id, worker.actor_identity) != (worker_id, actor_identity):
raise ForbiddenError("request worker identity mismatch")
self._prepare(task_id, worker)
return self.lease_controller.renew(receipt, worker_id=worker_id,
actor_identity=actor_identity, lease_seconds=lease_seconds)
def claim_task(self, *, task_id: str, worker_id: str, actor_identity: str, lease_seconds: int = 30):
"""Manual and automatic host claims share this exact authority seam."""
if self._closed or not self.bindings.enabled or self.bindings.pilot is None:
raise ForbiddenError("C6 claim binding disabled or unavailable")
provider = self.bindings.request_worker_provider
if provider is None:
raise ForbiddenError("authenticated request worker provider required")
candidate = provider(worker_id, actor_identity)
if candidate is None:
raise ForbiddenError("authenticated request worker unavailable")
worker = self._worker(candidate)
if (worker.worker_id, worker.actor_identity) != (worker_id, actor_identity):
raise ForbiddenError("worker provider binding mismatch")
binding, preflight = self._prepare(task_id, worker)
return self._claim_prepared(binding, preflight, worker, lease_seconds=lease_seconds)
def _claim_prepared(self, binding, preflight, worker, *, lease_seconds=30):
task_id, worker_id, actor_identity = binding.task_id, worker.worker_id, worker.actor_identity
if preflight.state != "READY":
raise ForbiddenError("C6 preflight: " + preflight.reason_code)
if self._intent(binding) is not None:
raise ForbiddenError("existing execution intent requires readback")
closure = collect_closure(binding, self.bindings.operation_readers, now=self.clock())
if reconcile_effect(closure.effect, binding=binding, now=self.clock()) != "REVALIDATE_BEFORE_RETRY":
raise ForbiddenError("C6 effect must be authoritatively absent before claim")
proof = self.schedulability.preflight(task_id=task_id, worker_id=worker_id,
actor_identity=actor_identity, persist=True)
if proof.observation.scope_digest != binding.scope_digest:
raise ForbiddenError("claim authority scope differs from operation scope")
result = self.coordinator.claim(proof_id=proof.proof_id, worker_id=worker_id,
actor_identity=actor_identity, lease_seconds=lease_seconds)
parsed = validate_task_plane_receipt(result, proof=proof)
return parsed
def tick(self) -> C6TickResult:
if self._closed or not self.bindings.enabled:
return C6TickResult("DISABLED", "C6_DISABLED")
if not self._lock.acquire(blocking=False):
return C6TickResult("BLOCKED", "LOCAL_TICK_IN_PROGRESS")
try:
audit = self.bindings.aro_tick.run_due() if self.bindings.aro_tick else None
if self.bindings.pilot is None:
return C6TickResult("BLOCKED", "EXECUTION_ENTRYPOINT_ABSENT", audit_report_ref=audit)
if self.bindings.pending_intent_provider is None:
return C6TickResult(
"BLOCKED", "DURABLE_PENDING_INTENT_PROVIDER_ABSENT",
audit_report_ref=audit,
)
# Restart recovery is ledger-first. It does not depend on the Task
# remaining discoverable and it never calls execute/resubmits.
pending = self._durable_pending()
if pending:
worker = self._worker()
closures = []
for binding, intent in pending:
observation, closed = self._reconcile_pending(binding, intent, worker)
closures.append(closed)
if closed.outcome == "SUCCEEDED":
assert self.bindings.pending_intent_completion is not None
self.bindings.pending_intent_completion(
binding, intent, observation
)
all_succeeded = all(item.outcome == "SUCCEEDED" for item in closures)
return C6TickResult(
"OBSERVED" if all_succeeded else "UNKNOWN",
"DURABLE_PENDING_INTENTS_RECONCILED"
if all_succeeded
else "DURABLE_PENDING_INTENT_READBACK",
closure=closures[-1],
audit_report_ref=audit,
)
if self._pending is not None:
# ACK loss is sticky. Reconciliation is a distinct, pure
# owner seam: it may observe/reconcile the exact durable action
# but may never acquire a new WriterLease or resend the commit.
intent = self._intent(self._pending)
if intent is None:
return C6TickResult("UNKNOWN", "PENDING_INTENT_UNAVAILABLE",
audit_report_ref=audit)
worker = self._worker()
observation, closure = self._reconcile_pending(
self._pending, intent, worker
)
if closure.outcome == "SUCCEEDED":
assert self.bindings.pending_intent_completion is not None
self.bindings.pending_intent_completion(
self._pending, intent, observation
)
self._pending = None
return C6TickResult("OBSERVED" if closure.outcome == "SUCCEEDED" else "UNKNOWN",
"PENDING_EFFECT_READBACK", closure=closure, audit_report_ref=audit)
worker = self._worker()
discovered = self.coordinator.discover(worker_id=worker.worker_id, actor_identity=worker.actor_identity)
if discovered.proof is None:
return C6TickResult("NO_CANDIDATES" if discovered.candidate_count == 0 else "BLOCKED",
discovered.status, discovery=discovered, audit_report_ref=audit)
task_id = discovered.proof.task_id
binding, preflight = self._prepare(task_id, worker)
if self._intent(binding) is not None:
self._pending = binding
intent = self._intent(binding)
try:
if intent is not None:
self.bindings.pilot.reconcile(binding=binding, intent=intent)
except Exception:
pass
closed = derive_closure(binding, collect_closure(binding,
self.bindings.operation_readers, now=self.clock()), now=self.clock())
return C6TickResult("OBSERVED" if closed.outcome == "SUCCEEDED" else "UNKNOWN",
"DURABLE_INTENT_READBACK", discovered, closure=closed, audit_report_ref=audit)
if preflight.state != "READY":
return C6TickResult("BLOCKED", preflight.reason_code, discovered, preflight=preflight, audit_report_ref=audit)
claim = self._claim_prepared(binding, preflight, worker)
# Re-read every precondition immediately before the effect. The
# executor independently enforces its linearization-point fences.
binding_after, refreshed = self._prepare(task_id, worker)
self._worker(worker)
if binding_after != binding or refreshed.state != "READY" or self._closed:
return C6TickResult("BLOCKED", "PRE_EFFECT_BINDING_CHANGED", discovered, claim, refreshed)
try:
with self.lease_controller.execution_guard(claim.to_dict(),
worker_id=worker.worker_id, actor_identity=worker.actor_identity):
binding_after, refreshed = self._prepare(task_id, self._worker(worker))
effect = collect_closure(binding, self.bindings.operation_readers, now=self.clock()).effect
if (binding_after != binding or refreshed.state != "READY" or self._closed
or reconcile_effect(effect, binding=binding, now=self.clock()) != "REVALIDATE_BEFORE_RETRY"
or self._intent(binding) is not None):
return C6TickResult("BLOCKED", "PRE_EFFECT_AUTHORITY_CHANGED", discovered, claim, refreshed)
self._pending = binding
intent = self.bindings.pilot.prepare_intent(binding=binding, claim=claim)
observed_intent = self._intent(binding)
if (not isinstance(intent, DurableExecutionIntent) or intent != observed_intent
or intent.claim_id != claim.claim_id):
raise ForbiddenError("execution intent not durably observed")
# Intent persistence may take time: acquire a new guard and
# read authority/effect again before calling the governed writer.
with self.lease_controller.execution_guard(claim.to_dict(),
worker_id=worker.worker_id, actor_identity=worker.actor_identity):
after, ready = self._prepare(task_id, self._worker(worker))
effect = collect_closure(binding, self.bindings.operation_readers, now=self.clock()).effect
if (after != binding or ready.state != "READY" or self._closed
or self._intent(binding) != intent
or reconcile_effect(effect, binding=binding, now=self.clock()) != "REVALIDATE_BEFORE_RETRY"):
raise ForbiddenError("pre-effect authority changed during intent persistence")
self.bindings.pilot.execute(binding=binding, claim=claim, intent=intent)
except Exception:
return C6TickResult("UNKNOWN", "EXECUTION_OUTCOME_UNKNOWN", discovered, claim, refreshed)
observed = collect_closure(binding, self.bindings.operation_readers, now=self.clock())
closed = derive_closure(binding, observed, now=self.clock())
if closed.outcome == "SUCCEEDED":
assert self.bindings.pending_intent_completion is not None
self.bindings.pending_intent_completion(binding, intent, observed)
self._pending = None
return C6TickResult("OBSERVED" if closed.outcome == "SUCCEEDED" else "UNKNOWN",
"THREE_LAYER_CLOSURE" if closed.outcome == "SUCCEEDED" else "CLOSURE_INCOMPLETE",
discovered, claim, refreshed, closed, audit)
except Exception:
return C6TickResult("UNKNOWN", "AUTHORITY_OR_OBSERVATION_UNAVAILABLE")
finally:
self._lock.release()
def close(self):
# Local tick lifecycle only. Authority leases are never implicitly
# released or renewed by closing an observer/coordinator.
self._closed = True
def bind_c6(service, bindings: C6Bindings, *, clock=None) -> C6Runtime:
if getattr(service, "c6_runtime", None) is not None:
raise ValueError("C6 runtime already bound")
runtime = C6Runtime(service, bindings, clock=clock)
service.c6_runtime = runtime
return runtime
@@ -1,121 +0,0 @@
"""Pure six-value C6 decision explanation policy.
The result is advice over already-read facts. It is not an authorization,
does not map implicitly to legacy NextAction, and owns no write capability.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Literal
RootState = Literal["VALID", "MISSING", "EXPIRED", "REVOKED", "UNKNOWN"]
class DecisionKind(str, Enum):
AUTO_FIX = "AUTO_FIX"
PLAN_ONLY = "PLAN_ONLY"
OWNER_APPROVAL_REQUIRED = "OWNER_APPROVAL_REQUIRED"
HARD_BLOCK = "HARD_BLOCK"
WAITING_ROOT_AUTHORIZATION = "WAITING_ROOT_AUTHORIZATION"
BLOCKED_WITH_EVIDENCE = "BLOCKED_WITH_EVIDENCE"
def _evidence_refs(value: object) -> tuple[str, ...]:
if not isinstance(value, tuple):
raise ValueError("evidence_refs must be a tuple")
for ref in value:
if not isinstance(ref, str) or not ref or ref != ref.strip() or "\x00" in ref:
raise ValueError("invalid evidence reference")
return value
@dataclass(frozen=True)
class DecisionInput:
root_state: RootState
facts_complete: bool
scope_covered: bool
policy_denied: bool
proposal_only: bool
executor_ready: bool
aro_phase_a: bool
evidence_refs: tuple[str, ...]
def __post_init__(self) -> None:
if self.root_state not in ("VALID", "MISSING", "EXPIRED", "REVOKED", "UNKNOWN"):
raise ValueError("invalid root_state")
for name in (
"facts_complete",
"scope_covered",
"policy_denied",
"proposal_only",
"executor_ready",
"aro_phase_a",
):
if type(getattr(self, name)) is not bool:
raise ValueError(f"{name} must be bool")
_evidence_refs(self.evidence_refs)
@dataclass(frozen=True)
class Decision:
kind: DecisionKind
reason: str
evidence_refs: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.kind, DecisionKind):
raise ValueError("kind must be DecisionKind")
if not isinstance(self.reason, str) or not self.reason:
raise ValueError("reason must be non-empty")
_evidence_refs(self.evidence_refs)
def decide(value: DecisionInput) -> Decision:
if not isinstance(value, DecisionInput):
raise TypeError("value must be DecisionInput")
if value.policy_denied or value.root_state == "REVOKED":
return Decision(
DecisionKind.HARD_BLOCK,
"POLICY_DENIED_OR_ROOT_REVOKED",
value.evidence_refs,
)
if not value.facts_complete or value.root_state == "UNKNOWN":
return Decision(
DecisionKind.BLOCKED_WITH_EVIDENCE,
"CRITICAL_FACTS_INCOMPLETE",
value.evidence_refs,
)
if value.root_state in ("MISSING", "EXPIRED"):
return Decision(
DecisionKind.WAITING_ROOT_AUTHORIZATION,
"ROOT_AUTHORIZATION_MISSING_OR_EXPIRED",
value.evidence_refs,
)
if not value.scope_covered:
return Decision(
DecisionKind.OWNER_APPROVAL_REQUIRED,
"SCOPE_OR_BUDGET_REQUIRES_OWNER_DECISION",
value.evidence_refs,
)
if value.proposal_only or value.aro_phase_a:
return Decision(
DecisionKind.PLAN_ONLY,
"NON_EXECUTABLE_PLAN_OR_READONLY_ARO",
value.evidence_refs,
)
if not value.executor_ready:
return Decision(
DecisionKind.BLOCKED_WITH_EVIDENCE,
"GOVERNED_EXECUTOR_NOT_READY",
value.evidence_refs,
)
return Decision(
DecisionKind.AUTO_FIX,
"EXISTING_AUTHORIZED_SCOPE_ELIGIBLE_FOR_GOVERNED_EXECUTION",
value.evidence_refs,
)
__all__ = ["Decision", "DecisionInput", "DecisionKind", "decide"]
+2 -16
View File
@@ -31,16 +31,7 @@ class DelegationEngine:
current = self.store.parent_of(current)
return True
def delegate(
self,
parent_id: str,
child_plan: TaskPlan,
*,
caller: str,
owner_principal_id: str = "",
mission_id: str = "",
created_at: float | None = None,
) -> TaskRecord:
def delegate(self, parent_id: str, child_plan: TaskPlan, *, caller: str) -> TaskRecord:
self.can_delegate(parent_id, child_plan)
parent = self.store.get_task(parent_id)
if child_plan.required_capabilities and not set(child_plan.required_capabilities).issubset(
@@ -89,12 +80,7 @@ class DelegationEngine:
raise ConflictError("delegation task_id conflict")
return self.store.delegate_task_with_budget_and_events(
parent_id,
child_plan,
caller=caller,
owner_principal_id=owner_principal_id,
mission_id=mission_id,
created_at=created_at,
parent_id, child_plan, caller=caller
)
def _depth(self, task_id: str) -> int:
+2 -39
View File
@@ -5,7 +5,6 @@ from __future__ import annotations
import hashlib
import json
from .models import EvidenceEnvelope
from .store import TaskPlaneStore
@@ -18,44 +17,8 @@ class EvidenceService:
def __init__(self, store: TaskPlaneStore):
self.store = store
def add(
self,
task_id: str,
kind: str,
payload: dict,
worker_identity: str,
*,
envelope: EvidenceEnvelope | None = None,
claim_id: str | None = None,
evidence_id: str | None = None,
created_at: float | None = None,
expires_at: float | None = None,
signature: str | None = None,
attestation_ref: str | None = None,
signature_algorithm: str | None = None,
signing_key_id: str | None = None,
verifier_identity: str | None = None,
authority_domain: str | None = None,
sequence: int | None = None,
) -> str:
return self.store.add_evidence(
task_id,
kind,
payload,
worker_identity,
envelope=envelope,
claim_id=claim_id,
evidence_id=evidence_id,
created_at=created_at,
expires_at=expires_at,
signature=signature,
attestation_ref=attestation_ref,
signature_algorithm=signature_algorithm,
signing_key_id=signing_key_id,
verifier_identity=verifier_identity,
authority_domain=authority_domain,
sequence=sequence,
)
def add(self, task_id: str, kind: str, payload: dict, worker_identity: str) -> str:
return self.store.add_evidence(task_id, kind, payload, worker_identity)
def list(self, task_id: str) -> list[dict]:
return self.store.list_evidence(task_id)
@@ -1,147 +0,0 @@
"""Authenticated Task Plane evidence envelope adapter.
The adapter deliberately receives key material through an external resolver.
It does not create, persist, or select a production trust root.
"""
from __future__ import annotations
import hashlib
import hmac
import math
import re
import time
from dataclasses import replace
from typing import Callable
from .models import EvidenceEnvelope, canonical_json, compute_sha256_hex
HMAC_SHA256_V1 = "hmac-sha256-v1"
_SIGNATURE_DOMAIN = b"aios.task-plane.evidence-envelope.v1"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
def _signature_payload(envelope: EvidenceEnvelope) -> bytes:
return (
_SIGNATURE_DOMAIN
+ b"\0"
+ canonical_json(envelope.digest_payload()).encode("utf-8")
)
class HmacEvidenceVerifier:
"""Verify full evidence envelopes using an externally managed HMAC key."""
def __init__(
self,
*,
verifier_identity: str,
key_id: str,
key_resolver: Callable[[str], bytes],
max_age_seconds: int = 300,
now_fn: Callable[[], float] | None = None,
) -> None:
if not verifier_identity or not key_id:
raise ValueError("verifier_identity and key_id are required")
if not callable(key_resolver):
raise ValueError("key_resolver is required")
if (
isinstance(max_age_seconds, bool)
or not isinstance(max_age_seconds, int)
or max_age_seconds <= 0
):
raise ValueError("max_age_seconds must be a positive integer")
self.verifier_identity = verifier_identity
self.key_id = key_id
self._key_resolver = key_resolver
self.max_age_seconds = max_age_seconds
self._now_fn = now_fn or time.time
def __call__(self, envelope: EvidenceEnvelope) -> bool:
try:
now = float(self._now_fn())
finite_timestamps = all(
math.isfinite(value)
for value in (now, envelope.created_at, envelope.expires_at)
)
expected_digest = compute_sha256_hex(envelope.digest_payload())
except (TypeError, ValueError, OverflowError):
return False
if (
not finite_timestamps
or envelope.expires_at <= envelope.created_at
or envelope.signature_algorithm != HMAC_SHA256_V1
or envelope.signing_key_id != self.key_id
or envelope.verifier_identity != self.verifier_identity
or envelope.authority_domain != self.verifier_identity
or not isinstance(envelope.sequence, int)
or isinstance(envelope.sequence, bool)
or envelope.sequence < 1
or not isinstance(envelope.signature, str)
or not _HEX64.fullmatch(envelope.signature)
or envelope.created_at > now
or envelope.expires_at <= now
or envelope.expires_at - envelope.created_at > self.max_age_seconds
or envelope.evidence_digest != expected_digest
):
return False
try:
key = self._key_resolver(self.key_id)
except Exception:
return False
if not isinstance(key, (bytes, bytearray)) or not key:
return False
expected = hmac.new(
bytes(key), _signature_payload(envelope), hashlib.sha256
).hexdigest()
return hmac.compare_digest(envelope.signature, expected)
def sign_evidence_envelope(
envelope: EvidenceEnvelope,
*,
key_id: str,
key: bytes,
verifier_identity: str,
) -> EvidenceEnvelope:
"""Return a signed envelope for an already authorized producer.
Production code should obtain ``key`` from its existing credential provider;
this helper intentionally performs no key generation or storage.
"""
if not isinstance(key, (bytes, bytearray)) or not key:
raise ValueError("key is required")
if not key_id or not verifier_identity:
raise ValueError("key_id and verifier_identity are required")
if envelope.authority_domain != verifier_identity:
raise ValueError("envelope authority_domain must match verifier_identity")
if not math.isfinite(envelope.created_at) or not math.isfinite(
envelope.expires_at
):
raise ValueError("evidence timestamps must be finite")
if envelope.expires_at <= envelope.created_at:
raise ValueError("evidence expiry must follow creation")
if (
not isinstance(envelope.sequence, int)
or isinstance(envelope.sequence, bool)
or envelope.sequence < 1
):
raise ValueError("evidence sequence must be a positive integer")
unsigned = replace(
envelope,
signature=None,
signature_algorithm=HMAC_SHA256_V1,
signing_key_id=key_id,
verifier_identity=verifier_identity,
evidence_digest="",
)
unsigned = replace(
unsigned,
evidence_digest=compute_sha256_hex(unsigned.digest_payload()),
)
signature = hmac.new(
bytes(key), _signature_payload(unsigned), hashlib.sha256
).hexdigest()
return replace(unsigned, signature=signature)
@@ -1,401 +0,0 @@
"""现场(field)组合根:让"Owner 基于授权"真正可接线、可执行。
本模块是 V2 governed-writer 机制的**现场装配层**,职责:
- 实现正式的 `OwnerAuthorizationAuthority`:持有 Owner 登记的授权记录
(authorization_ref -> OwnerAuthorizationPolicy),`verify()` 解析引用、
核验策略、签发 verification receipt;未知引用 / 策略失效一律 fail-closed。
- 提供 `FieldGovernedWriterFactory`(组合根):把 7 个协议适配器(signer /
execution / principal_issuer / jobs / git / writer_lease / owner_authority)
装配到 `GovernedWriterAutomation.acquire_verified()` 生产入口上。
- 提供默认的生产适配器参考实现(`_DefaultOwnerAuthority`),供现场在真实
端点未就绪时先闭环;真实端点就绪后替换对应 adapter 即可,组合根接口不变。
设计原则(与 OWNER_AUTHORIZATION_MODEL.md 的 `aios.owner-authorization.v1` 一致):
1. 生产入口唯一授权来源是 Owner 登记的 `authorization_ref`;Agent 本地自造的
`OwnerAuthorizationPolicy` 不构成 proof(fail-closed)。
2. 长期业务授权(Policy)与短时执行凭证(epoch/lease/principal)分离。
3. `legacy_global_writes_enabled=false` 是安全不变式,不是 governed-writer blocker。
本模块不修改 S6 frozen 9 文件,不触碰 legacy/global 写;只做 governed 写路径的
现场装配。适配器接口全部复用 `governed_writer_automation.py` 的 Protocol。
"""
from __future__ import annotations
import time
from dataclasses import dataclass, field
from typing import Any, Mapping
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
EpochStatus,
GovernedWriterAdapters,
GovernedWriterAutomation,
GovernedWriterRequest,
GovernedWriterSession,
GovernedWriteReadiness,
JobBindingReceipt,
OwnerAuthorizationPolicy,
RecoveryAuthorization,
TaskLeaseReceipt,
TaskPrincipalReceipt,
TaskState,
VerifiedOwnerAuthorization,
WriterLeaseReceipt,
)
# ---------------------------------------------------------------------------
# 正式 OwnerAuthorizationAuthority 现场实现
# ---------------------------------------------------------------------------
@dataclass
class FieldOwnerAuthorizationAuthority:
"""现场 authority:登记 Owner 授权记录并按引用验证,签发核验回执。
生产语义:
- `register(ref, policy)` 由 Owner/管理员在受信侧登记;登记即"一次授权"。
- `verify(authorization_ref, request)` 解析引用 -> 核验 -> 返回
`VerifiedOwnerAuthorization`(含非空 receipt)。
- 任何未知引用、未登记、策略与请求越界,都抛 `AuthorizationDenied`
(fail-closed:不猜测、不降级)。
"""
_registry: dict[str, OwnerAuthorizationPolicy] = field(default_factory=dict)
_receipt_counter: int = 0
def register(self, authorization_ref: str, policy: OwnerAuthorizationPolicy) -> None:
if authorization_ref != policy.authorization_id:
raise AuthorizationDenied(
"authorization_ref must equal policy.authorization_id"
)
self._registry[authorization_ref] = policy
def contains(self, authorization_ref: str) -> bool:
return authorization_ref in self._registry
def policy(self, authorization_ref: str) -> OwnerAuthorizationPolicy:
if authorization_ref not in self._registry:
raise AuthorizationDenied(f"unknown authorization reference: {authorization_ref}")
return self._registry[authorization_ref]
def verify(
self,
*,
authorization_ref: str,
request: GovernedWriterRequest,
) -> VerifiedOwnerAuthorization:
if authorization_ref not in self._registry:
raise AuthorizationDenied(
f"owner authorization reference not registered: {authorization_ref}"
)
policy = self._registry[authorization_ref]
policy.validate(request, now=time.time())
self._receipt_counter += 1
receipt = f"receipt:owner-{authorization_ref}:{self._receipt_counter}"
return VerifiedOwnerAuthorization(
policy=policy,
authorization_ref=authorization_ref,
verification_receipt=receipt,
verified_at=time.time(),
)
# ---------------------------------------------------------------------------
# 默认生产适配器参考实现(真实端点未就绪时先闭环,接口与协议一一对应)
# ---------------------------------------------------------------------------
class _DefaultTrustedSigner:
"""TrustedSignerAdapter:JIT 重签同 scope 的 epoch(幂等由 ledger 守护)。"""
def __init__(self) -> None:
self._counter = 0
self.calls: list[dict] = []
def reissue_epoch(
self,
*,
owner_policy: OwnerAuthorizationPolicy,
request: GovernedWriterRequest,
prior_epoch: EpochStatus | None,
minimum_remaining_ttl: float,
idempotency_key: str,
) -> EpochStatus:
self._counter += 1
self.calls.append(
{
"key": idempotency_key,
"scope": request.scope_fingerprint,
"policy": owner_policy.policy_fingerprint,
"prior": None if prior_epoch is None else prior_epoch.epoch_id,
}
)
return EpochStatus(
epoch_id=f"epoch-gw-{self._counter}",
scope_fingerprint=request.scope_fingerprint,
expires_at=time.time() + 7200.0,
policy_digest=owner_policy.policy_fingerprint,
)
class _DefaultExecutionPlane:
"""ExecutionPlane:QUEUED 自动 claim + lease;heartbeat 记录。"""
def __init__(self, initial: TaskState) -> None:
self.task = initial
self._lease_counter = 0
self.claims: list = []
self.heartbeats: list = []
def read_task(self, task_id: str) -> TaskState:
if task_id != self.task.task_id:
raise AuthorizationDenied(f"unknown task: {task_id}")
return self.task
def apply_recovery(
self,
*,
task: TaskState,
authorization: RecoveryAuthorization,
idempotency_key: str,
) -> TaskState:
raise AuthorizationDenied("terminal recovery requires a real recovery endpoint")
def claim(
self,
*,
task: TaskState,
actor: str,
idempotency_key: str,
):
from app.task_plane.governed_writer_automation import ClaimReceipt
self.claims.append({"task": task.task_id, "actor": actor, "key": idempotency_key})
return ClaimReceipt(
claim_id=f"claim-gw-{len(self.claims)}",
task_id=task.task_id,
actor=actor,
fencing=len(self.claims),
)
def heartbeat(self, *, claim) -> None:
self.heartbeats.append(claim.claim_id)
def issue_task_lease(
self,
*,
claim,
idempotency_key: str,
) -> TaskLeaseReceipt:
self._lease_counter += 1
return TaskLeaseReceipt(
task_lease_id=f"tasklease-gw-{self._lease_counter}",
task_id=claim.task_id,
token_ref=f"token-gw-{self._lease_counter}",
expires_at=time.time() + 3600.0,
fencing=claim.fencing,
)
class _DefaultPrincipalIssuer:
"""TaskPrincipalIssuer:按 lease 派发 scoped TaskPrincipal。"""
def __init__(self) -> None:
self._counter = 0
def issue(
self,
*,
lease: TaskLeaseReceipt,
requested_capabilities: frozenset[str],
idempotency_key: str,
) -> TaskPrincipalReceipt:
self._counter += 1
return TaskPrincipalReceipt(
principal_id=f"principal-gw-{self._counter}",
task_id=lease.task_id,
capabilities=requested_capabilities,
expires_at=time.time() + 1800.0,
)
class _DefaultJobs:
"""JobAuthority:reconcile 到与请求一致的 job 绑定。"""
def __init__(self, *, repository: str, target_branch: str, generation: int) -> None:
self._repo = repository
self._branch = target_branch
self._gen = generation
self._counter = 0
def read_or_reconcile(
self,
*,
task_id: str,
principal: TaskPrincipalReceipt,
idempotency_key: str,
) -> JobBindingReceipt:
self._counter += 1
return JobBindingReceipt(
job_id=f"job-gw-{self._counter}",
task_id=task_id,
repository=self._repo,
target_branch=self._branch,
generation=self._gen,
)
class _DefaultGit:
"""GitAuthority:只读 branch 读回,决定 CAS 模式。"""
def __init__(self, *, exists: bool, head_oid: str | None = None) -> None:
from app.task_plane.governed_writer_automation import BranchReadback
self._exists = exists
self._head = head_oid
self.calls: list = []
def read_branch(self, *, repository: str, target_branch: str):
from app.task_plane.governed_writer_automation import BranchReadback
self.calls.append({"repo": repository, "branch": target_branch})
return BranchReadback(exists=self._exists, head_oid=self._head)
class _DefaultWriterLease:
"""WriterLeaseAuthority:签发短时 writer lease(CAS 与 base 一致)。"""
def __init__(self) -> None:
self._counter = 0
def acquire(
self,
*,
task_id: str,
job: JobBindingReceipt,
principal: TaskPrincipalReceipt,
repository: str,
target_branch: str,
branch_mode: str,
base_oid: str,
expected_branch_oid: str,
generation: int,
idempotency_key: str,
) -> WriterLeaseReceipt:
self._counter += 1
return WriterLeaseReceipt(
writer_lease_id=f"writerlease-gw-{self._counter}",
task_id=task_id,
job_id=job.job_id,
repository=repository,
target_branch=target_branch,
branch_mode=branch_mode,
expected_branch_oid=expected_branch_oid,
generation=generation,
expires_at=time.time() + 900.0,
fencing=self._counter,
)
# ---------------------------------------------------------------------------
# 组合根:装配 7 适配器 -> 生产入口
# ---------------------------------------------------------------------------
@dataclass
class FieldRuntimeStatus:
"""现场运行时状态:由 W-RUNTIME 只读线提供,用于派生 governed 写就绪。"""
legacy_global_writes_enabled: bool = False
task_principal_writes_enabled: bool = True
task_principal_verifier_ready: bool = True
governed_write_blockers: tuple[str, ...] = ()
@dataclass
class FieldGovernedWriterFactory:
"""组合根:把现场配置装配成 `acquire_verified()` 生产入口可直接执行的对象。
用法(现场接线):
factory = FieldGovernedWriterFactory.build(
authority=owner_authority,
manifest_store=...,
action_ledger=...,
task_initial=TaskState(...),
branch_exists=...,
head_oid=...,
repo=...,
branch=...,
generation=...,
runtime_status=...,
)
session = factory.acquire(request, authorization_ref)
"""
automation: GovernedWriterAutomation
adapters: GovernedWriterAdapters
readiness: GovernedWriteReadiness
owner_authority: FieldOwnerAuthorizationAuthority
@classmethod
def build(
cls,
*,
authority: FieldOwnerAuthorizationAuthority,
manifest_store,
action_ledger,
task_initial: TaskState,
branch_exists: bool,
head_oid: str | None,
repository: str,
target_branch: str,
generation: int,
runtime_status: FieldRuntimeStatus | None = None,
decision_engine=None,
) -> "FieldGovernedWriterFactory":
runtime_status = runtime_status or FieldRuntimeStatus()
adapters = GovernedWriterAdapters(
signer=_DefaultTrustedSigner(),
execution=_DefaultExecutionPlane(task_initial),
principal_issuer=_DefaultPrincipalIssuer(),
jobs=_DefaultJobs(
repository=repository, target_branch=target_branch, generation=generation
),
git=_DefaultGit(exists=branch_exists, head_oid=head_oid),
writer_lease=_DefaultWriterLease(),
owner_authority=authority,
)
automation = GovernedWriterAutomation(
manifest_store=manifest_store,
action_ledger=action_ledger,
decision_engine=decision_engine,
)
readiness = GovernedWriteReadiness(
legacy_global_writes_enabled=runtime_status.legacy_global_writes_enabled,
task_principal_writes_enabled=runtime_status.task_principal_writes_enabled,
task_principal_verifier_ready=runtime_status.task_principal_verifier_ready,
governed_write_blockers=tuple(runtime_status.governed_write_blockers),
)
return cls(
automation=automation,
adapters=adapters,
readiness=readiness,
owner_authority=authority,
)
def acquire(
self,
request: GovernedWriterRequest,
authorization_ref: str,
*,
epoch: EpochStatus | None = None,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
"""生产入口:先验 Owner 授权,再自动完成受治理写链。"""
return self.automation.acquire_verified(
request=request,
authorization_ref=authorization_ref,
readiness=self.readiness,
epoch=epoch,
adapters=self.adapters,
recovery_authorization=recovery_authorization,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
@@ -1,147 +0,0 @@
"""Fail-closed candidate-specific governed admission preflight.
Ordinary/global writes are deliberately NOT a prerequisite. This evaluates the
governed Bridge/TaskPrincipal path only.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Sequence
from .governance_resolver import ResolutionResult, ResolutionState
class AdmissionOperation(str, Enum):
GOVERNED_COMMIT = "GOVERNED_COMMIT"
RECOVER_EXISTING_CHILD = "RECOVER_EXISTING_CHILD"
@dataclass(frozen=True)
class GovernanceFacts:
governed_writes_enabled: bool
task_principal_writes_enabled: bool
recovery_execution_enabled: bool
recovery_shadow_mode: bool
authenticated_principal_id: str | None
target_branch: str | None
branch_protection_known: bool
expected_branch_oid: str | None
generation: int | None
task_lease_id: str | None
task_principal_id: str | None
writer_lease_id: str | None
accepted_file_allowlist: tuple[str, ...] = ()
acceptance_revision: str | None = None
@dataclass(frozen=True)
class AdmissionBlocker:
code: str
detail: str
@dataclass(frozen=True)
class AdmissionResult:
ready: bool
operation: AdmissionOperation
blockers: tuple[AdmissionBlocker, ...]
class AdmissionPreflight:
def evaluate(
self,
*,
resolution: ResolutionResult,
facts: GovernanceFacts,
operation: AdmissionOperation,
) -> AdmissionResult:
blockers: list[AdmissionBlocker] = []
if resolution.state is ResolutionState.NOT_FOUND:
blockers.append(AdmissionBlocker(
"TASK_NOT_RESOLVED", "exact candidate task not found"
))
elif resolution.state is ResolutionState.AMBIGUOUS:
blockers.append(AdmissionBlocker(
"TASK_RESOLUTION_AMBIGUOUS", "multiple exact candidate tasks"
))
elif resolution.state is not ResolutionState.RESOLVED:
blockers.append(AdmissionBlocker(
"TASK_RESOLUTION_INVALID", resolution.detail or "resolver invalid"
))
if not facts.governed_writes_enabled:
blockers.append(AdmissionBlocker(
"GOVERNED_WRITES_DISABLED",
"governed Bridge write path is disabled",
))
if not facts.task_principal_writes_enabled:
blockers.append(AdmissionBlocker(
"TASK_PRINCIPAL_WRITES_DISABLED",
"TaskPrincipal governed writes are disabled",
))
if not facts.authenticated_principal_id:
blockers.append(AdmissionBlocker(
"CALLER_PRINCIPAL_UNRESOLVED",
"authenticated governed caller principal is missing",
))
if not facts.target_branch:
blockers.append(AdmissionBlocker(
"TARGET_BRANCH_UNRESOLVED", "target branch is missing"
))
if not facts.branch_protection_known:
blockers.append(AdmissionBlocker(
"BRANCH_PROTECTION_UNKNOWN",
"branch protection has not been read through authority",
))
if not facts.expected_branch_oid:
blockers.append(AdmissionBlocker(
"EXPECTED_BRANCH_OID_UNRESOLVED",
"expected_branch_oid/CAS preimage is missing",
))
if facts.generation is None:
blockers.append(AdmissionBlocker(
"GENERATION_UNRESOLVED", "governed generation is missing"
))
if not facts.task_lease_id:
blockers.append(AdmissionBlocker(
"TASK_LEASE_MISSING", "TaskLease not established"
))
if not facts.task_principal_id:
blockers.append(AdmissionBlocker(
"TASK_PRINCIPAL_MISSING", "TaskPrincipal not established"
))
if not facts.writer_lease_id:
blockers.append(AdmissionBlocker(
"WRITER_LEASE_MISSING", "WriterLease not established"
))
if not facts.accepted_file_allowlist:
blockers.append(AdmissionBlocker(
"FILE_ALLOWLIST_MISSING", "accepted file allowlist is empty"
))
if not facts.acceptance_revision:
blockers.append(AdmissionBlocker(
"ACCEPTANCE_REVISION_MISSING",
"candidate acceptance revision is missing",
))
if operation is AdmissionOperation.RECOVER_EXISTING_CHILD:
if not facts.recovery_execution_enabled:
blockers.append(AdmissionBlocker(
"RECOVERY_EXECUTION_DISABLED",
"recovery execution path is disabled",
))
if facts.recovery_shadow_mode:
blockers.append(AdmissionBlocker(
"RECOVERY_SHADOW_MODE",
"recovery is still shadow-only",
))
# Ordinary/global write state is intentionally absent from the model.
return AdmissionResult(
ready=not blockers,
operation=operation,
blockers=tuple(blockers),
)
@@ -1,132 +0,0 @@
"""Canonical recovery observation checkpoint and unchanged-blocker budget.
The checkpoint is NOT an authority. It records the last reconciled observation
so agents do not repeatedly prove the same unchanged blocker.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, replace
import hashlib
import json
import os
from pathlib import Path
from typing import Protocol, Sequence
def _canonical(value) -> str:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
def _digest(value) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class RecoveryCheckpoint:
schema: str
goal_id: str
repository: str
source_oid: str
candidate_digest: str
task_id: str | None
mission_id: str | None
root_task_id: str | None
parent_task_id: str | None
principal_id: str | None
blocker_code: str | None
blocker_fingerprint: str | None
next_legal_action: str
last_verified_at: str
evidence_refs: tuple[str, ...]
unchanged_cycles: int
mechanism_repair_required: bool
recovery_idempotency_key: str | None = None
recovery_receipt_ref: str | None = None
recovery_context_digest: str | None = None
def to_dict(self) -> dict:
value = asdict(self)
value["evidence_refs"] = list(self.evidence_refs)
return value
@property
def digest(self) -> str:
return _digest(self.to_dict())
class CheckpointStore(Protocol):
def load(self) -> RecoveryCheckpoint | None: ...
def save(self, checkpoint: RecoveryCheckpoint) -> None: ...
class JsonCheckpointStore:
"""Atomic local observation cache; never used as authorization evidence."""
def __init__(self, path: str | Path) -> None:
self.path = Path(path)
def load(self) -> RecoveryCheckpoint | None:
if not self.path.exists():
return None
raw = json.loads(self.path.read_text(encoding="utf-8"))
raw["evidence_refs"] = tuple(raw.get("evidence_refs") or ())
return RecoveryCheckpoint(**raw)
def save(self, checkpoint: RecoveryCheckpoint) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
temp = self.path.with_suffix(self.path.suffix + ".tmp")
temp.write_text(
json.dumps(
checkpoint.to_dict(),
ensure_ascii=False,
indent=2,
sort_keys=True,
) + "\n",
encoding="utf-8",
)
os.replace(temp, self.path)
@dataclass(frozen=True)
class RetryBudget:
unchanged_threshold: int = 3
escalation_eligible_codes: frozenset[str] = frozenset({
"TASK_NOT_RESOLVED",
"TASK_RESOLUTION_AMBIGUOUS",
"CALLER_PRINCIPAL_UNRESOLVED",
"CANONICAL_CHANNEL_MISSING",
"RESOLVER_UNAVAILABLE",
})
def __post_init__(self):
if self.unchanged_threshold < 2 or self.unchanged_threshold > 100:
raise ValueError("unchanged_threshold must be in [2,100]")
@staticmethod
def blocker_fingerprint(code: str, details: Sequence[str]) -> str:
return _digest({
"code": code,
"details": sorted(str(item) for item in details),
})
def observe(
self,
*,
previous: RecoveryCheckpoint | None,
blocker_code: str,
blocker_fingerprint: str,
) -> tuple[int, bool]:
same = (
previous is not None
and previous.blocker_code == blocker_code
and previous.blocker_fingerprint == blocker_fingerprint
)
cycles = (previous.unchanged_cycles + 1) if same else 1
repair = (
blocker_code in self.escalation_eligible_codes
and cycles >= self.unchanged_threshold
)
return cycles, repair
File diff suppressed because it is too large Load Diff
@@ -1,118 +0,0 @@
"""Live driver that converts external surface failures into one checkpoint.
The controller remains authority-neutral. This driver owns the site boundary:
surface availability, binding-contract failures, and poll fallback.
"""
from __future__ import annotations
from .governance_checkpoint import RecoveryCheckpoint, RetryBudget
from .governance_recovery_controller import (
GovernanceEvent,
GovernanceRecoveryController,
ReconcileResult,
)
from .site_adapters import LiveBindingViolation, LiveSurfaceUnavailable
class GovernanceLiveDriver:
def __init__(
self,
controller: GovernanceRecoveryController,
*,
retry_budget: RetryBudget | None = None,
) -> None:
self.controller = controller
self.retry_budget = retry_budget or controller.retry_budget
def _candidate_digest(self) -> str:
query = self.controller.query
return (
query.patch_digest
or query.artifact_sha256
or query.candidate_oid
or ""
)
def _persist_external_blocker(
self,
event: GovernanceEvent,
*,
code: str,
detail: str,
mechanism_repair_required: bool,
) -> ReconcileResult:
previous = self.controller.checkpoints.load()
fingerprint = self.retry_budget.blocker_fingerprint(code, (detail,))
if mechanism_repair_required:
cycles = (
previous.unchanged_cycles + 1
if previous
and previous.blocker_code == code
and previous.blocker_fingerprint == fingerprint
else 1
)
else:
cycles, _ = self.retry_budget.observe(
previous=previous,
blocker_code=code,
blocker_fingerprint=fingerprint,
)
next_action = (
"REPAIR_MECHANISM"
if mechanism_repair_required
else "WAIT_DEPENDENCY"
)
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.controller.goal_id,
repository=self.controller.query.repository,
source_oid=self.controller.query.base_oid,
candidate_digest=self._candidate_digest(),
task_id=None,
mission_id=None,
root_task_id=None,
parent_task_id=None,
principal_id=None,
blocker_code=code,
blocker_fingerprint=fingerprint,
next_legal_action=next_action,
last_verified_at=self.controller._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=cycles,
mechanism_repair_required=mechanism_repair_required,
)
self.controller.checkpoints.save(checkpoint)
return ReconcileResult(
status="BLOCKED",
next_legal_action=next_action,
task_id=None,
blocker_code=code,
mechanism_repair_required=mechanism_repair_required,
)
def on_event(self, event: GovernanceEvent) -> ReconcileResult:
try:
return self.controller.on_event(event)
except LiveSurfaceUnavailable as exc:
return self._persist_external_blocker(
event,
code="LIVE_SURFACE_UNAVAILABLE",
detail=str(exc),
mechanism_repair_required=False,
)
except LiveBindingViolation as exc:
return self._persist_external_blocker(
event,
code="LIVE_BINDING_VIOLATION",
detail=str(exc),
mechanism_repair_required=True,
)
def poll_once(
self, *, revision: str, occurred_at: str
) -> ReconcileResult:
return self.on_event(GovernanceEvent(
kind="POLL_FALLBACK",
revision=revision,
occurred_at=occurred_at,
))
@@ -1,474 +0,0 @@
"""Event-driven governed recovery reconciler with polling fallback.
The controller cannot create a task and cannot bypass admission. It resumes
only an exact, already-existing child through an injected governed executor.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
from typing import Mapping, Protocol
from .governance_admission import (
AdmissionOperation,
AdmissionPreflight,
GovernanceFacts,
)
from .governance_checkpoint import (
CheckpointStore,
RecoveryCheckpoint,
RetryBudget,
)
from .governance_resolver import ExactTaskResolver, ResolutionQuery, ResolutionState
def _canonical(value) -> str:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
def _digest(value) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class GovernanceEvent:
kind: str
revision: str
occurred_at: str
@dataclass(frozen=True)
class RecoveryReceipt:
receipt_ref: str
task_id: str
accepted: bool
@dataclass(frozen=True)
class ReconcileResult:
status: str
next_legal_action: str
task_id: str | None
blocker_code: str | None
mechanism_repair_required: bool
receipt_ref: str | None = None
class GovernanceFactsProvider(Protocol):
def snapshot(
self,
*,
task_id: str | None,
actor_identity: str,
operation: AdmissionOperation,
) -> GovernanceFacts: ...
class ExistingChildRecoveryExecutor(Protocol):
def recover_existing_child(
self,
*,
task_id: str,
actor_identity: str,
idempotency_key: str,
facts: GovernanceFacts,
) -> RecoveryReceipt: ...
# Optional at runtime. If implemented, this is preferred after an
# unknown-result write so the controller reconciles before re-executing.
def reconcile_recovery_result(
self,
*,
task_id: str,
actor_identity: str,
idempotency_key: str,
) -> RecoveryReceipt | None: ...
class GovernanceRecoveryController:
def __init__(
self,
*,
goal_id: str,
query: ResolutionQuery,
actor_identity: str,
resolver: ExactTaskResolver,
facts_provider: GovernanceFactsProvider,
executor: ExistingChildRecoveryExecutor,
checkpoint_store: CheckpointStore,
retry_budget: RetryBudget = RetryBudget(),
) -> None:
self.goal_id = goal_id
self.query = query
self.actor_identity = actor_identity
self.resolver = resolver
self.facts_provider = facts_provider
self.executor = executor
self.checkpoints = checkpoint_store
self.retry_budget = retry_budget
self.preflight = AdmissionPreflight()
def _timestamp(self, event: GovernanceEvent) -> str:
return event.occurred_at or datetime.now(timezone.utc).isoformat()
def _save_blocked(
self,
*,
event: GovernanceEvent,
task_id: str | None,
mission_id: str | None,
root_task_id: str | None,
parent_task_id: str | None,
principal_id: str | None,
blocker_code: str,
blocker_details: tuple[str, ...],
) -> ReconcileResult:
previous = self.checkpoints.load()
fingerprint = self.retry_budget.blocker_fingerprint(
blocker_code, blocker_details
)
cycles, repair = self.retry_budget.observe(
previous=previous,
blocker_code=blocker_code,
blocker_fingerprint=fingerprint,
)
next_action = "REPAIR_MECHANISM" if repair else "WAIT_DEPENDENCY"
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=(
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
),
task_id=task_id,
mission_id=mission_id,
root_task_id=root_task_id,
parent_task_id=parent_task_id,
principal_id=principal_id,
blocker_code=blocker_code,
blocker_fingerprint=fingerprint,
next_legal_action=next_action,
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=cycles,
mechanism_repair_required=repair,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="BLOCKED",
next_legal_action=next_action,
task_id=task_id,
blocker_code=blocker_code,
mechanism_repair_required=repair,
)
def _candidate_digest(self) -> str:
return (
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
)
def _recovery_context_digest(
self, *, task_id: str, facts: GovernanceFacts
) -> str:
return _digest({
"task_id": task_id,
"repository": self.query.repository,
"candidate": self._candidate_digest(),
"principal": facts.authenticated_principal_id,
"branch": facts.target_branch,
"branch_oid": facts.expected_branch_oid,
"generation": facts.generation,
"task_lease_id": facts.task_lease_id,
"task_principal_id": facts.task_principal_id,
"writer_lease_id": facts.writer_lease_id,
"acceptance_revision": facts.acceptance_revision,
"file_allowlist": sorted(facts.accepted_file_allowlist),
})
def _finish_recovered(
self,
*,
event: GovernanceEvent,
resolved,
facts: GovernanceFacts,
idempotency_key: str,
context_digest: str,
receipt: RecoveryReceipt,
) -> ReconcileResult:
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=self._candidate_digest(),
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code=None,
blocker_fingerprint=None,
next_legal_action="ROOT_REVIEW",
last_verified_at=self._timestamp(event),
evidence_refs=(
f"event:{event.kind}:{event.revision}",
receipt.receipt_ref,
),
unchanged_cycles=0,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_receipt_ref=receipt.receipt_ref,
recovery_context_digest=context_digest,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RECOVERED",
next_legal_action="ROOT_REVIEW",
task_id=resolved.task_id,
blocker_code=None,
mechanism_repair_required=False,
receipt_ref=receipt.receipt_ref,
)
def reconcile(self, event: GovernanceEvent) -> ReconcileResult:
resolution = self.resolver.resolve(
self.query, actor_identity=self.actor_identity
)
if resolution.state is not ResolutionState.RESOLVED:
code = {
ResolutionState.NOT_FOUND: "TASK_NOT_RESOLVED",
ResolutionState.AMBIGUOUS: "TASK_RESOLUTION_AMBIGUOUS",
ResolutionState.INVALID_QUERY: "TASK_RESOLUTION_INVALID",
}[resolution.state]
details = (
resolution.detail,
*resolution.candidate_task_ids,
)
return self._save_blocked(
event=event,
task_id=None,
mission_id=None,
root_task_id=None,
parent_task_id=None,
principal_id=None,
blocker_code=code,
blocker_details=tuple(str(v) for v in details if v),
)
resolved = resolution.resolved
assert resolved is not None
facts = self.facts_provider.snapshot(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
admission = self.preflight.evaluate(
resolution=resolution,
facts=facts,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
if not admission.ready:
primary = admission.blockers[0]
details = tuple(
f"{blocker.code}:{blocker.detail}"
for blocker in admission.blockers
)
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code=primary.code,
blocker_details=details,
)
context_digest = self._recovery_context_digest(
task_id=resolved.task_id, facts=facts
)
previous = self.checkpoints.load()
candidate_digest = self._candidate_digest()
unknown_previous = (
previous is not None
and previous.blocker_code == "RECOVERY_RESULT_UNKNOWN"
and previous.task_id == resolved.task_id
and previous.candidate_digest == candidate_digest
and bool(previous.recovery_idempotency_key)
)
if unknown_previous:
idempotency_key = previous.recovery_idempotency_key
reconcile = getattr(
self.executor, "reconcile_recovery_result", None
)
if callable(reconcile):
receipt = reconcile(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
idempotency_key=idempotency_key,
)
if receipt is not None:
if receipt.accepted:
return self._finish_recovered(
event=event,
resolved=resolved,
facts=facts,
idempotency_key=idempotency_key,
context_digest=(
previous.recovery_context_digest
or context_digest
),
receipt=receipt,
)
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_NOT_ACCEPTED",
blocker_details=(receipt.receipt_ref,),
)
if (
previous.recovery_context_digest is not None
and previous.recovery_context_digest != context_digest
):
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=candidate_digest,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
blocker_fingerprint=_digest({
"task_id": resolved.task_id,
"idempotency_key": idempotency_key,
"context_changed": True,
}),
next_legal_action="RECOVER_EXISTING_CHILD",
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=1,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_context_digest=(
previous.recovery_context_digest
),
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RESULT_UNKNOWN",
next_legal_action="RECOVER_EXISTING_CHILD",
task_id=resolved.task_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
mechanism_repair_required=False,
)
else:
idempotency_key = "recover-" + _digest({
"task_id": resolved.task_id,
"repository": self.query.repository,
"candidate": candidate_digest,
"branch_oid": facts.expected_branch_oid,
"generation": facts.generation,
"writer_lease_id": facts.writer_lease_id,
})[:40]
try:
receipt = self.executor.recover_existing_child(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
idempotency_key=idempotency_key,
facts=facts,
)
except Exception:
# Unknown-result write: retain the SAME idempotency key for the next
# reconcile. Never assume the side effect did not happen.
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=(
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
),
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
blocker_fingerprint=_digest({
"task_id": resolved.task_id,
"idempotency_key": idempotency_key,
}),
next_legal_action="RECOVER_EXISTING_CHILD",
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=1,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_context_digest=context_digest,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RESULT_UNKNOWN",
next_legal_action="RECOVER_EXISTING_CHILD",
task_id=resolved.task_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
mechanism_repair_required=False,
)
if not receipt.accepted:
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_NOT_ACCEPTED",
blocker_details=(receipt.receipt_ref,),
)
return self._finish_recovered(
event=event,
resolved=resolved,
facts=facts,
idempotency_key=idempotency_key,
context_digest=context_digest,
receipt=receipt,
)
def on_event(self, event: GovernanceEvent) -> ReconcileResult:
# Event-driven primary path: reconcile immediately in the same call.
return self.reconcile(event)
def poll_once(self, *, revision: str, occurred_at: str) -> ReconcileResult:
# Polling is only a fallback and uses exactly the same reconcile path.
return self.reconcile(GovernanceEvent(
kind="POLL_FALLBACK",
revision=revision,
occurred_at=occurred_at,
))
@@ -1,229 +0,0 @@
"""Read-only exact Task/lineage resolver.
This is an index/projection over existing TaskPlane authority, not a second task
authority. It never creates, claims, mutates, or guesses a task.
The source supplied by the host MUST enforce the caller's existing TaskPlane
read authorization before returning records.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Mapping, Protocol, Sequence
class ResolutionState(str, Enum):
RESOLVED = "RESOLVED"
NOT_FOUND = "NOT_FOUND"
AMBIGUOUS = "AMBIGUOUS"
INVALID_QUERY = "INVALID_QUERY"
@dataclass(frozen=True)
class ResolutionQuery:
repository: str
base_oid: str
task_id: str | None = None
candidate_oid: str | None = None
patch_digest: str | None = None
artifact_sha256: str | None = None
pr_id: str | None = None
release_intent: str | None = None
mission_id: str | None = None
root_task_id: str | None = None
parent_task_id: str | None = None
owner_principal_id: str | None = None
def selectors(self) -> Mapping[str, str]:
values = {
"repository": self.repository,
"base_oid": self.base_oid,
"task_id": self.task_id,
"candidate_oid": self.candidate_oid,
"patch_digest": self.patch_digest,
"artifact_sha256": self.artifact_sha256,
"pr_id": self.pr_id,
"release_intent": self.release_intent,
"mission_id": self.mission_id,
"root_task_id": self.root_task_id,
"parent_task_id": self.parent_task_id,
"owner_principal_id": self.owner_principal_id,
}
return {key: str(value) for key, value in values.items() if value}
def validate(self) -> None:
if not self.repository.strip() or not self.base_oid.strip():
raise ValueError("repository and base_oid required")
strong = (
self.task_id,
self.candidate_oid,
self.patch_digest,
self.artifact_sha256,
self.pr_id,
self.release_intent,
self.mission_id,
self.root_task_id,
)
if not any(value for value in strong):
raise ValueError(
"at least one strong selector required "
"(task/candidate/patch/artifact/pr/intent/mission/root)"
)
@dataclass(frozen=True)
class ResolvedTask:
task_id: str
mission_id: str | None
root_task_id: str | None
parent_task_id: str | None
owner_principal_id: str | None
record: Mapping[str, Any]
matched_selectors: tuple[str, ...]
@dataclass(frozen=True)
class ResolutionResult:
state: ResolutionState
resolved: ResolvedTask | None = None
candidate_task_ids: tuple[str, ...] = ()
detail: str = ""
class TaskResolutionSource(Protocol):
"""Caller-authorized read projection over the existing TaskPlane."""
def get_task(
self, task_id: str, *, actor_identity: str
) -> Mapping[str, Any] | None: ...
def list_tasks(
self, *, actor_identity: str
) -> Sequence[Mapping[str, Any]]: ...
def _mapping(value: Any) -> Mapping[str, Any]:
return value if isinstance(value, Mapping) else {}
def _metadata(record: Mapping[str, Any]) -> Mapping[str, Any]:
plan = _mapping(record.get("plan") or record.get("task_plan"))
merged: dict[str, Any] = {}
for source in (
_mapping(record.get("metadata")),
_mapping(plan.get("metadata")),
):
merged.update(source)
# Some existing lineage metadata is nested.
for key in ("aios_task_lineage", "lineage", "task_lineage"):
nested = _mapping(merged.get(key))
for nkey, value in nested.items():
merged.setdefault(nkey, value)
return merged
def _first(record: Mapping[str, Any], metadata: Mapping[str, Any], *keys: str):
for key in keys:
if key in record and record.get(key) not in (None, ""):
return record.get(key)
if key in metadata and metadata.get(key) not in (None, ""):
return metadata.get(key)
return None
def _projection(record: Mapping[str, Any]) -> Mapping[str, str]:
metadata = _metadata(record)
values = {
"repository": _first(record, metadata, "repository", "repo"),
"base_oid": _first(record, metadata, "base_oid", "source_oid"),
"task_id": _first(record, metadata, "task_id"),
"candidate_oid": _first(record, metadata, "candidate_oid", "head_oid"),
"patch_digest": _first(record, metadata, "patch_digest", "candidate_digest"),
"artifact_sha256": _first(record, metadata, "artifact_sha256", "artifact_digest"),
"pr_id": _first(record, metadata, "pr_id", "pull_request_id"),
"release_intent": _first(record, metadata, "release_intent", "release_intent_id"),
"mission_id": _first(record, metadata, "mission_id"),
"root_task_id": _first(record, metadata, "root_task_id"),
"parent_task_id": _first(record, metadata, "parent_task_id"),
"owner_principal_id": _first(record, metadata, "owner_principal_id"),
}
return {
key: str(value)
for key, value in values.items()
if value not in (None, "")
}
def _matches(record: Mapping[str, Any], selectors: Mapping[str, str]) -> bool:
projection = _projection(record)
# Exact-only: a missing selector never counts as a partial match.
return all(projection.get(key) == value for key, value in selectors.items())
class ExactTaskResolver:
def __init__(self, source: TaskResolutionSource) -> None:
self.source = source
def resolve(
self, query: ResolutionQuery, *, actor_identity: str
) -> ResolutionResult:
try:
query.validate()
except ValueError as exc:
return ResolutionResult(
ResolutionState.INVALID_QUERY, detail=str(exc)
)
selectors = query.selectors()
if query.task_id:
record = self.source.get_task(
query.task_id, actor_identity=actor_identity
)
records = [record] if record is not None else []
else:
records = list(
self.source.list_tasks(actor_identity=actor_identity)
)
matches = [
record
for record in records
if record is not None and _matches(record, selectors)
]
if not matches:
return ResolutionResult(
ResolutionState.NOT_FOUND,
detail="no caller-authorized task exactly matches all selectors",
)
if len(matches) > 1:
ids = sorted(
str(_projection(record).get("task_id") or "")
for record in matches
)
return ResolutionResult(
ResolutionState.AMBIGUOUS,
candidate_task_ids=tuple(ids),
detail="multiple exact matches; resolver refuses to guess",
)
record = matches[0]
projected = _projection(record)
task_id = projected.get("task_id")
if not task_id:
return ResolutionResult(
ResolutionState.NOT_FOUND,
detail="matching record lacks authoritative task_id",
)
resolved = ResolvedTask(
task_id=task_id,
mission_id=projected.get("mission_id"),
root_task_id=projected.get("root_task_id"),
parent_task_id=projected.get("parent_task_id"),
owner_principal_id=projected.get("owner_principal_id"),
record=record,
matched_selectors=tuple(sorted(selectors)),
)
return ResolutionResult(ResolutionState.RESOLVED, resolved=resolved)
@@ -1,912 +0,0 @@
from __future__ import annotations
import hashlib
import json
import time
from dataclasses import asdict, dataclass, field
from enum import Enum
from typing import Any, Mapping, Protocol, Sequence
from .governance_control_kernel import (
ActionKind,
ActionLedger,
AutonomousDecisionEngine,
BlockerClassification,
GovernanceControlError,
ManifestStore,
NextAction,
ProducerKind,
)
class AuthorizationDenied(GovernanceControlError):
pass
class GovernedWriterUnavailable(GovernanceControlError):
pass
class OwnerActionRequired(GovernanceControlError):
pass
class GovernedWriterState(str, Enum):
PREFLIGHT_FROZEN = "PREFLIGHT_FROZEN"
OWNER_AUTHORIZED = "OWNER_AUTHORIZED"
EPOCH_VALID = "EPOCH_VALID"
TASK_CLAIMED = "TASK_CLAIMED"
TASK_LEASED = "TASK_LEASED"
PRINCIPAL_ISSUED = "PRINCIPAL_ISSUED"
JOB_BOUND = "JOB_BOUND"
CAS_VERIFIED = "CAS_VERIFIED"
WRITER_LEASED = "WRITER_LEASED"
RISK_ORDER = {
"low": 10,
"medium": 20,
"engineering": 25,
"high": 30,
"critical": 40,
}
def _canonical(value: Any) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _digest(value: Any) -> str:
return "sha256:" + hashlib.sha256(_canonical(value)).hexdigest()
@dataclass(frozen=True)
class OwnerAuthorizationPolicy:
"""Long-lived business authorization, separate from short-lived credentials."""
authorization_id: str
subject: str
repository: str
allowed_branch_prefixes: tuple[str, ...]
allowed_capabilities: frozenset[str]
risk_ceiling: str
trust_domain: str
revocation_epoch: int
not_before: float
expires_at: float | None = None
revoked: bool = False
allowed_operations: frozenset[str] = frozenset(
{
"claim",
"heartbeat",
"task_lease",
"task_principal",
"job_reconcile",
"writer_lease",
"governed_commit",
"readback",
"reconcile",
"epoch_reissue",
"successor_recovery",
}
)
def validate(self, request: "GovernedWriterRequest", *, now: float | None = None) -> None:
now = time.time() if now is None else float(now)
if self.revoked:
raise AuthorizationDenied("owner authorization revoked")
if now < self.not_before:
raise AuthorizationDenied("owner authorization not yet valid")
if self.expires_at is not None and now >= self.expires_at:
raise AuthorizationDenied("owner authorization expired")
if request.subject != self.subject:
raise AuthorizationDenied("subject mismatch")
if request.repository != self.repository:
raise AuthorizationDenied("repository outside authorization")
if not any(request.target_branch.startswith(p) for p in self.allowed_branch_prefixes):
raise AuthorizationDenied("branch outside authorization")
if not request.required_capabilities.issubset(self.allowed_capabilities):
missing = sorted(request.required_capabilities - self.allowed_capabilities)
raise AuthorizationDenied("capability outside authorization: " + ",".join(missing))
ceiling = RISK_ORDER.get(self.risk_ceiling.lower())
requested = RISK_ORDER.get(request.risk.lower())
if ceiling is None or requested is None:
raise AuthorizationDenied("unknown risk level")
if requested > ceiling:
raise AuthorizationDenied("risk exceeds authorization ceiling")
@property
def policy_fingerprint(self) -> str:
return _digest(
{
"authorization_id": self.authorization_id,
"subject": self.subject,
"repository": self.repository,
"allowed_branch_prefixes": list(self.allowed_branch_prefixes),
"allowed_capabilities": sorted(self.allowed_capabilities),
"risk_ceiling": self.risk_ceiling,
"trust_domain": self.trust_domain,
"revocation_epoch": self.revocation_epoch,
"not_before": self.not_before,
"expires_at": self.expires_at,
"allowed_operations": sorted(self.allowed_operations),
}
)
@dataclass(frozen=True)
class GovernedWriterRequest:
subject: str
task_id: str
repository: str
target_branch: str
risk: str
generation: int
base_oid: str
required_capabilities: frozenset[str]
file_allowlist: tuple[str, ...]
plan_digest: str
manifest_id: str
runtime_fingerprint: str
process_start_identity: str
@property
def scope_fingerprint(self) -> str:
return _digest(
{
"subject": self.subject,
"task_id": self.task_id,
"repository": self.repository,
"target_branch": self.target_branch,
"risk": self.risk,
"generation": self.generation,
"base_oid": self.base_oid,
"required_capabilities": sorted(self.required_capabilities),
"file_allowlist": list(self.file_allowlist),
"plan_digest": self.plan_digest,
}
)
@dataclass(frozen=True)
class GovernedWriteReadiness:
"""Unambiguous write-path semantics.
`legacy_global_writes_enabled=False` is a required safety invariant, not a
blocker. `governed_write_path_open` is derived from the scoped writer
prerequisites below and is intentionally independent from any ambiguous
legacy `writes_enabled` field.
"""
legacy_global_writes_enabled: bool
task_principal_writes_enabled: bool
task_principal_verifier_ready: bool
governed_write_blockers: tuple[str, ...] = ()
@property
def blockers(self) -> tuple[str, ...]:
blockers = list(self.governed_write_blockers)
if self.legacy_global_writes_enabled:
blockers.append("LEGACY_GLOBAL_WRITES_MUST_REMAIN_DISABLED")
if not self.task_principal_writes_enabled:
blockers.append("TASK_PRINCIPAL_WRITES_DISABLED")
if not self.task_principal_verifier_ready:
blockers.append("TASK_PRINCIPAL_VERIFIER_NOT_READY")
return tuple(dict.fromkeys(blockers))
@property
def governed_write_path_open(self) -> bool:
return not self.blockers
@classmethod
def from_runtime_status(cls, status: Mapping[str, Any]) -> "GovernedWriteReadiness":
# Deliberately ignore ambiguous `writes_enabled` /
# `AIOS_GITOPS_WRITES_ENABLED`; those describe legacy/global surfaces
# in older control planes and must never be used as the sole governed
# writer readiness signal.
readiness = status.get("task_principal_readiness") or {}
blockers = status.get("governed_write_blockers")
if blockers is None:
blockers = status.get("blockers") or ()
return cls(
legacy_global_writes_enabled=bool(
status.get(
"legacy_global_writes_enabled",
status.get("legacy_writes_enabled", False),
)
),
task_principal_writes_enabled=bool(
status.get("task_principal_writes_enabled", False)
),
task_principal_verifier_ready=bool(
readiness.get("ready", status.get("task_principal_verifier_ready", False))
),
governed_write_blockers=tuple(str(x) for x in blockers),
)
@dataclass(frozen=True)
class EpochStatus:
epoch_id: str
scope_fingerprint: str
expires_at: float
policy_digest: str
def is_valid(self, *, now: float, minimum_remaining_ttl: float) -> bool:
return (self.expires_at - now) >= minimum_remaining_ttl
@dataclass(frozen=True)
class TaskState:
task_id: str
status: str
revision: int
claimed_by: str | None = None
@dataclass(frozen=True)
class ClaimReceipt:
claim_id: str
task_id: str
actor: str
fencing: int
@dataclass(frozen=True)
class TaskLeaseReceipt:
task_lease_id: str
task_id: str
token_ref: str
expires_at: float
fencing: int
@dataclass(frozen=True)
class TaskPrincipalReceipt:
principal_id: str
task_id: str
capabilities: frozenset[str]
expires_at: float
@dataclass(frozen=True)
class JobBindingReceipt:
job_id: str
task_id: str
repository: str
target_branch: str
generation: int
@dataclass(frozen=True)
class BranchReadback:
exists: bool
head_oid: str | None
@dataclass(frozen=True)
class WriterLeaseReceipt:
writer_lease_id: str
task_id: str
job_id: str
repository: str
target_branch: str
branch_mode: str
expected_branch_oid: str
generation: int
expires_at: float
fencing: int
@dataclass(frozen=True)
class GovernedWriterSession:
state: GovernedWriterState
authorization_id: str
policy_fingerprint: str
scope_fingerprint: str
epoch_id: str
task_id: str
claim_id: str
task_lease_id: str
principal_id: str
job_id: str
writer_lease_id: str
branch_mode: str
expected_branch_oid: str
expires_at: float
evidence_refs: tuple[str, ...] = ()
def to_dict(self) -> dict[str, Any]:
result = asdict(self)
result["state"] = self.state.value
return result
@dataclass(frozen=True)
class RecoveryAuthorization:
"""Pre-approved signed recovery/successor authorization reference."""
authorization_ref: str
task_id: str
action: str # TERMINAL_RECOVERY | SUCCESSOR
expires_at: float
class TrustedSignerAdapter(Protocol):
def reissue_epoch(
self,
*,
owner_policy: OwnerAuthorizationPolicy,
request: GovernedWriterRequest,
prior_epoch: EpochStatus | None,
minimum_remaining_ttl: float,
idempotency_key: str,
) -> EpochStatus: ...
class ExecutionPlane(Protocol):
def read_task(self, task_id: str) -> TaskState: ...
def apply_recovery(
self,
*,
task: TaskState,
authorization: RecoveryAuthorization,
idempotency_key: str,
) -> TaskState: ...
def claim(
self,
*,
task: TaskState,
actor: str,
idempotency_key: str,
) -> ClaimReceipt: ...
def heartbeat(self, *, claim: ClaimReceipt) -> None: ...
def issue_task_lease(
self,
*,
claim: ClaimReceipt,
idempotency_key: str,
) -> TaskLeaseReceipt: ...
class TaskPrincipalIssuer(Protocol):
def issue(
self,
*,
lease: TaskLeaseReceipt,
requested_capabilities: frozenset[str],
idempotency_key: str,
) -> TaskPrincipalReceipt: ...
class JobAuthority(Protocol):
def read_or_reconcile(
self,
*,
task_id: str,
principal: TaskPrincipalReceipt,
idempotency_key: str,
) -> JobBindingReceipt: ...
class GitAuthority(Protocol):
def read_branch(self, *, repository: str, target_branch: str) -> BranchReadback: ...
class WriterLeaseAuthority(Protocol):
def acquire(
self,
*,
task_id: str,
job: JobBindingReceipt,
principal: TaskPrincipalReceipt,
repository: str,
target_branch: str,
branch_mode: str,
base_oid: str,
expected_branch_oid: str,
generation: int,
idempotency_key: str,
) -> WriterLeaseReceipt: ...
@dataclass(frozen=True)
class VerifiedOwnerAuthorization:
policy: OwnerAuthorizationPolicy
authorization_ref: str
verification_receipt: str
verified_at: float
class OwnerAuthorizationAuthority(Protocol):
def verify(
self,
*,
authorization_ref: str,
request: GovernedWriterRequest,
) -> VerifiedOwnerAuthorization: ...
@dataclass
class GovernedWriterAdapters:
signer: TrustedSignerAdapter
execution: ExecutionPlane
principal_issuer: TaskPrincipalIssuer
jobs: JobAuthority
git: GitAuthority
writer_lease: WriterLeaseAuthority
owner_authority: OwnerAuthorizationAuthority | None = None
class GovernedWriterAutomation:
"""Owner-policy-driven acquisition of a scoped governed writer capability.
This is the mechanism that removes repeated manual intervention. Long-lived
OwnerAuthorizationPolicy establishes allowed scope. Every concrete effect is
still protected by fresh runtime manifest, short-lived TaskLease,
TaskPrincipal, WriterLease, CAS and idempotency.
"""
def __init__(
self,
*,
manifest_store: ManifestStore,
action_ledger: ActionLedger,
decision_engine: AutonomousDecisionEngine | None = None,
):
self.manifest_store = manifest_store
self.action_ledger = action_ledger
self.decision_engine = decision_engine or AutonomousDecisionEngine()
def _begin_action(
self,
*,
request: GovernedWriterRequest,
suffix: str,
kind: ActionKind,
target: str,
precondition: Mapping[str, Any],
):
return self.action_ledger.begin(
idempotency_key=f"writer:{request.scope_fingerprint}:{suffix}",
manifest_id=request.manifest_id,
phase="GOVERNED_WRITER",
kind=kind,
target=target,
precondition_hash=_digest(precondition),
)
def _ensure_epoch(
self,
*,
request: GovernedWriterRequest,
owner_policy: OwnerAuthorizationPolicy,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
now: float,
minimum_remaining_ttl: float,
) -> EpochStatus:
if (
epoch is not None
and epoch.scope_fingerprint == request.scope_fingerprint
and epoch.is_valid(now=now, minimum_remaining_ttl=minimum_remaining_ttl)
):
return epoch
# Changed scope may not silently inherit an old Owner grant.
if epoch is not None and epoch.scope_fingerprint != request.scope_fingerprint:
raise OwnerActionRequired("epoch scope changed; new Owner authorization required")
ticket = self._begin_action(
request=request,
suffix="epoch",
kind=ActionKind.EPOCH_ISSUANCE,
target="trusted-grant-signer-adapter",
precondition={
"owner_policy": owner_policy.policy_fingerprint,
"scope": request.scope_fingerprint,
"prior_epoch": None if epoch is None else epoch.epoch_id,
},
)
if ticket.reused and ticket.requires_reconcile:
raise GovernedWriterUnavailable("epoch issuance effect unknown; reconcile required")
if ticket.reused and ticket.state.value == "SUCCEEDED":
# The ledger proves the action succeeded, but the concrete receipt
# must be obtained from the signer/readback, not fabricated locally.
raise GovernedWriterUnavailable("epoch already issued; signer readback required")
try:
fresh = adapters.signer.reissue_epoch(
owner_policy=owner_policy,
request=request,
prior_epoch=epoch,
minimum_remaining_ttl=minimum_remaining_ttl,
idempotency_key=ticket.idempotency_key,
)
if fresh.scope_fingerprint != request.scope_fingerprint:
raise GovernedWriterUnavailable("signer returned mismatched scope")
if not fresh.is_valid(now=now, minimum_remaining_ttl=minimum_remaining_ttl):
raise GovernedWriterUnavailable("signer returned insufficient TTL")
self.action_ledger.mark_succeeded(
ticket.action_id,
result={
"epoch_id": fresh.epoch_id,
"expires_at": fresh.expires_at,
"scope_fingerprint": fresh.scope_fingerprint,
},
)
return fresh
except OwnerActionRequired:
self.action_ledger.mark_failed(ticket.action_id, result={"owner_action": True})
raise
except Exception as exc:
self.action_ledger.mark_effect_unknown(
ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
def _ensure_claimable_task(
self,
*,
request: GovernedWriterRequest,
recovery_authorization: RecoveryAuthorization | None,
adapters: GovernedWriterAdapters,
now: float,
) -> TaskState:
task = adapters.execution.read_task(request.task_id)
status = task.status.upper()
if status == "QUEUED":
return task
if status in {"CLAIMED", "RUNNING"}:
return task
if status == "EXPIRED":
if recovery_authorization is None:
raise OwnerActionRequired(
"terminal task expired; owner-signed recovery/successor authorization required"
)
if recovery_authorization.task_id != task.task_id:
raise AuthorizationDenied("recovery authorization task mismatch")
if recovery_authorization.expires_at <= now:
raise AuthorizationDenied("recovery authorization expired")
if recovery_authorization.action not in {"TERMINAL_RECOVERY", "SUCCESSOR"}:
raise AuthorizationDenied("unsupported recovery action")
ticket = self._begin_action(
request=request,
suffix="task-recovery",
kind=ActionKind.GENERIC_MUTATION,
target="agent-task-terminal-recovery",
precondition={
"task_id": task.task_id,
"revision": task.revision,
"status": task.status,
"authorization_ref": recovery_authorization.authorization_ref,
"action": recovery_authorization.action,
},
)
if ticket.reused and ticket.requires_reconcile:
raise GovernedWriterUnavailable("task recovery effect unknown; reconcile required")
try:
recovered = adapters.execution.apply_recovery(
task=task,
authorization=recovery_authorization,
idempotency_key=ticket.idempotency_key,
)
if recovered.status.upper() != "QUEUED":
raise GovernedWriterUnavailable(
"recovery did not return claimable QUEUED task"
)
self.action_ledger.mark_succeeded(
ticket.action_id,
result={
"task_id": recovered.task_id,
"status": recovered.status,
"revision": recovered.revision,
},
)
return recovered
except Exception as exc:
self.action_ledger.mark_effect_unknown(
ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
raise GovernedWriterUnavailable(f"task status not claimable: {task.status}")
def acquire_verified(
self,
*,
request: GovernedWriterRequest,
authorization_ref: str,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
"""Production entry: verify Owner authorization before any effect.
The field composition root MUST use this method. `acquire()` is an
internal/test hook for already-verified policy objects.
"""
if adapters.owner_authority is None:
raise AuthorizationDenied("OwnerAuthorizationAuthority is not configured")
verified = adapters.owner_authority.verify(
authorization_ref=authorization_ref,
request=request,
)
if verified.authorization_ref != authorization_ref:
raise AuthorizationDenied("owner authorization reference mismatch")
if not verified.verification_receipt:
raise AuthorizationDenied("owner authorization verification receipt missing")
return self.acquire(
request=request,
owner_policy=verified.policy,
readiness=readiness,
epoch=epoch,
adapters=adapters,
recovery_authorization=recovery_authorization,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
def acquire(
self,
*,
request: GovernedWriterRequest,
owner_policy: OwnerAuthorizationPolicy,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
now = time.time() if now is None else float(now)
# Gate 1: authoritative frozen runtime.
self.manifest_store.assert_current(
manifest_id=request.manifest_id,
runtime_fingerprint=request.runtime_fingerprint,
process_start_identity=request.process_start_identity,
)
# Gate 2: long-lived Owner policy.
owner_policy.validate(request, now=now)
# Gate 3: scoped writer readiness, deliberately independent from legacy
# global-write switches.
if not readiness.governed_write_path_open:
raise GovernedWriterUnavailable(
"governed writer not ready: " + ",".join(readiness.blockers)
)
# Gate 4: JIT delegation epoch. If unchanged scope is authorized, the
# Agent may use the trusted signer adapter autonomously.
epoch = self._ensure_epoch(
request=request,
owner_policy=owner_policy,
epoch=epoch,
adapters=adapters,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
# Gate 5: task must be claimable, with Owner recovery only when the
# lifecycle contract explicitly requires a signed terminal recovery.
task = self._ensure_claimable_task(
request=request,
recovery_authorization=recovery_authorization,
adapters=adapters,
now=now,
)
# Claim is idempotent per scope/task revision.
claim_ticket = self._begin_action(
request=request,
suffix=f"claim-r{task.revision}",
kind=ActionKind.CLAIM,
target=f"task:{task.task_id}",
precondition={"task_id": task.task_id, "revision": task.revision, "status": task.status},
)
if claim_ticket.reused and claim_ticket.requires_reconcile:
raise GovernedWriterUnavailable("claim effect unknown; reconcile required")
if task.status.upper() in {"CLAIMED", "RUNNING"}:
raise GovernedWriterUnavailable(
"existing claim must be reconciled into a concrete claim receipt"
)
try:
claim = adapters.execution.claim(
task=task,
actor=request.subject,
idempotency_key=claim_ticket.idempotency_key,
)
adapters.execution.heartbeat(claim=claim)
self.action_ledger.mark_succeeded(
claim_ticket.action_id,
result={"claim_id": claim.claim_id, "fencing": claim.fencing},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
claim_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
lease_ticket = self._begin_action(
request=request,
suffix=f"tasklease-{claim.claim_id}",
kind=ActionKind.GENERIC_MUTATION,
target="task-lease",
precondition={"claim_id": claim.claim_id, "fencing": claim.fencing},
)
try:
task_lease = adapters.execution.issue_task_lease(
claim=claim,
idempotency_key=lease_ticket.idempotency_key,
)
self.action_ledger.mark_succeeded(
lease_ticket.action_id,
result={
"task_lease_id": task_lease.task_lease_id,
"expires_at": task_lease.expires_at,
},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
lease_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
principal_ticket = self._begin_action(
request=request,
suffix=f"principal-{task_lease.task_lease_id}",
kind=ActionKind.GENERIC_MUTATION,
target="task-principal-issuer",
precondition={
"task_lease_id": task_lease.task_lease_id,
"capabilities": sorted(request.required_capabilities),
},
)
try:
principal = adapters.principal_issuer.issue(
lease=task_lease,
requested_capabilities=request.required_capabilities,
idempotency_key=principal_ticket.idempotency_key,
)
if not request.required_capabilities.issubset(principal.capabilities):
raise GovernedWriterUnavailable(
"issued TaskPrincipal lacks required capabilities"
)
self.action_ledger.mark_succeeded(
principal_ticket.action_id,
result={"principal_id": principal.principal_id, "expires_at": principal.expires_at},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
principal_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
job_ticket = self._begin_action(
request=request,
suffix="job",
kind=ActionKind.GENERIC_MUTATION,
target="gitops-job",
precondition={
"task_id": task.task_id,
"principal_id": principal.principal_id,
"plan_digest": request.plan_digest,
},
)
try:
job = adapters.jobs.read_or_reconcile(
task_id=task.task_id,
principal=principal,
idempotency_key=job_ticket.idempotency_key,
)
if (
job.repository != request.repository
or job.target_branch != request.target_branch
or job.generation != request.generation
):
raise GovernedWriterUnavailable("job binding mismatch")
self.action_ledger.mark_succeeded(
job_ticket.action_id,
result={"job_id": job.job_id},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
job_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
branch = adapters.git.read_branch(
repository=request.repository,
target_branch=request.target_branch,
)
if branch.exists:
if not branch.head_oid:
raise GovernedWriterUnavailable("branch exists without readable HEAD")
branch_mode = "CONTINUE_EXISTING"
expected_branch_oid = branch.head_oid
else:
branch_mode = "CREATE_NEW"
expected_branch_oid = request.base_oid
writer_ticket = self._begin_action(
request=request,
suffix=f"writer-{job.job_id}-{expected_branch_oid}",
kind=ActionKind.GENERIC_MUTATION,
target="writer-lease",
precondition={
"job_id": job.job_id,
"principal_id": principal.principal_id,
"branch_mode": branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": expected_branch_oid,
"generation": request.generation,
},
)
try:
writer = adapters.writer_lease.acquire(
task_id=task.task_id,
job=job,
principal=principal,
repository=request.repository,
target_branch=request.target_branch,
branch_mode=branch_mode,
base_oid=request.base_oid,
expected_branch_oid=expected_branch_oid,
generation=request.generation,
idempotency_key=writer_ticket.idempotency_key,
)
if writer.expected_branch_oid != expected_branch_oid:
raise GovernedWriterUnavailable("WriterLease CAS mismatch")
self.action_ledger.mark_succeeded(
writer_ticket.action_id,
result={
"writer_lease_id": writer.writer_lease_id,
"expires_at": writer.expires_at,
"expected_branch_oid": writer.expected_branch_oid,
},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
writer_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
expires_at = min(
epoch.expires_at,
task_lease.expires_at,
principal.expires_at,
writer.expires_at,
)
return GovernedWriterSession(
state=GovernedWriterState.WRITER_LEASED,
authorization_id=owner_policy.authorization_id,
policy_fingerprint=owner_policy.policy_fingerprint,
scope_fingerprint=request.scope_fingerprint,
epoch_id=epoch.epoch_id,
task_id=task.task_id,
claim_id=claim.claim_id,
task_lease_id=task_lease.task_lease_id,
principal_id=principal.principal_id,
job_id=job.job_id,
writer_lease_id=writer.writer_lease_id,
branch_mode=branch_mode,
expected_branch_oid=expected_branch_oid,
expires_at=expires_at,
)
@@ -1,789 +0,0 @@
"""Fail-closed host adapter for the C6 governed source-patch pilot.
The adapter owns no worker registration, authority database, effect ledger, or
Git mutation implementation. Those are host-owned. It only joins an exact C6
operation binding and v3 TaskPlane claim to an existing durable external
transaction owner.
The transaction owner is a security boundary. Its ``execute_prepared`` method
must durably record the send phase before an external call, call
``before_external_call`` while holding its own lease/fence, acquire and persist
the real WriterLease, enforce expected-OID CAS at the write linearization point,
and leave an ambiguous call in EFFECT_UNKNOWN. A callback by itself is not an
atomic revocation or CAS fence.
``RootActionConsumer`` exposes an exact-key facade only after the host installs
a trusted operation-to-review binding provider. C6 callers cannot synthesize
review_id, mission_id, root_task_id, root_revision, plan digest, or execution
key. The facade reuses the consumer's existing review, fence, and effect rows.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import hashlib
import json
import math
import re
import time
from typing import Literal, Protocol
from release_host_authority import VerifiedCurrentRecordReader
from .c6_composition import DurableExecutionIntent
from .operation_view import ClosureObservation, OperationBinding, binding_digest, validate_oid
from .patch_contract import ExecutablePatchV1, parse_executable_patch
from .schedulability_store_adapter import TaskPlaneClaimReceiptV3
_ACTION_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,191}$")
_DIGEST = re.compile(r"^[0-9a-f]{64}$")
HOST_PATCH_AUTHORITY_PURPOSE = "aios.c6.host-patch-authority.current.v1"
HOST_PATCH_RECONCILE_AUTHORITY_PURPOSE = (
"aios.c6.host-patch-reconcile-authority.current.v1"
)
HOST_PATCH_AUTHORITY_SCHEMA = "aios.c6.host-patch-authority.v1"
_AUTHORITY_RECORD_FIELDS = frozenset(
{
"schema",
"authorization_ref",
"authority_generation",
"task_generation",
"claim_id",
"claim_fence",
"expected_branch_oid",
"binding_digest",
"lifecycle_state",
"scope_state",
"expires_at",
}
)
def _canonical_json_bytes(value: Mapping[str, object]) -> bytes:
return json.dumps(
dict(value), ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
ActionState = Literal[
"PREPARED", "IN_PROGRESS", "EFFECT_UNKNOWN", "COMMITTED", "FAILED_FINAL"
]
class HostPatchIntegrationError(RuntimeError):
"""The host binding is incomplete, stale, or contradictory."""
class HostPatchEffectUnknown(HostPatchIntegrationError):
"""The same immutable action must be reconciled, never resubmitted."""
class HostPatchConfigurationError(HostPatchIntegrationError):
"""No production-safe exact-key transaction owner is configured."""
@dataclass(frozen=True)
class HostPatchArtifact:
document: Mapping[str, object]
patch_bytes: bytes
allowed_files: frozenset[str]
commit_message: str
def __post_init__(self) -> None:
if not isinstance(self.document, Mapping):
raise ValueError("patch document provider must return a mapping")
if not isinstance(self.patch_bytes, bytes) or not self.patch_bytes:
raise ValueError("patch provider must return non-empty bytes")
if not isinstance(self.allowed_files, frozenset) or not self.allowed_files:
raise ValueError("patch provider must return a frozen non-empty allowlist")
if (
not isinstance(self.commit_message, str)
or not self.commit_message.strip()
or self.commit_message != self.commit_message.strip()
):
raise ValueError("canonical commit message required")
@dataclass(frozen=True)
class HostPatchAuthority:
"""Fresh host readback before a transaction-owner call.
``claim_fence`` is the TaskPlane receipt revision. It is deliberately
separate from the WriterLease fence returned by the external transaction.
"""
authorization_ref: str
authority_key_id: str
authority_key_fingerprint: str
authority_record_sha256: str
authority_generation: int
task_generation: int
claim_id: str
claim_fence: int
expected_branch_oid: str
expires_at: float
def __post_init__(self) -> None:
if (
not isinstance(self.authorization_ref, str)
or not self.authorization_ref.strip()
or not isinstance(self.authority_key_id, str)
or not self.authority_key_id.strip()
or not isinstance(self.claim_id, str)
or not self.claim_id.strip()
):
raise ValueError("host authority identity required")
if (
not isinstance(self.authority_key_fingerprint, str)
or _DIGEST.fullmatch(self.authority_key_fingerprint) is None
):
raise ValueError("verified host authority key fingerprint required")
if (
not isinstance(self.authority_record_sha256, str)
or _DIGEST.fullmatch(self.authority_record_sha256) is None
):
raise ValueError("verified host authority record digest required")
for name in ("authority_generation", "task_generation", "claim_fence"):
value = getattr(self, name)
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError(f"invalid host authority {name}")
validate_oid(self.expected_branch_oid)
if (
isinstance(self.expires_at, bool)
or not isinstance(self.expires_at, (int, float))
or not math.isfinite(float(self.expires_at))
):
raise ValueError("invalid host authority expiry")
@dataclass(frozen=True)
class HostPatchAction:
"""Readback owned by the existing external transaction boundary."""
action_id: str
state: ActionState
binding_digest: str
effect_key: str
authorization_ref: str
authority_key_id: str
authority_key_fingerprint: str
authority_record_sha256: str
claim_id: str
claim_fence: int
task_generation: int
authority_generation: int
expected_branch_oid: str
claim_receipt: Mapping[str, object]
writer_lease_id: str | None = None
writer_lease_fence: int | None = None
result_oid: str | None = None
receipt_ref: str | None = None
def __post_init__(self) -> None:
if not isinstance(self.action_id, str) or _ACTION_ID.fullmatch(self.action_id) is None:
raise ValueError("canonical immutable action_id required")
if self.state not in {
"PREPARED", "IN_PROGRESS", "EFFECT_UNKNOWN", "COMMITTED", "FAILED_FINAL"
}:
raise ValueError("invalid host patch action state")
if not isinstance(self.binding_digest, str) or _DIGEST.fullmatch(self.binding_digest) is None:
raise ValueError("action binding digest required")
if not isinstance(self.effect_key, str) or not self.effect_key:
raise ValueError("action effect key required")
if (
not isinstance(self.authorization_ref, str)
or not self.authorization_ref
or not isinstance(self.authority_key_id, str)
or not self.authority_key_id
or not isinstance(self.authority_key_fingerprint, str)
or _DIGEST.fullmatch(self.authority_key_fingerprint) is None
or not isinstance(self.authority_record_sha256, str)
or _DIGEST.fullmatch(self.authority_record_sha256) is None
or not isinstance(self.claim_id, str)
or not self.claim_id
or not isinstance(self.claim_receipt, Mapping)
):
raise ValueError("action authority/claim identity required")
for name in ("claim_fence", "task_generation", "authority_generation"):
value = getattr(self, name)
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError(f"invalid action {name}")
validate_oid(self.expected_branch_oid)
if self.writer_lease_fence is not None and (
isinstance(self.writer_lease_fence, bool)
or not isinstance(self.writer_lease_fence, int)
or self.writer_lease_fence < 0
):
raise ValueError("invalid WriterLease fence")
if self.result_oid is not None:
validate_oid(self.result_oid)
if self.state == "COMMITTED":
if (
not self.writer_lease_id
or self.writer_lease_fence is None
or self.result_oid is None
or not self.receipt_ref
):
raise ValueError("committed action lacks WriterLease/OID/receipt binding")
class SignedHostPatchAuthorityReader:
"""Build host authority only from a verified, purpose-bound current record.
The underlying reader enforces a registered key, signature, short lifetime,
exact query hash, and a fresh nonce. This remains a freshness challenge;
the root-action transaction owner still supplies the atomic fence and CAS.
Host configuration must register both effect and reconcile purposes to the
same authority key id/raw-key fingerprint. Reconcile has a distinct signed
query and grants observation only, while prepared identity remains the same
stable authorization semantics across the two purposes.
"""
def __init__(self, current_records: VerifiedCurrentRecordReader) -> None:
if not isinstance(current_records, VerifiedCurrentRecordReader):
raise TypeError("current_records must be VerifiedCurrentRecordReader")
purposes = (
HOST_PATCH_AUTHORITY_PURPOSE,
HOST_PATCH_RECONCILE_AUTHORITY_PURPOSE,
)
try:
key_ids = [current_records.purpose_key_ids[purpose] for purpose in purposes]
fingerprints = [
current_records.purpose_key_fingerprints[purpose]
for purpose in purposes
]
except KeyError as exc:
raise HostPatchConfigurationError(
"effect and reconcile authority purposes must both be registered"
) from exc
if (
any(len(ids) != 1 for ids in key_ids)
or key_ids[0] != key_ids[1]
or set(fingerprints[0]) != set(key_ids[0])
or fingerprints[0] != fingerprints[1]
):
raise HostPatchConfigurationError(
"effect and reconcile purposes require one identical key/fingerprint"
)
self._records = current_records
@staticmethod
def _query(
binding: OperationBinding, claim: Mapping[str, object], *, reconcile: bool
) -> dict[str, object]:
return {
"schema": (
"aios.c6.host-patch-reconcile-authority-query.v1"
if reconcile
else "aios.c6.host-patch-authority-query.v1"
),
"binding": asdict(binding),
"binding_digest": binding_digest(binding),
"claim": dict(claim),
}
def _read(
self, binding: OperationBinding, claim: Mapping[str, object], *, reconcile: bool
) -> HostPatchAuthority:
purpose = (
HOST_PATCH_RECONCILE_AUTHORITY_PURPOSE
if reconcile
else HOST_PATCH_AUTHORITY_PURPOSE
)
verified = self._records.read(
purpose, self._query(binding, claim, reconcile=reconcile)
)
record = verified.record
if frozenset(record) != _AUTHORITY_RECORD_FIELDS:
missing = sorted(_AUTHORITY_RECORD_FIELDS - frozenset(record))
extra = sorted(frozenset(record) - _AUTHORITY_RECORD_FIELDS)
raise HostPatchIntegrationError(
f"host authority record fields are not exact; missing={missing!r} extra={extra!r}"
)
if (
record["schema"] != HOST_PATCH_AUTHORITY_SCHEMA
or record["lifecycle_state"] != "ACTIVE"
or record["scope_state"] != "IN_SCOPE"
or record["binding_digest"] != binding_digest(binding)
or record["task_generation"] != binding.generation
or record["claim_id"] != claim.get("claim_id")
or record["claim_fence"] != claim.get("receipt_revision")
or record["expected_branch_oid"] != binding.source_oid
):
raise HostPatchIntegrationError("signed host authority binding mismatch")
if any(
not isinstance(record[name], str) or not record[name].strip()
for name in (
"authorization_ref", "claim_id", "expected_branch_oid",
"binding_digest", "lifecycle_state", "scope_state",
)
):
raise HostPatchIntegrationError("signed host authority text fields are invalid")
for name in ("authority_generation", "task_generation", "claim_fence"):
if (
isinstance(record[name], bool)
or not isinstance(record[name], int)
or record[name] < 0
):
raise HostPatchIntegrationError(
f"signed host authority {name} is invalid"
)
if (
isinstance(record["expires_at"], bool)
or not isinstance(record["expires_at"], (int, float))
or not math.isfinite(float(record["expires_at"]))
):
raise HostPatchIntegrationError("signed host authority expiry is invalid")
try:
envelope_expiry = datetime.fromisoformat(
verified.expires_at.replace("Z", "+00:00")
).astimezone(timezone.utc).timestamp()
authority = HostPatchAuthority(
authorization_ref=record["authorization_ref"],
authority_key_id=verified.key_id,
authority_key_fingerprint=verified.key_fingerprint,
# The signed envelope and its short observation expiry change
# across fresh reads. Prepared identity binds only the stable
# authorization semantics; freshness is independently checked
# on every read.
authority_record_sha256=hashlib.sha256(
_canonical_json_bytes(
{key: value for key, value in record.items() if key != "expires_at"}
)
).hexdigest(),
authority_generation=record["authority_generation"], # type: ignore[arg-type]
task_generation=record["task_generation"], # type: ignore[arg-type]
claim_id=record["claim_id"],
claim_fence=record["claim_fence"], # type: ignore[arg-type]
expected_branch_oid=record["expected_branch_oid"],
expires_at=min(float(record["expires_at"]), envelope_expiry),
)
except (TypeError, ValueError) as exc:
raise HostPatchIntegrationError("signed host authority record is invalid") from exc
if (
not reconcile
and authority.expires_at
> float(claim.get("lease_until", authority.expires_at))
):
raise HostPatchIntegrationError("host authority outlives the TaskPlane claim")
return authority
def __call__(
self, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3
) -> HostPatchAuthority:
if not isinstance(claim, TaskPlaneClaimReceiptV3):
raise TypeError("v3 TaskPlane claim receipt required")
return self._read(binding, claim.to_dict(), reconcile=False)
def read_for_action(
self, binding: OperationBinding, action: HostPatchAction
) -> HostPatchAuthority:
if not isinstance(action, HostPatchAction):
raise TypeError("host patch action required")
return self._read(binding, action.claim_receipt, reconcile=True)
def signed_host_patch_authority_provider(
current_records: VerifiedCurrentRecordReader,
) -> SignedHostPatchAuthorityReader:
"""Factory used by host composition without weakening reader type checks."""
return SignedHostPatchAuthorityReader(current_records)
class HostPatchTransactionOwner(Protocol):
"""Exact-key facade implemented by an existing durable transaction owner."""
def read_exact(self, effect_key: str) -> HostPatchAction | None: ...
def iter_pending_exact(
self, *, limit: int
) -> tuple[tuple[OperationBinding, HostPatchAction], ...]: ...
def mark_exact_closed(
self, *, binding: OperationBinding, action_id: str,
observation: ClosureObservation
) -> None: ...
def prepare_exact(
self,
*,
binding: OperationBinding,
claim: TaskPlaneClaimReceiptV3,
authority: HostPatchAuthority,
contract: ExecutablePatchV1,
patch_bytes: bytes,
commit_message: str,
) -> HostPatchAction: ...
def reconcile_exact(
self,
*,
action_id: str,
binding: OperationBinding,
authority: HostPatchAuthority,
before_external_call: Callable[[], None],
) -> HostPatchAction: ...
def execute_prepared(
self,
*,
action_id: str,
binding: OperationBinding,
claim: TaskPlaneClaimReceiptV3,
authority: HostPatchAuthority,
contract: ExecutablePatchV1,
patch_bytes: bytes,
commit_message: str,
before_external_call: Callable[[], None],
) -> HostPatchAction: ...
_EXACT_OWNER_METHODS = (
"read_exact", "iter_pending_exact", "mark_exact_closed", "prepare_exact", "reconcile_exact",
"execute_prepared"
)
def require_exact_root_action_owner(service: object) -> HostPatchTransactionOwner:
"""Return an exact-key root-action facade or fail before enabling C6.
``TaskPlaneService.root_actions`` currently exposes ``run_once`` only, so a
stock service is rejected. A production composition may install a facade
backed by the same RootActionConsumer/PublicBridgeRootActionExecutor store;
it must not create another effect ledger.
"""
owner = getattr(service, "root_actions", None)
missing = [name for name in _EXACT_OWNER_METHODS if not callable(getattr(owner, name, None))]
if missing:
raise HostPatchConfigurationError(
"exact root-action transaction owner unavailable: " + ",".join(missing)
)
if getattr(owner, "store", None) is not getattr(service, "store", None):
raise HostPatchConfigurationError(
"exact root-action owner must reuse the host TaskPlane store"
)
configured = getattr(owner, "assert_exact_owner_configured", None)
if not callable(configured):
raise HostPatchConfigurationError(
"exact root-action owner configuration assertion unavailable"
)
try:
configured()
except Exception as exc:
raise HostPatchConfigurationError(
"exact root-action transaction owner is not configured"
) from exc
return owner # type: ignore[return-value]
class HostPatchExecutor:
"""Concrete ``GovernedSourcePatchPilot`` over host-owned formal providers."""
def __init__(
self,
*,
artifact_provider: Callable[[OperationBinding], HostPatchArtifact],
authority_provider: Callable[
[OperationBinding, TaskPlaneClaimReceiptV3], HostPatchAuthority
],
transaction_owner: HostPatchTransactionOwner,
clock: Callable[[], float] | None = None,
) -> None:
if not callable(artifact_provider) or not callable(authority_provider):
raise HostPatchConfigurationError("artifact and authority providers required")
if not callable(getattr(authority_provider, "read_for_action", None)):
raise HostPatchConfigurationError(
"authority provider must support signed restart reconciliation"
)
if any(not callable(getattr(transaction_owner, name, None)) for name in _EXACT_OWNER_METHODS):
raise HostPatchConfigurationError("exact external transaction owner required")
self._artifacts = artifact_provider
self._authority_provider = authority_provider
self._transactions = transaction_owner
self._clock = clock or time.time
def _artifact(self, binding: OperationBinding) -> tuple[HostPatchArtifact, ExecutablePatchV1]:
artifact = self._artifacts(binding)
if not isinstance(artifact, HostPatchArtifact):
raise HostPatchIntegrationError("invalid host patch artifact")
contract = parse_executable_patch(
artifact.document,
patch_bytes=artifact.patch_bytes,
allowed_files=artifact.allowed_files,
expected_binding=binding,
)
return artifact, contract
def _authority(
self, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3
) -> HostPatchAuthority:
value = self._authority_provider(binding, claim)
if not isinstance(value, HostPatchAuthority):
raise HostPatchIntegrationError("invalid host authority readback")
if (
value.claim_id != claim.claim_id
or value.claim_fence != claim.receipt_revision
or value.task_generation != binding.generation
or value.expected_branch_oid != binding.source_oid
or value.expires_at <= float(self._clock())
):
raise HostPatchIntegrationError("stale or mismatched host authority")
return value
@staticmethod
def _validate_action(
action: HostPatchAction,
binding: OperationBinding,
*,
claim_id: str | None = None,
claim_fence: int | None = None,
authority: HostPatchAuthority | None = None,
) -> HostPatchAction:
if not isinstance(action, HostPatchAction):
raise HostPatchIntegrationError("transaction owner returned invalid action")
if (
action.binding_digest != binding_digest(binding)
or action.effect_key != binding.effect_key
or action.task_generation != binding.generation
or action.expected_branch_oid != binding.source_oid
or (claim_id is not None and action.claim_id != claim_id)
or (claim_fence is not None and action.claim_fence != claim_fence)
):
raise HostPatchIntegrationError("external action binding mismatch")
if authority is not None and (
action.authorization_ref != authority.authorization_ref
or action.authority_key_id != authority.authority_key_id
or action.authority_key_fingerprint != authority.authority_key_fingerprint
or action.authority_record_sha256 != authority.authority_record_sha256
or action.authority_generation != authority.authority_generation
or action.claim_id != authority.claim_id
or action.claim_fence != authority.claim_fence
or action.expected_branch_oid != authority.expected_branch_oid
):
raise HostPatchIntegrationError("external action authority changed")
if action.state == "FAILED_FINAL":
raise HostPatchIntegrationError("external action failed final")
return action
def read_intent(self, binding: OperationBinding) -> DurableExecutionIntent | None:
"""C6 ``intent_reader`` wiring backed only by the transaction owner."""
action = self._transactions.read_exact(binding.effect_key)
if action is None:
return None
action = self._validate_action(action, binding)
return DurableExecutionIntent(
binding_digest(binding), binding.effect_key, action.claim_id, action.action_id
)
def pending_intents(
self, *, limit: int = 16
) -> tuple[tuple[OperationBinding, DurableExecutionIntent], ...]:
if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= 64:
raise HostPatchConfigurationError("bounded pending intent limit required")
result: list[tuple[OperationBinding, DurableExecutionIntent]] = []
for pending_binding, action in self._transactions.iter_pending_exact(limit=limit):
checked = self._validate_action(action, pending_binding)
if checked.state not in {
"PREPARED", "IN_PROGRESS", "EFFECT_UNKNOWN", "COMMITTED"
}:
raise HostPatchIntegrationError("transaction owner enumerated failed action")
result.append(
(
pending_binding,
DurableExecutionIntent(
binding_digest(pending_binding),
pending_binding.effect_key,
checked.claim_id,
checked.action_id,
),
)
)
return tuple(result)
def mark_intent_closed(
self, binding: OperationBinding, intent: DurableExecutionIntent,
observation: ClosureObservation,
) -> None:
if (
not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
or not isinstance(observation, ClosureObservation)
):
raise HostPatchIntegrationError("invalid durable closure acknowledgement")
self._transactions.mark_exact_closed(
binding=binding, action_id=intent.receipt_ref, observation=observation
)
def prepare_intent(
self, *, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3
) -> DurableExecutionIntent:
artifact, contract = self._artifact(binding)
authority = self._authority(binding, claim)
action = self._transactions.prepare_exact(
binding=binding,
claim=claim,
authority=authority,
contract=contract,
patch_bytes=artifact.patch_bytes,
commit_message=artifact.commit_message,
)
action = self._validate_action(
action,
binding,
claim_id=claim.claim_id,
claim_fence=claim.receipt_revision,
authority=authority,
)
return DurableExecutionIntent(
binding_digest(binding), binding.effect_key, claim.claim_id, action.action_id
)
def execute(
self,
*,
binding: OperationBinding,
claim: TaskPlaneClaimReceiptV3,
intent: DurableExecutionIntent,
) -> None:
if (
not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
or intent.claim_id != claim.claim_id
):
raise HostPatchIntegrationError("execution intent binding mismatch")
artifact, contract = self._artifact(binding)
authority = self._authority(binding, claim)
action = self._transactions.read_exact(binding.effect_key)
if action is None or action.action_id != intent.receipt_ref:
raise HostPatchIntegrationError("durable external action unavailable")
action = self._validate_action(
action, binding, claim_id=claim.claim_id,
claim_fence=claim.receipt_revision, authority=authority,
)
def revalidate() -> None:
fresh = self._authority(binding, claim)
if (
fresh.authorization_ref != authority.authorization_ref
or fresh.authority_key_id != authority.authority_key_id
or fresh.authority_key_fingerprint != authority.authority_key_fingerprint
or fresh.authority_record_sha256 != authority.authority_record_sha256
or fresh.authority_generation != authority.authority_generation
or fresh.task_generation != authority.task_generation
or fresh.claim_id != authority.claim_id
or fresh.claim_fence != authority.claim_fence
or fresh.expected_branch_oid != authority.expected_branch_oid
):
raise HostPatchIntegrationError("host authority changed before external call")
observed = self._transactions.reconcile_exact(
action_id=action.action_id,
binding=binding,
authority=authority,
before_external_call=revalidate,
)
observed = self._validate_action(
observed, binding, claim_id=claim.claim_id,
claim_fence=claim.receipt_revision, authority=authority,
)
if observed.action_id != action.action_id:
raise HostPatchIntegrationError("immutable external action_id changed")
if observed.state == "COMMITTED":
return
if observed.state in {"IN_PROGRESS", "EFFECT_UNKNOWN"}:
raise HostPatchEffectUnknown("external effect requires exact-key reconciliation")
if observed.state != "PREPARED":
raise HostPatchIntegrationError("external action is not executable")
completed = self._transactions.execute_prepared(
action_id=action.action_id,
binding=binding,
claim=claim,
authority=authority,
contract=contract,
patch_bytes=artifact.patch_bytes,
commit_message=artifact.commit_message,
before_external_call=revalidate,
)
completed = self._validate_action(
completed, binding, claim_id=claim.claim_id,
claim_fence=claim.receipt_revision, authority=authority,
)
if completed.action_id != action.action_id:
raise HostPatchIntegrationError("immutable external action_id changed")
if completed.state != "COMMITTED":
raise HostPatchEffectUnknown("external transaction did not return committed readback")
def reconcile(
self, *, binding: OperationBinding, intent: DurableExecutionIntent
) -> None:
"""Reconcile a durable intent without acquiring a lease or resending a commit."""
if (
not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
):
raise HostPatchIntegrationError("reconciliation intent binding mismatch")
action = self._transactions.read_exact(binding.effect_key)
if action is None or action.action_id != intent.receipt_ref:
raise HostPatchIntegrationError("durable external action unavailable")
action = self._validate_action(action, binding, claim_id=intent.claim_id)
reader = self._authority_provider
authority = reader.read_for_action(binding, action) # type: ignore[attr-defined]
if not isinstance(authority, HostPatchAuthority):
raise HostPatchIntegrationError("invalid reconciliation authority readback")
action = self._validate_action(action, binding, authority=authority)
def revalidate() -> None:
fresh = reader.read_for_action(binding, action) # type: ignore[attr-defined]
if (
not isinstance(fresh, HostPatchAuthority)
or fresh.authorization_ref != authority.authorization_ref
or fresh.authority_key_id != authority.authority_key_id
or fresh.authority_key_fingerprint != authority.authority_key_fingerprint
or fresh.authority_record_sha256 != authority.authority_record_sha256
or fresh.authority_generation != authority.authority_generation
or fresh.task_generation != authority.task_generation
or fresh.claim_id != authority.claim_id
or fresh.claim_fence != authority.claim_fence
or fresh.expected_branch_oid != authority.expected_branch_oid
):
raise HostPatchIntegrationError(
"host authority changed before reconciliation call"
)
observed = self._transactions.reconcile_exact(
action_id=action.action_id,
binding=binding,
authority=authority,
before_external_call=revalidate,
)
observed = self._validate_action(observed, binding, authority=authority)
if observed.action_id != action.action_id:
raise HostPatchIntegrationError("immutable external action_id changed")
if observed.state != "COMMITTED":
raise HostPatchEffectUnknown("external effect remains unresolved")
__all__ = [
"HostPatchAction",
"HostPatchArtifact",
"HostPatchAuthority",
"HostPatchConfigurationError",
"HostPatchEffectUnknown",
"HostPatchExecutor",
"HostPatchIntegrationError",
"HostPatchTransactionOwner",
"HOST_PATCH_AUTHORITY_PURPOSE",
"HOST_PATCH_AUTHORITY_SCHEMA",
"SignedHostPatchAuthorityReader",
"require_exact_root_action_owner",
"signed_host_patch_authority_provider",
]
@@ -1,113 +0,0 @@
"""Authenticated host readback for independent verification and acceptance.
The host supplies separate, registered signing identities and current-record
transports. This adapter never creates receipts or treats an ``authenticated``
payload flag as evidence. Missing providers remain UNKNOWN through OperationReaders.
"""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import asdict, dataclass
from release_host_authority import VerifiedCurrentRecordReader
from .operation_view import OperationBinding, binding_digest, validate_digest
@dataclass(frozen=True)
class ReceiptAuthorityPolicy:
verifier_id: str
verifier_key_id: str
acceptor_id: str
acceptor_key_id: str
acceptance_revision: str
verifier_key_fingerprint: str
acceptor_key_fingerprint: str
def __post_init__(self) -> None:
for name, value in asdict(self).items():
if not isinstance(value, str) or not value or value.strip() != value or "\x00" in value:
raise ValueError(f"invalid {name}")
if self.verifier_id == self.acceptor_id or self.verifier_key_id == self.acceptor_key_id:
raise ValueError("verification and acceptance must use independent identities and keys")
validate_digest(self.verifier_key_fingerprint)
validate_digest(self.acceptor_key_fingerprint)
if self.verifier_key_fingerprint == self.acceptor_key_fingerprint:
raise ValueError("verification and acceptance require independent public keys")
class HostOperationReceiptReaders:
"""Bind verified transport signatures to host-pinned role identities.
Policies are trusted host configuration, never request or receipt fields.
Receipt/result linkage and freshness are also checked by collect_closure /
derive_closure against the separately observed Git effect.
"""
def __init__(
self,
*,
verifier_reader: VerifiedCurrentRecordReader,
acceptance_reader: VerifiedCurrentRecordReader,
policy: ReceiptAuthorityPolicy,
) -> None:
self._verifier = verifier_reader
self._acceptance = acceptance_reader
self._policy = policy
def _read(self, binding: OperationBinding, *, acceptance: bool) -> dict[str, object]:
policy = self._policy
identity = policy.acceptor_id if acceptance else policy.verifier_id
key_id = policy.acceptor_key_id if acceptance else policy.verifier_key_id
if binding.actor_id in (policy.verifier_id, policy.acceptor_id):
raise ValueError("executor cannot verify or accept its own operation")
kind = "acceptance" if acceptance else "verification"
query = {
"binding": asdict(binding),
"binding_digest": binding_digest(binding),
"expected_identity": identity,
"expected_key_id": key_id,
"expected_key_fingerprint": policy.acceptor_key_fingerprint if acceptance else policy.verifier_key_fingerprint,
}
if acceptance:
query["expected_acceptance_revision"] = policy.acceptance_revision
reader = self._acceptance if acceptance else self._verifier
verified = reader.read(f"operation-{kind}", query)
if verified.key_id != key_id:
raise ValueError("receipt signer does not match the pinned role key")
fingerprint = policy.acceptor_key_fingerprint if acceptance else policy.verifier_key_fingerprint
if verified.key_fingerprint != fingerprint:
raise ValueError("receipt signer public key fingerprint mismatch")
document = verified.record
if not isinstance(document, Mapping) or set(document) != {"schema", kind}:
raise ValueError("invalid signed receipt document")
schema = f"aios.operation-{kind}-observation.v1"
if document["schema"] != schema:
raise ValueError("invalid receipt schema")
observation = document[kind]
common = {"binding_digest", "receipt_ref", "result_artifact_digest", "outcome", "observed_at", "expires_at"}
fields = common | (
{"acceptor_id", "acceptance_revision", "verifier_receipt_ref"}
if acceptance else {"verifier_id", "verification_receipt_ref"}
)
if not isinstance(observation, Mapping) or set(observation) != fields:
raise ValueError("invalid signed receipt observation")
if observation["binding_digest"] != binding_digest(binding):
raise ValueError("receipt binding mismatch")
if observation["acceptor_id" if acceptance else "verifier_id"] != identity:
raise ValueError("receipt identity mismatch")
if acceptance and observation["acceptance_revision"] != policy.acceptance_revision:
raise ValueError("acceptance revision mismatch")
result: dict[str, object] = {
"schema": schema,
kind: {**observation, "authenticated": True},
}
if acceptance:
result["expected_acceptor_id"] = policy.acceptor_id
result["expected_acceptance_revision"] = policy.acceptance_revision
return result
def verifier(self, binding: OperationBinding) -> Mapping[str, object]:
return self._read(binding, acceptance=False)
def acceptance(self, binding: OperationBinding) -> Mapping[str, object]:
return self._read(binding, acceptance=True)
@@ -1,536 +0,0 @@
"""Mission-owned, append-only bindings for verified external legacy executions."""
from __future__ import annotations
import json
import re
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Mapping, Protocol, Sequence
from .errors import ConflictError, NotFoundError, ValidationError
from .models import compute_sha256_hex
ATTACH_REQUEST_SCHEMA = "aios.mission-legacy-attach-request.v1"
ATTACH_EVIDENCE_SCHEMA = "aios.verified-legacy-attachment-evidence.v1"
ATTACH_RECEIPT_SCHEMA = "aios.mission-legacy-attachment-receipt.v1"
ATTACHMENT_KIND = "LEGACY_EXECUTION_SUBJECT"
RELATION_TYPE = "RECOVERY"
SOURCE_KIND = "agent-task-bridge-job"
REQUIRED_READBACK_ROLES = frozenset(
{"agent_task", "admission", "owner_registry", "bridge_job"}
)
REQUIRED_C04_EVIDENCE = frozenset(
{
"verified legacy ownership and acceptance relevance",
"unchanged task/job/idempotency/reconciliation identity",
"authoritative attach readback",
}
)
_SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
def _canonical(value: object) -> str:
try:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
except (TypeError, ValueError) as exc:
raise ValidationError("legacy attachment value must be JSON data") from exc
def _object(value: object, fields: frozenset[str], name: str) -> dict[str, object]:
if not isinstance(value, Mapping) or set(value) != fields:
raise ValidationError(f"invalid {name} fields")
return json.loads(_canonical(dict(value)))
def _identifier(value: object, name: str, *, maximum: int = 256) -> str:
if (
not isinstance(value, str)
or not value
or len(value) > maximum
or any(ord(character) < 33 for character in value)
):
raise ValidationError(f"invalid {name}")
return value
def _integer(value: object, name: str) -> int:
if type(value) is not int or value < 0:
raise ValidationError(f"invalid {name}")
return value
def _utc(value: object, name: str) -> str:
if not isinstance(value, str) or not value:
raise ValidationError(f"invalid {name}")
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise ValidationError(f"invalid {name}") from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise ValidationError(f"invalid {name}")
return parsed.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
@dataclass(frozen=True)
class AuthorityReadbackRef:
"""A non-secret pointer and digest issued by one independent authority."""
role: str
authority: str
readback_ref: str
canonical_sha256: str
observed_at: str
revision: int | None = None
def to_dict(self) -> dict[str, object]:
if self.role not in REQUIRED_READBACK_ROLES:
raise ValidationError("invalid legacy authority readback role")
authority = _identifier(self.authority, "readback authority")
reference = _identifier(self.readback_ref, "readback ref", maximum=1024)
if not _SHA256_RE.fullmatch(self.canonical_sha256):
raise ValidationError("invalid readback canonical SHA-256")
observed = _utc(self.observed_at, "readback observed_at")
revision = self.revision
if revision is not None:
revision = _integer(revision, "readback revision")
return {
"role": self.role,
"authority": authority,
"readback_ref": reference,
"canonical_sha256": self.canonical_sha256,
"observed_at": observed,
"revision": revision,
}
@dataclass(frozen=True)
class VerifiedLegacyAttachmentEvidence:
"""Typed output of a configured authority reader, never a caller payload."""
schema: str
source_kind: str
source_task_id: str
source_task_created_at: str
source_task_status: str
source_task_revision: int
source_task_attempt: int
source_task_max_attempts: int
external_job_id: str
internal_job_id: str
internal_idempotency_key: str
job_branch: str
job_state: str
job_revision: int
side_effect_state: str
grant_id: str
grant_digest: str
execution_digest: str
epoch_id: str
epoch_digest: str
historical_signer_id: str
historical_signer_fingerprint: str
registry_generation: int
signature_verified: bool
epoch_currently_valid: bool
readbacks: Sequence[AuthorityReadbackRef]
def to_dict(self) -> dict[str, object]:
if self.schema != ATTACH_EVIDENCE_SCHEMA or self.source_kind != SOURCE_KIND:
raise ValidationError("invalid legacy attachment evidence schema")
if self.signature_verified is not True or self.epoch_currently_valid is not False:
raise ConflictError("legacy historical Owner evidence is not verified-expired")
values = {
"source_task_id": self.source_task_id,
"source_task_status": self.source_task_status,
"external_job_id": self.external_job_id,
"internal_job_id": self.internal_job_id,
"internal_idempotency_key": self.internal_idempotency_key,
"job_branch": self.job_branch,
"job_state": self.job_state,
"side_effect_state": self.side_effect_state,
"grant_id": self.grant_id,
"historical_signer_id": self.historical_signer_id,
"historical_signer_fingerprint": self.historical_signer_fingerprint,
"epoch_id": self.epoch_id,
}
normalized = {
key: _identifier(value, key, maximum=1024)
for key, value in values.items()
}
for name, value in {
"grant_digest": self.grant_digest,
"execution_digest": self.execution_digest,
"epoch_digest": self.epoch_digest,
}.items():
if not _SHA256_RE.fullmatch(value):
raise ValidationError(f"invalid {name}")
normalized[name] = value
task_revision = _integer(self.source_task_revision, "source_task_revision")
task_attempt = _integer(self.source_task_attempt, "source_task_attempt")
task_max_attempts = _integer(
self.source_task_max_attempts, "source_task_max_attempts"
)
if task_attempt > task_max_attempts:
raise ConflictError("legacy task attempt exceeds max attempts")
job_revision = _integer(self.job_revision, "job_revision")
registry_generation = _integer(
self.registry_generation, "registry_generation"
)
readbacks = [value.to_dict() for value in self.readbacks]
roles = [str(value["role"]) for value in readbacks]
if len(roles) != len(set(roles)) or set(roles) != REQUIRED_READBACK_ROLES:
raise ValidationError("legacy evidence requires four authority readbacks")
readbacks.sort(key=lambda value: str(value["role"]))
return {
"schema": ATTACH_EVIDENCE_SCHEMA,
"source_kind": SOURCE_KIND,
**normalized,
"source_task_created_at": _utc(
self.source_task_created_at, "source_task_created_at"
),
"source_task_revision": task_revision,
"source_task_attempt": task_attempt,
"source_task_max_attempts": task_max_attempts,
"job_revision": job_revision,
"registry_generation": registry_generation,
"signature_verified": True,
"epoch_currently_valid": False,
"readbacks": readbacks,
}
class LegacyAttachmentEvidenceProvider(Protocol):
"""Configured server-side reader for the four independent authority sources."""
def read_verified(
self,
*,
mission_id: str,
root_task_id: str,
anchor_task_id: str,
source_kind: str,
source_task_id: str,
external_job_id: str,
original_acceptance_digest: str,
) -> VerifiedLegacyAttachmentEvidence: ...
def normalize_attach_request(value: object) -> dict[str, object]:
body = _object(
value,
frozenset({"schema", "anchor_task_id", "source", "expected"}),
"legacy attachment request",
)
if body["schema"] != ATTACH_REQUEST_SCHEMA:
raise ValidationError("invalid legacy attachment request schema")
source = _object(
body["source"],
frozenset({"kind", "task_id", "external_job_id"}),
"legacy attachment source",
)
if source["kind"] != SOURCE_KIND:
raise ValidationError("invalid legacy attachment source kind")
expected = _object(
body["expected"],
frozenset(
{
"root_revision",
"anchor_revision",
"agent_task_revision",
"agent_task_attempt",
"internal_job_id",
"job_revision",
"internal_idempotency_key",
}
),
"legacy attachment expected",
)
return {
"schema": ATTACH_REQUEST_SCHEMA,
"anchor_task_id": _identifier(body["anchor_task_id"], "anchor_task_id"),
"source": {
"kind": SOURCE_KIND,
"task_id": _identifier(source["task_id"], "source task_id"),
"external_job_id": _identifier(
source["external_job_id"], "external_job_id"
),
},
"expected": {
"root_revision": _integer(expected["root_revision"], "root_revision"),
"anchor_revision": _integer(
expected["anchor_revision"], "anchor_revision"
),
"agent_task_revision": _integer(
expected["agent_task_revision"], "agent_task_revision"
),
"agent_task_attempt": _integer(
expected["agent_task_attempt"], "agent_task_attempt"
),
"internal_job_id": _identifier(
expected["internal_job_id"], "internal_job_id"
),
"job_revision": _integer(expected["job_revision"], "job_revision"),
"internal_idempotency_key": _identifier(
expected["internal_idempotency_key"], "internal_idempotency_key"
),
},
}
def validate_c04_acceptance(
acceptance: Mapping[str, object], *, source_task_id: str, external_job_id: str
) -> None:
candidate = acceptance.get("legacy_candidate")
if (
not isinstance(candidate, Mapping)
or candidate.get("task_id") != source_task_id
or candidate.get("job_id") != external_job_id
):
raise ConflictError("mission legacy candidate authority mismatch")
conditions = acceptance.get("conditions")
if not isinstance(conditions, list):
raise ConflictError("mission C04 acceptance condition is unavailable")
matches = [
condition
for condition in conditions
if isinstance(condition, Mapping) and condition.get("key") == "C04"
]
if len(matches) != 1:
raise ConflictError("mission C04 acceptance condition is unavailable")
condition = matches[0]
evidence = condition.get("required_evidence")
if (
condition.get("purpose") != "LEGACY-LINEAGE-MIGRATION"
or not isinstance(evidence, list)
or not REQUIRED_C04_EVIDENCE.issubset(
value for value in evidence if isinstance(value, str)
)
):
raise ConflictError("mission C04 acceptance contract mismatch")
def validate_evidence_matches_request(
evidence: Mapping[str, object], request: Mapping[str, object]
) -> None:
source = request["source"]
expected = request["expected"]
assert isinstance(source, Mapping) and isinstance(expected, Mapping)
pairs = {
"source_kind": source["kind"],
"source_task_id": source["task_id"],
"external_job_id": source["external_job_id"],
"source_task_revision": expected["agent_task_revision"],
"source_task_attempt": expected["agent_task_attempt"],
"internal_job_id": expected["internal_job_id"],
"job_revision": expected["job_revision"],
"internal_idempotency_key": expected["internal_idempotency_key"],
}
mismatches = [name for name, value in pairs.items() if evidence.get(name) != value]
if mismatches:
raise ConflictError(
"legacy provider evidence mismatch: " + ",".join(sorted(mismatches))
)
class LegacyAttachmentLedger:
"""Append-only persistence owned by one Mission authority store."""
def __init__(self, store) -> None:
self.store = store
with store._transaction():
store.conn.execute(
"""
CREATE TABLE IF NOT EXISTS task_mission_legacy_attachments (
attachment_id TEXT PRIMARY KEY,
mission_id TEXT NOT NULL,
root_task_id TEXT NOT NULL,
anchor_task_id TEXT NOT NULL,
authenticated_identity TEXT NOT NULL,
owner_principal_id TEXT NOT NULL,
attachment_kind TEXT NOT NULL,
relation_type TEXT NOT NULL,
attachment_sequence INTEGER NOT NULL,
display_name TEXT NOT NULL,
source_kind TEXT NOT NULL,
source_task_id TEXT NOT NULL,
external_job_id TEXT NOT NULL,
internal_job_id TEXT NOT NULL,
internal_idempotency_key TEXT NOT NULL,
request_digest TEXT NOT NULL,
idempotency_key TEXT NOT NULL,
evidence_digest TEXT NOT NULL,
receipt_digest TEXT NOT NULL,
receipt_json TEXT NOT NULL,
created_at REAL NOT NULL,
UNIQUE (authenticated_identity, idempotency_key),
UNIQUE (source_kind, source_task_id),
UNIQUE (source_kind, external_job_id),
UNIQUE (mission_id, anchor_task_id, attachment_sequence),
UNIQUE (mission_id, anchor_task_id, source_kind, source_task_id, external_job_id)
)
"""
)
store.conn.execute(
"CREATE INDEX IF NOT EXISTS ix_mission_legacy_attachments "
"ON task_mission_legacy_attachments(mission_id, anchor_task_id)"
)
store.conn.execute(
"""
CREATE TABLE IF NOT EXISTS task_mission_acceptance_child_bindings (
mission_id TEXT NOT NULL,
condition_key TEXT NOT NULL,
child_task_id TEXT NOT NULL UNIQUE,
original_acceptance_digest TEXT NOT NULL,
binding_digest TEXT NOT NULL,
created_at REAL NOT NULL,
PRIMARY KEY (mission_id, condition_key)
)
"""
)
def acceptance_child_binding(self, mission_id: str, condition_key: str):
return self.store.conn.execute(
"SELECT * FROM task_mission_acceptance_child_bindings "
"WHERE mission_id=? AND condition_key=?",
(mission_id, condition_key),
).fetchone()
def bind_acceptance_child(
self,
*,
mission_id: str,
condition_key: str,
child_task_id: str,
original_acceptance_digest: str,
binding_digest: str,
created_at: float,
) -> None:
existing = self.acceptance_child_binding(mission_id, condition_key)
if existing is not None:
if (
existing["child_task_id"] != child_task_id
or existing["original_acceptance_digest"]
!= original_acceptance_digest
or existing["binding_digest"] != binding_digest
):
raise ConflictError(
"mission acceptance condition already has a canonical child"
)
return
child_binding = self.store.conn.execute(
"SELECT mission_id,condition_key FROM "
"task_mission_acceptance_child_bindings WHERE child_task_id=?",
(child_task_id,),
).fetchone()
if child_binding is not None:
raise ConflictError("mission child already belongs to an acceptance condition")
self.store.conn.execute(
"INSERT INTO task_mission_acceptance_child_bindings "
"(mission_id,condition_key,child_task_id,original_acceptance_digest,"
"binding_digest,created_at) VALUES(?,?,?,?,?,?)",
(
mission_id,
condition_key,
child_task_id,
original_acceptance_digest,
binding_digest,
created_at,
),
)
def by_idempotency(self, identity: str, key: str):
return self.store.conn.execute(
"SELECT * FROM task_mission_legacy_attachments "
"WHERE authenticated_identity=? AND idempotency_key=?",
(identity, key),
).fetchone()
def by_source(self, source_kind: str, task_id: str, external_job_id: str):
return self.store.conn.execute(
"SELECT * FROM task_mission_legacy_attachments "
"WHERE source_kind=? AND (source_task_id=? OR external_job_id=?)",
(source_kind, task_id, external_job_id),
).fetchone()
def by_id(self, mission_id: str, attachment_id: str):
row = self.store.conn.execute(
"SELECT * FROM task_mission_legacy_attachments "
"WHERE mission_id=? AND attachment_id=?",
(mission_id, attachment_id),
).fetchone()
if row is None:
raise NotFoundError("legacy attachment not found")
return row
def next_sequence(self, mission_id: str, anchor_task_id: str) -> int:
row = self.store.conn.execute(
"SELECT COALESCE(MAX(attachment_sequence),0)+1 "
"FROM task_mission_legacy_attachments "
"WHERE mission_id=? AND anchor_task_id=?",
(mission_id, anchor_task_id),
).fetchone()
sequence = int(row[0])
if not 1 <= sequence <= 99:
raise ConflictError("legacy attachment sequence exhausted")
return sequence
def insert(
self,
*,
receipt: Mapping[str, object],
authenticated_identity: str,
idempotency_key: str,
request_digest: str,
evidence_digest: str,
created_at: float,
) -> None:
self.store.conn.execute(
"INSERT INTO task_mission_legacy_attachments "
"(attachment_id,mission_id,root_task_id,anchor_task_id,"
"authenticated_identity,owner_principal_id,attachment_kind,"
"relation_type,attachment_sequence,display_name,source_kind,"
"source_task_id,external_job_id,internal_job_id,"
"internal_idempotency_key,request_digest,idempotency_key,"
"evidence_digest,receipt_digest,receipt_json,created_at) "
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
receipt["attachment_id"], receipt["mission_id"],
receipt["root_task_id"], receipt["anchor_task_id"],
authenticated_identity, receipt["owner_principal_id"],
receipt["attachment_kind"], receipt["relation_type"],
receipt["attachment_sequence"], receipt["display_name"],
receipt["source"]["source_kind"],
receipt["source"]["source_task_id"],
receipt["source"]["external_job_id"],
receipt["source"]["internal_job_id"],
receipt["source"]["internal_idempotency_key"],
request_digest, idempotency_key, evidence_digest,
receipt["receipt_digest"], _canonical(receipt), created_at,
),
)
@staticmethod
def receipt(row) -> dict[str, object]:
try:
value = json.loads(row["receipt_json"])
except (TypeError, ValueError, json.JSONDecodeError) as exc:
raise ConflictError("persisted legacy attachment receipt is invalid") from exc
if (
not isinstance(value, dict)
or value.get("schema") != ATTACH_RECEIPT_SCHEMA
or value.get("receipt_digest") != row["receipt_digest"]
):
raise ConflictError("persisted legacy attachment receipt is invalid")
digest_payload = dict(value)
digest_payload.pop("receipt_digest", None)
digest_payload.pop("idempotent", None)
if compute_sha256_hex(digest_payload) != row["receipt_digest"]:
raise ConflictError("persisted legacy attachment receipt digest mismatch")
return value
@@ -1,846 +0,0 @@
"""Actual TaskPlane binding for task identity, lineage and root review.
All writes use the existing :class:`TaskPlaneStore` connection and transaction
fence. Request metadata is never used as an authorization source.
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Callable, Mapping, Sequence
if __package__ and "." in __package__: # app.task_plane package import
from ..aios_task_lineage import (
AcceptanceCondition,
AcceptanceEvaluator,
NextLegalAction,
RelationType,
RootAcceptanceSnapshot,
RootReviewRecord,
TaskRecord as LineageTaskRecord,
lineage_from_metadata,
normalize_goal_slug,
root_review_from_record,
)
from ..aios_task_lineage_integration import (
DISPLAY_NAME_METADATA_KEY,
LINEAGE_METADATA_KEY,
LINEAGE_REQUEST_DIGEST_KEY,
NewTaskIdentityRequest,
PERSISTED_DISPATCH_STATUSES,
RootActionDispatchReceipt,
TaskAuthoritySnapshot,
TaskIdentityLineageService,
TerminalReviewOutcome,
_as_task_record,
)
from ..aios_task_lineage_taskplane_adapter import (
AuthenticatedTaskContext,
InProcessTaskPlaneLineageAdapter,
TaskPlaneServiceBindings,
)
from ..aios_task_lineage_hooks import TaskLineageLifecycleHooks
else: # top-level task_plane import used by direct-module test harnesses
from aios_task_lineage import (
AcceptanceCondition,
AcceptanceEvaluator,
NextLegalAction,
RelationType,
RootAcceptanceSnapshot,
RootReviewRecord,
TaskRecord as LineageTaskRecord,
lineage_from_metadata,
normalize_goal_slug,
root_review_from_record,
)
from aios_task_lineage_integration import (
DISPLAY_NAME_METADATA_KEY,
LINEAGE_METADATA_KEY,
LINEAGE_REQUEST_DIGEST_KEY,
NewTaskIdentityRequest,
PERSISTED_DISPATCH_STATUSES,
RootActionDispatchReceipt,
TaskAuthoritySnapshot,
TaskIdentityLineageService,
TerminalReviewOutcome,
_as_task_record,
)
from aios_task_lineage_taskplane_adapter import (
AuthenticatedTaskContext,
InProcessTaskPlaneLineageAdapter,
TaskPlaneServiceBindings,
)
from aios_task_lineage_hooks import TaskLineageLifecycleHooks
from .delegation import DelegationEngine
from .errors import ConflictError, ForbiddenError, NotFoundError, ValidationError
from .models import TaskPlan, TaskRecord, canonical_json, compute_sha256_hex
from .store import TaskPlaneStore
RESERVED_LINEAGE_METADATA_KEYS = frozenset(
{LINEAGE_METADATA_KEY, DISPLAY_NAME_METADATA_KEY, LINEAGE_REQUEST_DIGEST_KEY}
)
PROVEN_TERMINAL_STATES = frozenset({"COMPLETED", "FAILED_FINAL", "CANCELLED"})
@dataclass(frozen=True)
class PrincipalLineageBinding:
"""Server-owned projection of one authenticated service identity."""
authenticated_identity: str
owner_principal_id: str
mission_id: str
preferred_owner_alias: str | None
root_acceptance_ref: str
def __post_init__(self) -> None:
for name in (
"authenticated_identity",
"owner_principal_id",
"mission_id",
"root_acceptance_ref",
):
if not str(getattr(self, name)).strip():
raise ValueError(f"{name} is required")
class TaskLineagePolicy:
"""Immutable service policy for authenticated identity and acceptance."""
def __init__(
self,
*,
principal_bindings: Sequence[PrincipalLineageBinding],
acceptance_evaluators: Mapping[str, AcceptanceEvaluator],
acceptance_evaluator_resolver: Callable[
[str], AcceptanceEvaluator | None
]
| None = None,
) -> None:
self._bindings: dict[str, PrincipalLineageBinding] = {}
for binding in principal_bindings:
if binding.authenticated_identity in self._bindings:
raise ValueError("duplicate authenticated lineage identity")
self._bindings[binding.authenticated_identity] = binding
self._evaluators = dict(acceptance_evaluators)
self._acceptance_evaluator_resolver = acceptance_evaluator_resolver
def with_acceptance_evaluator_resolver(
self, resolver: Callable[[str], AcceptanceEvaluator | None]
) -> "TaskLineagePolicy":
previous = self._acceptance_evaluator_resolver
def combined(reference: str) -> AcceptanceEvaluator | None:
resolved = resolver(reference)
if resolved is not None:
return resolved
return previous(reference) if previous is not None else None
return TaskLineagePolicy(
principal_bindings=tuple(self._bindings.values()),
acceptance_evaluators=self._evaluators,
acceptance_evaluator_resolver=combined,
)
def resolve_principal(self, authenticated_identity: str) -> PrincipalLineageBinding:
try:
return self._bindings[authenticated_identity]
except KeyError as exc:
raise ForbiddenError("authenticated identity has no lineage policy binding") from exc
def _acceptance_evaluator(self, reference: str) -> AcceptanceEvaluator | None:
evaluator = self._evaluators.get(reference)
if evaluator is None and self._acceptance_evaluator_resolver is not None:
evaluator = self._acceptance_evaluator_resolver(reference)
return evaluator
def assert_acceptance_ref_registered(self, reference: str) -> None:
if self._acceptance_evaluator(reference) is None:
raise ForbiddenError("root acceptance policy is not registered")
def evaluate_acceptance(
self,
root: LineageTaskRecord,
descendants: Sequence[LineageTaskRecord],
) -> RootAcceptanceSnapshot:
reference = root.lineage.root_acceptance_ref
evaluator = self._acceptance_evaluator(reference)
if evaluator is None:
raise ForbiddenError("root acceptance policy is not registered")
snapshot = evaluator(root, descendants)
if not isinstance(snapshot, RootAcceptanceSnapshot) or not snapshot.conditions:
return RootAcceptanceSnapshot(
(
AcceptanceCondition(
key="root-acceptance-proof-unavailable",
satisfied=False,
owner_boundary=True,
),
)
)
normalized: list[AcceptanceCondition] = []
for condition in snapshot.conditions:
if condition.satisfied and not condition.evidence_refs:
normalized.append(
AcceptanceCondition(
key=condition.key,
satisfied=False,
owner_boundary=True,
)
)
else:
normalized.append(condition)
return RootAcceptanceSnapshot(tuple(normalized))
class TaskPlaneLineageStoreBinding:
"""Official in-process read/write surface over the existing TaskPlane DB."""
def __init__(self, store: TaskPlaneStore) -> None:
self.store = store
def service_bindings(self) -> TaskPlaneServiceBindings:
return TaskPlaneServiceBindings(
get_task_snapshot=self.get_task_snapshot,
list_descendant_snapshots=self.list_descendant_snapshots,
reserve_sibling_sequence=self.reserve_sibling_sequence,
compare_and_swap_task_metadata=self.compare_and_swap_task_metadata,
get_root_review_record=self.get_root_review_record,
compare_and_swap_root_review_record=self.compare_and_swap_root_review_record,
get_root_action_dispatch=self.get_root_action_dispatch,
dispatch_root_action=self.dispatch_root_action,
)
def _terminal_proof(self, task: TaskRecord) -> Mapping[str, object] | None:
row = self.store.conn.execute(
"SELECT * FROM task_terminal_proofs WHERE task_id=?", (task.task_id,)
).fetchone()
if row is None or task.status not in PROVEN_TERMINAL_STATES:
return None
claim = self.store.conn.execute(
"SELECT status,result_payload FROM claims WHERE claim_id=? AND task_id=?",
(row["claim_id"], task.task_id),
).fetchone()
if claim is None or claim["status"] != "COMPLETED" or not claim["result_payload"]:
return None
try:
result = json.loads(claim["result_payload"])
refs = tuple(json.loads(row["evidence_refs_json"]))
except (TypeError, ValueError, json.JSONDecodeError):
return None
if result.get("result_digest") != row["result_digest"]:
return None
if task.revision < int(row["task_revision"]):
return None
evidence_ids = {entry["evidence_id"] for entry in self.store.list_evidence(task.task_id)}
if not set(refs).issubset(evidence_ids):
return None
if not self.store.verify_event_chain(task.task_id):
return None
terminal_event = any(
event["type"] in {"TASK_COMPLETED", "TASK_FAILED"}
and event["data"].get("claim_id") == row["claim_id"]
for event in self.store.list_events(task.task_id)
)
if not terminal_event or not self.store.verify_evidence_integrity(task.task_id):
return None
return dict(row)
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot:
task = self.store.get_task(task_id)
if not task.owner_principal_id or not task.mission_id:
raise ForbiddenError("task lacks server-owned lineage authority binding")
proof = self._terminal_proof(task)
return TaskAuthoritySnapshot(
task_id=task.task_id,
revision=task.revision,
status=task.status,
owner_principal_id=task.owner_principal_id,
mission_id=task.mission_id,
created_at=datetime.fromtimestamp(task.created_at, tz=timezone.utc),
metadata=dict(task.task_plan.metadata),
evidence_refs=(
tuple(json.loads(proof["evidence_refs_json"])) if proof else ()
),
terminal_evidence_proven=proof is not None,
)
def list_descendant_snapshots(
self, root_task_id: str
) -> tuple[TaskAuthoritySnapshot, ...]:
with self.store._lock:
rows = self.store.conn.execute(
"SELECT task_id FROM tasks WHERE root_task_id=? AND task_id<>? "
"ORDER BY created_at,task_id",
(root_task_id, root_task_id),
).fetchall()
return tuple(self.get_task_snapshot(row["task_id"]) for row in rows)
def reserve_sibling_sequence(
self, parent_task_id: str, relation_type: RelationType, child_task_id: str
) -> int:
with self.store._transaction():
existing = self.store.conn.execute(
"SELECT parent_task_id,relation_type,sibling_sequence "
"FROM task_lineage_sequences WHERE child_task_id=?",
(child_task_id,),
).fetchone()
if existing is not None:
if (
existing["parent_task_id"] != parent_task_id
or existing["relation_type"] != relation_type.value
):
raise ConflictError("child sibling reservation conflicts with lineage")
return int(existing["sibling_sequence"])
row = self.store.conn.execute(
"SELECT COALESCE(MAX(sibling_sequence),0)+1 AS next_sequence "
"FROM task_lineage_sequences WHERE parent_task_id=? AND relation_type=?",
(parent_task_id, relation_type.value),
).fetchone()
sequence = int(row["next_sequence"])
if sequence > 99:
raise ConflictError("lineage sibling sequence exhausted")
self.store.conn.execute(
"INSERT INTO task_lineage_sequences "
"(parent_task_id,relation_type,child_task_id,sibling_sequence,created_at) "
"VALUES(?,?,?,?,?)",
(parent_task_id, relation_type.value, child_task_id, sequence, datetime.now(tz=timezone.utc).timestamp()),
)
return sequence
def compare_and_swap_task_metadata(
self, task_id: str, expected_revision: int, metadata_patch: Mapping[str, object]
) -> bool:
if not set(metadata_patch).issubset(RESERVED_LINEAGE_METADATA_KEYS):
raise ForbiddenError("lineage CAS contains non-lineage metadata")
with self.store._transaction():
task = self.store.get_task(task_id)
if task.revision != expected_revision:
return False
lineage_value = metadata_patch.get(LINEAGE_METADATA_KEY)
if isinstance(lineage_value, Mapping):
lineage = lineage_from_metadata(lineage_value)
if lineage.relation_type != RelationType.ROOT:
# The root check and child metadata CAS share this BEGIN
# IMMEDIATE transaction, closing the legacy attach race.
self.store.assert_root_open_for_child(lineage.root_task_id)
if task.status in PROVEN_TERMINAL_STATES:
raise ConflictError("terminal task lineage metadata is immutable")
payload = task.task_plan.to_dict(redact=False)
metadata = dict(payload.get("metadata") or {})
for key, value in metadata_patch.items():
if key in metadata and metadata[key] != value:
raise ConflictError("authoritative lineage metadata conflict")
metadata[key] = value
payload["metadata"] = metadata
now = datetime.now(tz=timezone.utc).timestamp()
updated = self.store.conn.execute(
"UPDATE tasks SET task_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=? AND revision=?",
(json.dumps(payload, ensure_ascii=False), now, task_id, expected_revision),
)
if updated.rowcount != 1:
return False
self.store.append_event(
task_id,
"TASK_LINEAGE_BOUND",
{"metadata_keys": sorted(metadata_patch)},
subject=f"task/{task_id}",
)
return True
def record_terminal_proof(
self,
*,
task_id: str,
claim_id: str,
result_digest: str,
evidence_refs: Sequence[str],
) -> None:
with self.store._transaction():
task = self.store.get_task(task_id)
if task.status not in PROVEN_TERMINAL_STATES:
raise ConflictError("terminal proof requires a final task status")
claim = self.store.conn.execute(
"SELECT status,result_payload FROM claims WHERE claim_id=? AND task_id=?",
(claim_id, task_id),
).fetchone()
if claim is None or claim["status"] != "COMPLETED" or not claim["result_payload"]:
raise ConflictError("terminal proof requires a completed claim")
stored_result = json.loads(claim["result_payload"])
if stored_result.get("result_digest") != result_digest:
raise ConflictError("terminal proof result digest mismatch")
refs = tuple(str(value) for value in evidence_refs)
evidence_ids = {entry["evidence_id"] for entry in self.store.list_evidence(task_id)}
if not set(refs).issubset(evidence_ids):
raise ConflictError("terminal proof evidence reference mismatch")
existing = self.store.conn.execute(
"SELECT * FROM task_terminal_proofs WHERE task_id=?", (task_id,)
).fetchone()
expected = (claim_id, result_digest, canonical_json(list(refs)))
if existing is not None:
actual = (
existing["claim_id"],
existing["result_digest"],
canonical_json(json.loads(existing["evidence_refs_json"])),
)
if actual != expected:
raise ConflictError("terminal proof replay conflict")
return
if not self.store.verify_event_chain(task_id) or not self.store.verify_evidence_integrity(task_id):
raise ConflictError("terminal proof journal integrity failed")
self.store.conn.execute(
"INSERT INTO task_terminal_proofs "
"(task_id,claim_id,task_revision,result_digest,evidence_refs_json,created_at) "
"VALUES(?,?,?,?,?,?)",
(
task_id,
claim_id,
task.revision,
result_digest,
json.dumps(list(refs), ensure_ascii=False),
datetime.now(tz=timezone.utc).timestamp(),
),
)
def get_root_review_record(self, review_id: str) -> Mapping[str, object] | None:
row = self.store.conn.execute(
"SELECT record_json FROM task_root_reviews WHERE review_id=?", (review_id,)
).fetchone()
return None if row is None else json.loads(row["record_json"])
def get_root_review_for_trigger(
self, trigger_child_task_id: str
) -> Mapping[str, object] | None:
row = self.store.conn.execute(
"SELECT record_json FROM task_root_reviews "
"WHERE trigger_child_task_id=? ORDER BY created_at LIMIT 1",
(trigger_child_task_id,),
).fetchone()
return None if row is None else json.loads(row["record_json"])
def compare_and_swap_root_review_record(
self,
root_task_id: str,
expected_root_revision: int,
review_record: Mapping[str, object],
) -> bool:
with self.store._transaction():
review_id = str(review_record.get("review_id") or "")
trigger_id = str(review_record.get("trigger_child_task_id") or "")
if (
review_record.get("root_task_id") != root_task_id
or int(review_record.get("root_revision_before", -1)) != expected_root_revision
or int(review_record.get("root_revision_after", -1)) != expected_root_revision + 1
):
raise ConflictError("root review authority binding mismatch")
existing = self.get_root_review_record(review_id)
if existing is not None:
if canonical_json(existing) != canonical_json(dict(review_record)):
raise ConflictError("root review replay conflict")
return True
trigger = self.get_task_snapshot(trigger_id)
if not trigger.terminal_evidence_proven:
raise ConflictError("root review trigger is not proven terminal")
trigger_revision = trigger.revision
frozen = self.store.conn.execute(
"SELECT review_id FROM task_root_reviews "
"WHERE trigger_child_task_id=? AND trigger_child_revision=?",
(trigger_id, trigger_revision),
).fetchone()
if frozen is not None:
raise ConflictError("terminal trigger already has a different root review")
root = self.store.get_task(root_task_id)
if root.revision != expected_root_revision:
return False
now = datetime.now(tz=timezone.utc).timestamp()
self.store.conn.execute(
"INSERT INTO task_root_reviews "
"(review_id,root_task_id,trigger_child_task_id,trigger_child_revision,"
"root_revision_before,root_revision_after,record_json,created_at) "
"VALUES(?,?,?,?,?,?,?,?)",
(
review_id,
root_task_id,
trigger_id,
trigger_revision,
expected_root_revision,
expected_root_revision + 1,
json.dumps(dict(review_record), ensure_ascii=False, sort_keys=True),
now,
),
)
updated = self.store.conn.execute(
"UPDATE tasks SET revision=revision+1,updated_at=? "
"WHERE task_id=? AND revision=?",
(now, root_task_id, expected_root_revision),
)
if updated.rowcount != 1:
raise ConflictError("root review CAS lost after reservation")
self.store.append_event(
root_task_id,
"ROOT_REVIEW",
{
"review_id": review_id,
"trigger_child_task_id": trigger_id,
"next_legal_action": review_record.get("next_legal_action"),
"root_complete": bool(review_record.get("root_complete")),
},
subject=f"task/{root_task_id}",
)
return True
def get_root_action_dispatch(self, review_id: str) -> RootActionDispatchReceipt | None:
row = self.store.conn.execute(
"SELECT * FROM task_root_action_dispatches WHERE review_id=?", (review_id,)
).fetchone()
if row is None:
return None
return RootActionDispatchReceipt(
review_id=row["review_id"],
root_task_id=row["root_task_id"],
action=NextLegalAction(row["action"]),
idempotency_key=row["idempotency_key"],
status=row["status"],
mission_id=row["mission_id"],
root_revision=int(row["root_revision"]),
plan_digest=row["plan_digest"],
execution_key=row["execution_key"],
)
def dispatch_root_action(
self, review: RootReviewRecord, idempotency_key: str
) -> RootActionDispatchReceipt:
with self.store._transaction():
persisted = self.get_root_review_record(review.review_id)
if persisted is None:
raise ConflictError("root action requires a persisted root review")
existing = self.get_root_action_dispatch(review.review_id)
if existing is not None:
if (
existing.root_task_id != review.root_task_id
or existing.action != review.next_legal_action
or existing.idempotency_key != idempotency_key
):
raise ConflictError("root action dispatch replay conflict")
return existing
now = datetime.now(tz=timezone.utc).timestamp()
self.store.conn.execute(
"INSERT INTO task_root_action_dispatches "
"(review_id,root_task_id,action,idempotency_key,status,created_at,"
"mission_id,root_revision,updated_at) VALUES(?,?,?,?,?,?,?,?,?)",
(
review.review_id,
review.root_task_id,
review.next_legal_action.value,
idempotency_key,
"PENDING",
now,
self.store.get_task(review.root_task_id).mission_id,
review.root_revision_after,
now,
),
)
self.store.append_event(
review.root_task_id,
"ROOT_NEXT_ACTION_PENDING",
{
"review_id": review.review_id,
"action": review.next_legal_action.value,
"idempotency_key": idempotency_key,
},
subject=f"task/{review.root_task_id}",
)
return self.get_root_action_dispatch(review.review_id) # type: ignore[return-value]
class TaskPlaneLineageCoordinator:
"""Coordinates lineage with real TaskPlane admission and completion paths."""
def __init__(self, store: TaskPlaneStore, policy: TaskLineagePolicy) -> None:
self.store = store
self.policy = policy
self.binding = TaskPlaneLineageStoreBinding(store)
self.adapter = InProcessTaskPlaneLineageAdapter(
self.binding.service_bindings(), readback=self.binding
)
self.service = TaskIdentityLineageService(self.adapter)
self.hooks = TaskLineageLifecycleHooks(self.adapter)
@staticmethod
def _request_digest(payload: Mapping[str, object]) -> str:
return compute_sha256_hex(dict(payload))
@staticmethod
def _reject_reserved_metadata(plan: TaskPlan) -> None:
if set(plan.metadata).intersection(RESERVED_LINEAGE_METADATA_KEYS):
raise ValidationError("lineage metadata is server-owned")
@staticmethod
def _goal_slug(plan: TaskPlan) -> str:
for candidate in (plan.intent, plan.task_type, f"TASK-{plan.task_id}"):
if not candidate:
continue
try:
return normalize_goal_slug(str(candidate))
except ValueError:
continue
raise ValidationError("task goal cannot be normalized for lineage")
def _existing_by_idempotency(self, key: str | None) -> TaskRecord | None:
if not key:
return None
row = self.store.conn.execute(
"SELECT task_id FROM tasks WHERE idempotency_key=?", (key,)
).fetchone()
return None if row is None else self.store.get_task(row["task_id"])
@staticmethod
def _verify_replay(
task: TaskRecord,
*,
request_digest: str,
principal: PrincipalLineageBinding,
parent_task_id: str | None,
) -> TaskRecord:
metadata = task.task_plan.metadata
if (
metadata.get(LINEAGE_REQUEST_DIGEST_KEY) != request_digest
or task.owner_principal_id != principal.owner_principal_id
or task.mission_id != principal.mission_id
or task.task_plan.parent_task_id != parent_task_id
):
raise ConflictError("lineage admission replay conflict")
return task
def create_root(
self,
*,
plan: TaskPlan,
authenticated_identity: str,
request_digest: str,
idempotency_key: str | None,
) -> TaskRecord:
principal = self.policy.resolve_principal(authenticated_identity)
return self.create_root_for_principal(
plan=plan,
principal=principal,
request_digest=request_digest,
idempotency_key=idempotency_key,
)
def create_root_for_principal(
self,
*,
plan: TaskPlan,
principal: PrincipalLineageBinding,
request_digest: str,
idempotency_key: str | None,
created_at: datetime | None = None,
) -> TaskRecord:
self.policy.assert_acceptance_ref_registered(principal.root_acceptance_ref)
self._reject_reserved_metadata(plan)
with self.store._transaction():
existing = self._existing_by_idempotency(idempotency_key)
if existing is not None:
return self._verify_replay(
existing,
request_digest=request_digest,
principal=principal,
parent_task_id=None,
)
created_at = created_at or datetime.now(tz=timezone.utc)
if created_at.tzinfo is None or created_at.utcoffset() is None:
raise ValidationError("lineage created_at must be timezone-aware")
created_at = created_at.astimezone(timezone.utc)
prepared = self.service.prepare_new_task_identity(
NewTaskIdentityRequest(
task_id=str(plan.task_id),
owner_principal_id=principal.owner_principal_id,
preferred_owner_alias=principal.preferred_owner_alias,
created_at=created_at,
mission_id=principal.mission_id,
goal_slug=self._goal_slug(plan),
root_acceptance_ref=principal.root_acceptance_ref,
relation_type=RelationType.ROOT,
)
)
plan.metadata = {
**plan.metadata,
**prepared.metadata_patch,
LINEAGE_REQUEST_DIGEST_KEY: request_digest,
}
return self.store.create_task_with_event(
plan,
idempotency_key=idempotency_key,
owner_principal_id=principal.owner_principal_id,
mission_id=principal.mission_id,
created_at=created_at.timestamp(),
)
def delegate_child(
self,
*,
parent_task_id: str,
plan: TaskPlan,
authenticated_identity: str,
request_digest: str,
delegation: DelegationEngine,
principal: PrincipalLineageBinding | None = None,
) -> TaskRecord:
if not plan.task_id:
raise ValidationError("lineage delegation requires a stable task_id")
principal = principal or self.policy.resolve_principal(authenticated_identity)
if principal.authenticated_identity != authenticated_identity:
raise ForbiddenError("delegation principal identity mismatch")
self._reject_reserved_metadata(plan)
with self.store._transaction():
parent = self.store.get_task(parent_task_id)
root = self.store.get_task(parent.root_task_id)
if (
root.owner_principal_id != principal.owner_principal_id
or root.mission_id != principal.mission_id
):
raise ForbiddenError("delegation actor does not match root authority")
try:
existing = self.store.get_task(str(plan.task_id))
except NotFoundError:
existing = None
if existing is not None:
return self._verify_replay(
existing,
request_digest=request_digest,
principal=principal,
parent_task_id=parent_task_id,
)
self.store.assert_root_open_for_child(root.task_id)
created_at = datetime.now(tz=timezone.utc)
prepared = self.service.prepare_new_task_identity(
NewTaskIdentityRequest(
task_id=str(plan.task_id),
owner_principal_id=root.owner_principal_id,
preferred_owner_alias=principal.preferred_owner_alias,
created_at=created_at,
mission_id=root.mission_id,
goal_slug=self._goal_slug(plan),
root_acceptance_ref=str(
root.task_plan.metadata[LINEAGE_METADATA_KEY]["root_acceptance_ref"]
),
relation_type=RelationType.CHILD,
parent_task_id=parent_task_id,
)
)
plan.metadata = {
**plan.metadata,
**prepared.metadata_patch,
LINEAGE_REQUEST_DIGEST_KEY: request_digest,
}
return delegation.delegate(
parent_task_id,
plan,
caller=authenticated_identity,
owner_principal_id=root.owner_principal_id,
mission_id=root.mission_id,
created_at=created_at.timestamp(),
)
def current_acceptance_snapshot(self, *, root_task_id: str) -> RootAcceptanceSnapshot:
"""Evaluate the current authoritative root/descendant projection."""
root = _as_task_record(self.binding.get_task_snapshot(root_task_id))
descendants = tuple(
_as_task_record(snapshot)
for snapshot in self.binding.list_descendant_snapshots(root_task_id)
)
return self.policy.evaluate_acceptance(root, descendants)
def review_proven_terminal_child(
self,
*,
child_task_id: str,
claim_id: str,
result_digest: str,
evidence_refs: Sequence[str],
):
with self.store._transaction():
self.binding.record_terminal_proof(
task_id=child_task_id,
claim_id=claim_id,
result_digest=result_digest,
evidence_refs=evidence_refs,
)
return self.resume_terminal_review(child_task_id=child_task_id)
def resume_terminal_review(self, *, child_task_id: str) -> TerminalReviewOutcome:
"""Resume after restart without re-completing a claim or re-evaluating a review.
Once a trigger has a persisted review, that exact decision is frozen.
Policy/evaluator drift therefore cannot create a second decision for the
same terminal transition.
"""
with self.store._transaction():
child = self.binding.get_task_snapshot(child_task_id)
if not child.terminal_evidence_proven:
raise ConflictError("terminal review resume requires durable proof")
raw_lineage = child.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(raw_lineage, Mapping):
raise ConflictError("lineage child lacks authoritative lineage metadata")
try:
child_lineage = lineage_from_metadata(dict(raw_lineage))
except ValueError as exc:
raise ConflictError("lineage child metadata is invalid") from exc
persisted = self.binding.get_root_review_for_trigger(child_task_id)
if persisted is not None:
review_id = persisted.get("review_id")
if not isinstance(review_id, str) or not review_id:
raise ConflictError("persisted root review identity is invalid")
try:
review = root_review_from_record(
persisted, expected_review_id=review_id
)
except ValueError as exc:
raise ConflictError("persisted root review is invalid") from exc
root = self.binding.get_task_snapshot(child_lineage.root_task_id)
if (
review.trigger_child_task_id != child_task_id
or review.root_task_id != child_lineage.root_task_id
or child_lineage.task_id != child_task_id
or child_lineage.owner_principal_id != child.owner_principal_id
or child_lineage.mission_id != child.mission_id
or root.owner_principal_id != child.owner_principal_id
or root.mission_id != child.mission_id
or root.revision < review.root_revision_after
):
raise ConflictError("persisted root review authority mismatch")
dispatch = self.binding.get_root_action_dispatch(review.review_id)
if dispatch is None:
dispatch = self.binding.dispatch_root_action(
review,
f"root-review:{review.review_id}:{review.next_legal_action.value}",
)
expected_key = (
f"root-review:{review.review_id}:{review.next_legal_action.value}"
)
if (
dispatch.review_id != review.review_id
or dispatch.root_task_id != review.root_task_id
or dispatch.action != review.next_legal_action
or dispatch.idempotency_key != expected_key
or dispatch.status not in PERSISTED_DISPATCH_STATUSES
):
raise ConflictError("persisted root action dispatch is invalid")
return TerminalReviewOutcome(review=review, dispatch=dispatch)
hook_outcome = self.hooks.on_proven_terminal_child(
child_task_id=child_task_id,
context=AuthenticatedTaskContext(
principal_id=child.owner_principal_id,
preferred_alias=child_lineage.owner_alias,
mission_id=child.mission_id,
),
acceptance_evaluator=self.policy.evaluate_acceptance,
reviewed_at=datetime.now(tz=timezone.utc),
require_independent_readback=False,
)
return hook_outcome.outcome
@@ -1,990 +0,0 @@
"""Live AIOS protocol adapters for governed-writer execution.
This module closes the gap between the generic governed-writer model and the
live field contracts observed on the V2R2C3 control plane:
- agent-task-api :18940
* POST /v1/agents/register
* GET /v1/tasks/{task_id}
* POST /v1/tasks/{task_id}/claim -> lease_token
* POST /v1/tasks/{task_id}/heartbeat
- task-principal-issuer :18946
* POST /v1/task-principal/assertions
* Authorization: TaskLease <lease-token>
* one assertion per concrete operation + bridge_request
- bridge :18892
* GET /v2/runtime
* POST /v2/writer-lease
* POST /v2/commit-files
* Authorization: TaskPrincipal <operation-assertion>
WriterLease and commit-files share one continuation idempotency key.
The module never prints or returns the agent token, lease token or TaskPrincipal
assertion. Secret values are kept only in an in-memory handle store.
"""
from __future__ import annotations
import base64
import hashlib
import json
import os
import re
import secrets
import subprocess
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Mapping, Sequence
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
GovernedWriteReadiness,
GovernedWriterRequest,
OwnerAuthorizationPolicy,
)
class LiveProtocolError(RuntimeError):
pass
class LiveProtocolAuthError(LiveProtocolError):
pass
class LiveProtocolConflict(LiveProtocolError):
pass
class LiveProtocolUnavailable(LiveProtocolError):
pass
class IssuerResponseError(LiveProtocolError):
"""Safe, structured issuer failure metadata for isolated diagnostics."""
def __init__(self, message: str, *, status: int, error_code: str | None,
operation: str) -> None:
super().__init__(message)
self.status = status
self.error_code = error_code
self.operation = operation
@dataclass(frozen=True)
class HttpResult:
status: int
body: Mapping[str, Any]
class JsonHttpTransport:
"""Small stdlib JSON transport. Authorization values are never logged."""
def request(
self,
method: str,
url: str,
*,
body: Mapping[str, Any] | None = None,
headers: Mapping[str, str] | None = None,
timeout: float = 20.0,
) -> HttpResult:
payload = None
req_headers = {"Accept": "application/json"}
if body is not None:
payload = json.dumps(
dict(body),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
req_headers["Content-Type"] = "application/json"
if headers:
req_headers.update({str(k): str(v) for k, v in headers.items()})
req = urllib.request.Request(
url,
data=payload,
headers=req_headers,
method=method.upper(),
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read()
status = int(resp.status)
except urllib.error.HTTPError as exc:
raw = exc.read()
status = int(exc.code)
except (OSError, urllib.error.URLError) as exc:
raise LiveProtocolUnavailable(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: transport unavailable"
) from exc
if not raw:
obj: Any = {}
else:
try:
obj = json.loads(raw.decode("utf-8"))
except Exception as exc:
raise LiveProtocolError(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: non-JSON response"
) from exc
if not isinstance(obj, Mapping):
raise LiveProtocolError(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: response must be object"
)
return HttpResult(status=status, body=dict(obj))
@dataclass(frozen=True)
class LiveProtocolConfig:
bridge_url: str = "http://127.0.0.1:18892"
agent_task_url: str = "http://127.0.0.1:18940"
principal_issuer_url: str = "http://127.0.0.1:18946"
control_token_file: str = (
"/run/credentials/aios-agent-task-api.service/control_token"
)
authority_git_dir: str = "/data/aios-gitops/repo.git"
issue_schema: str = "aios.task-principal-issue.v1"
agent_type: str = "aios.p0-task-admission-worker"
agent_version: str = "governed-writer-live-v1"
lease_seconds: int = 900
assertion_ttl_seconds: int = 60
minimum_remaining_window_seconds: int = 240
def normalized(self) -> "LiveProtocolConfig":
return LiveProtocolConfig(
bridge_url=self.bridge_url.rstrip("/"),
agent_task_url=self.agent_task_url.rstrip("/"),
principal_issuer_url=self.principal_issuer_url.rstrip("/"),
control_token_file=self.control_token_file,
authority_git_dir=self.authority_git_dir,
issue_schema=self.issue_schema,
agent_type=self.agent_type,
agent_version=self.agent_version,
lease_seconds=int(self.lease_seconds),
assertion_ttl_seconds=int(self.assertion_ttl_seconds),
minimum_remaining_window_seconds=int(
self.minimum_remaining_window_seconds
),
)
class SecretHandleStore:
"""Process-local secret container.
Public receipts contain handles only; secret values are not serialized.
"""
def __init__(self) -> None:
self._values: dict[str, str] = {}
def put(self, kind: str, value: str) -> str:
if not isinstance(value, str) or not value:
raise LiveProtocolAuthError(f"{kind} secret missing")
handle = f"{kind}:{secrets.token_hex(12)}"
self._values[handle] = value
return handle
def get(self, handle: str) -> str:
try:
return self._values[handle]
except KeyError as exc:
raise LiveProtocolAuthError("secret handle not available") from exc
def drop(self, handle: str) -> None:
self._values.pop(handle, None)
@dataclass(frozen=True)
class LiveTask:
task_id: str
status: str
revision: int
deadline_epoch: float | None
raw: Mapping[str, Any]
@dataclass(frozen=True)
class LiveClaim:
task_id: str
claim_ref: str
server_claim_id: str | None
lease_handle: str
fencing_generation: int | None
lease_expires_at: float | None
task_revision: int | None
public_receipt: Mapping[str, Any] = field(default_factory=dict)
@dataclass(frozen=True)
class LiveWriterLease:
# /v2/writer-lease is a validated binding projection. The durable branch
# lease is created only by the continuation executor during commit.
writer_lease_id: str | None
task_id: str
job_id: str
branch_mode: str
expected_branch_oid: str
generation: int
expires_at: float | None
continuation_key: str
raw: Mapping[str, Any]
@dataclass(frozen=True)
class LiveWriterSession:
authorization_id: str
task_id: str
agent_id: str
claim_ref: str
job_id: str
branch_mode: str
expected_branch_oid: str
writer_lease_id: str | None
continuation_key: str
principal_issue_schema: str
agent_token_handle: str = field(repr=False)
lease_handle: str = field(repr=False)
task_lease_receipt: Mapping[str, Any] = field(default_factory=dict)
writer_lease_binding: Mapping[str, Any] = field(default_factory=dict)
def public_dict(self) -> dict[str, Any]:
public = {
"authorization_id": self.authorization_id,
"task_id": self.task_id,
"agent_id": self.agent_id,
"claim_ref": self.claim_ref,
"job_id": self.job_id,
"branch_mode": self.branch_mode,
"expected_branch_oid": self.expected_branch_oid,
"continuation_key": self.continuation_key,
"principal_issue_schema": self.principal_issue_schema,
"writer_lease_binding": dict(self.writer_lease_binding),
}
return public
def _parse_epoch(value: Any) -> float | None:
if value is None:
return None
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str):
text = value.strip()
if not text:
return None
try:
return float(text)
except ValueError:
pass
# ISO-8601 minimal support.
import datetime as dt
try:
return dt.datetime.fromisoformat(text.replace("Z", "+00:00")).timestamp()
except Exception:
return None
return None
def _safe_ref_from_token(token: str, prefix: str) -> str:
return f"{prefix}:sha256:{hashlib.sha256(token.encode('utf-8')).hexdigest()[:24]}"
class LiveAgentTaskClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
secrets_store: SecretHandleStore | None = None,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.secrets = secrets_store or SecretHandleStore()
def _control_token(self) -> str:
path = Path(self.config.control_token_file)
try:
token = path.read_text(encoding="utf-8").strip()
except OSError as exc:
raise LiveProtocolAuthError(
"agent-task control token file unavailable"
) from exc
if len(token) < 8:
raise LiveProtocolAuthError("agent-task control token invalid")
return token
def _control_headers(self) -> dict[str, str]:
return {"Authorization": "Bearer " + self._control_token()}
def read_task(self, task_id: str) -> LiveTask:
quoted = urllib.parse.quote(task_id, safe="")
result = self.transport.request(
"GET",
f"{self.config.agent_task_url}/v1/tasks/{quoted}",
headers=self._control_headers(),
timeout=15,
)
if result.status == 404:
raise LiveProtocolConflict("task not found")
if result.status not in (200, 201):
raise LiveProtocolError(f"task read failed HTTP {result.status}")
raw = result.body
task_obj = raw.get("task") if isinstance(raw.get("task"), Mapping) else raw
status = str(task_obj.get("status") or "")
if not status:
raise LiveProtocolError("task readback missing status")
revision = int(task_obj.get("revision") or 0)
return LiveTask(
task_id=str(task_obj.get("task_id") or task_id),
status=status,
revision=revision,
deadline_epoch=_parse_epoch(task_obj.get("deadline")),
raw=dict(task_obj),
)
def register_agent(
self,
*,
task_id: str,
capabilities: Sequence[str],
route: str,
agent_id: str | None = None,
) -> tuple[str, str]:
if agent_id is None:
agent_id = (
"governed-writer-"
+ hashlib.sha256(
(task_id + "\0" + str(time.time_ns())).encode("utf-8")
).hexdigest()[:20]
)
body = {
"agent_id": agent_id,
"agent_type": self.config.agent_type,
"agent_version": self.config.agent_version,
"capabilities": list(capabilities),
"metadata": {"task_id": task_id, "route": route},
}
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/agents/register",
body=body,
headers=self._control_headers(),
timeout=15,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"agent register failed HTTP {result.status}")
token = result.body.get("agent_token")
if not isinstance(token, str) or len(token) < 8:
raise LiveProtocolAuthError("agent register returned no usable token")
return agent_id, self.secrets.put("agent-token", token)
def claim(
self,
*,
task_id: str,
agent_token_handle: str,
lease_seconds: int | None = None,
) -> LiveClaim:
lease_seconds = int(lease_seconds or self.config.lease_seconds)
quoted = urllib.parse.quote(task_id, safe="")
token = self.secrets.get(agent_token_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/claim",
body={"lease_seconds": lease_seconds},
headers={"Authorization": "Bearer " + token},
timeout=15,
)
if result.status == 409:
raise LiveProtocolConflict(
str(result.body.get("error") or "task not claimable")
)
if result.status not in (200, 201):
raise LiveProtocolError(f"claim failed HTTP {result.status}")
if result.body.get("status") != "CLAIMED":
raise LiveProtocolError("claim response not CLAIMED")
lease_token = result.body.get("lease_token")
if not isinstance(lease_token, str) or len(lease_token) < 8:
raise LiveProtocolAuthError("claim returned no lease token")
server_claim_id = result.body.get("claim_id")
lease_id = result.body.get("lease_id")
public_ref = None
for candidate in (server_claim_id, lease_id):
if isinstance(candidate, str) and candidate:
public_ref = candidate
break
if public_ref is None:
public_ref = _safe_ref_from_token(lease_token, "claim")
lease_handle = self.secrets.put("task-lease", lease_token)
fencing = result.body.get("fencing_generation")
return LiveClaim(
task_id=task_id,
claim_ref=public_ref,
server_claim_id=(
str(server_claim_id)
if isinstance(server_claim_id, str) and server_claim_id
else None
),
lease_handle=lease_handle,
fencing_generation=int(fencing) if fencing is not None else None,
lease_expires_at=_parse_epoch(result.body.get("lease_expires_at")),
task_revision=(
int(result.body["receipt_revision"])
if result.body.get("receipt_revision") is not None
else None
),
public_receipt={k: result.body[k] for k in (
"task_id", "status", "claim_id", "lease_id", "fencing_generation",
"receipt_revision", "lease_expires_at", "attempt", "revision",
) if k in result.body},
)
def heartbeat(
self,
*,
claim: LiveClaim,
agent_token_handle: str,
phase: str,
lease_seconds: int | None = None,
) -> LiveTask:
lease_seconds = int(lease_seconds or self.config.lease_seconds)
quoted = urllib.parse.quote(claim.task_id, safe="")
agent_token = self.secrets.get(agent_token_handle)
lease_token = self.secrets.get(claim.lease_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/heartbeat",
body={
"lease_token": lease_token,
"lease_seconds": lease_seconds,
"progress": {"phase": phase, "status": "running"},
},
headers={"Authorization": "Bearer " + agent_token},
timeout=15,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"heartbeat failed HTTP {result.status}")
if result.body.get("status") not in ("RUNNING", "CLAIMED"):
raise LiveProtocolError("heartbeat did not yield RUNNING/CLAIMED")
task = self.read_task(claim.task_id)
if task.status not in ("RUNNING", "CLAIMED"):
raise LiveProtocolConflict(
f"authoritative task state after heartbeat is {task.status}"
)
return task
def submit_result(
self,
*,
task_id: str,
agent_id: str,
agent_token_handle: str,
lease_handle: str,
idempotency_key: str,
result_value: Mapping[str, Any],
) -> Mapping[str, Any]:
if not isinstance(idempotency_key, str) or not 1 <= len(idempotency_key) <= 160:
raise LiveProtocolError("task result idempotency key invalid")
if not isinstance(result_value, Mapping):
raise LiveProtocolError("task result mapping required")
quoted = urllib.parse.quote(task_id, safe="")
agent_token = self.secrets.get(agent_token_handle)
lease_token = self.secrets.get(lease_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/result",
body={
"lease_token": lease_token,
"idempotency_key": idempotency_key,
"result": dict(result_value),
},
headers={"Authorization": "Bearer " + agent_token},
timeout=15,
)
if result.status == 409:
raise LiveProtocolConflict(
str(result.body.get("error") or "task result conflict")
)
if result.status not in (200, 201):
raise LiveProtocolError(f"task result HTTP {result.status}")
if (
result.body.get("task_id") != task_id
or result.body.get("status") != "VERIFYING"
or result.body.get("claimed_by") != agent_id
or result.body.get("result") != dict(result_value)
):
raise LiveProtocolConflict("task result readback mismatch")
return dict(result.body)
class LiveTaskPrincipalClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
secrets_store: SecretHandleStore,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.secrets = secrets_store
self.public_receipts: list[dict[str, Any]] = []
def issue_assertion(
self,
*,
task_id: str,
lease_handle: str,
operation: str,
bridge_request: Mapping[str, Any],
request_id: str,
) -> str:
lease_token = self.secrets.get(lease_handle)
body = {
"schema": self.config.issue_schema,
"request_id": request_id,
"task_id": task_id,
"operation": operation,
"bridge_request": dict(bridge_request),
"requested_ttl_seconds": self.config.assertion_ttl_seconds,
}
last: HttpResult | None = None
for attempt in range(2):
result = self.transport.request(
"POST",
f"{self.config.principal_issuer_url}/v1/task-principal/assertions",
body=body,
headers={"Authorization": "TaskLease " + lease_token},
timeout=20,
)
last = result
assertion = result.body.get("assertion")
if result.status in (200, 201) and isinstance(assertion, str) and assertion:
self.public_receipts.append({
"task_id": task_id, "operation": operation, "request_id": request_id,
"assertion_sha256": hashlib.sha256(assertion.encode()).hexdigest(),
"issuer_response": {k: result.body[k] for k in ("jti", "expires_at", "replayed") if k in result.body},
})
return assertion
if not (
attempt == 0
and result.status == 503
and result.body.get("error") == "TASK_PRINCIPAL_ISSUER_UNAVAILABLE"
):
break
time.sleep(1)
status = 0 if last is None else last.status
raw_code = None if last is None else last.body.get("error")
error_code = (
raw_code if isinstance(raw_code, str)
and re.fullmatch(r"(?:TASK_PRINCIPAL|EXECUTION_PLAN|GOVERNED)_[A-Z0-9_]{3,120}", raw_code) else None
)
raise IssuerResponseError(
f"TaskPrincipal assertion issue failed operation={operation} HTTP {status}",
status=status, error_code=error_code, operation=operation,
)
class LiveGitReader:
def __init__(self, git_dir: str) -> None:
self.git_dir = str(git_dir)
def branch_head(self, branch: str) -> str | None:
proc = subprocess.run(
["git", "--git-dir", self.git_dir, "rev-parse", f"refs/heads/{branch}"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
timeout=15,
)
if proc.returncode != 0:
return None
head = proc.stdout.strip()
if len(head) != 40 or any(c not in "0123456789abcdef" for c in head):
raise LiveProtocolError("git branch HEAD is not a full OID")
return head
def file_sha256(self, oid: str, path: str) -> str:
proc = subprocess.run(
["git", "--git-dir", self.git_dir, "show", f"{oid}:{path}"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=20,
)
if proc.returncode != 0:
raise LiveProtocolError(f"git readback failed for {path}")
return hashlib.sha256(proc.stdout).hexdigest()
class LiveBridgeClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
principal_client: LiveTaskPrincipalClient,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.principal_client = principal_client
def runtime_status(self) -> Mapping[str, Any]:
result = self.transport.request(
"GET", f"{self.config.bridge_url}/v2/runtime", timeout=10
)
if result.status != 200:
raise LiveProtocolUnavailable(f"bridge runtime HTTP {result.status}")
return result.body
def acquire_writer_lease(
self,
*,
task_id: str,
job_id: str,
lease_handle: str,
repository: str,
target_branch: str,
generation: int,
base_oid: str,
expected_branch_oid: str,
branch_mode: str,
continuation_key: str,
) -> LiveWriterLease:
req = {
"schema": "aios.task-writer-lease.v2",
"task_id": task_id,
"job_id": job_id,
"idempotency_key": continuation_key,
"repository": repository,
"branch_mode": branch_mode,
"base_oid": base_oid,
"expected_branch_oid": expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"lease_seconds": int(self.config.lease_seconds),
}
assertion = self.principal_client.issue_assertion(
task_id=task_id,
lease_handle=lease_handle,
operation="acquireWriterLease",
bridge_request=req,
request_id="writer-lease-" + secrets.token_hex(8),
)
result = self.transport.request(
"POST",
f"{self.config.bridge_url}/v2/writer-lease",
body=req,
headers={"Authorization": "TaskPrincipal " + assertion},
timeout=30,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"writer lease HTTP {result.status}")
body = result.body
expected_binding = {
"schema": "aios.task-writer-lease.v2",
"task_id": task_id,
"repository": repository,
"base_oid": base_oid,
"expected_branch_oid": expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"lease_seconds": int(self.config.lease_seconds),
}
for field, expected in expected_binding.items():
if body.get(field) != expected:
raise LiveProtocolConflict(f"writer lease {field} mismatch")
projected_branch_mode = body.get("branch_mode")
allowed_branch_modes = {branch_mode}
# CREATE_NEW is materialized by the Bridge before it returns the
# descriptor, so the returned binding is CONTINUE_EXISTING.
if branch_mode == "CREATE_NEW":
allowed_branch_modes.add("CONTINUE_EXISTING")
if projected_branch_mode not in allowed_branch_modes:
raise LiveProtocolConflict("writer lease branch_mode mismatch")
return LiveWriterLease(
writer_lease_id=None,
task_id=task_id,
job_id=job_id,
branch_mode=str(projected_branch_mode),
expected_branch_oid=expected_branch_oid,
generation=int(generation),
expires_at=None,
continuation_key=continuation_key,
raw=dict(body),
)
def commit_files(
self,
*,
session: LiveWriterSession,
repository: str,
target_branch: str,
generation: int,
files: Mapping[str, bytes],
message: str,
) -> Mapping[str, Any]:
entries = [
{
"path": path,
"content_base64": base64.b64encode(data).decode("ascii"),
}
for path, data in files.items()
]
req = {
"task_id": session.task_id,
"job_id": session.job_id,
"repository": repository,
"expected_branch_oid": session.expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"idempotency_key": session.continuation_key,
"files": entries,
"message": message,
}
assertion = self.principal_client.issue_assertion(
task_id=session.task_id,
lease_handle=session.lease_handle,
operation="commitFilesGoverned",
bridge_request=req,
request_id="commit-files-" + secrets.token_hex(8),
)
result = self.transport.request(
"POST",
f"{self.config.bridge_url}/v2/commit-files",
body=req,
headers={"Authorization": "TaskPrincipal " + assertion},
timeout=45,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"commit-files HTTP {result.status}")
return dict(result.body)
class LiveGovernedWriterExecutor:
"""Execute the real post-admission writer chain.
Prerequisites:
- Owner authorization was already verified by the caller/authority.
- A fresh admission task exists and is QUEUED.
- The task's execution plan already binds the supplied job_id.
- A valid delegation epoch/admission window is already active.
This executor deliberately does not create/rewrite admission plans or sign
Owner epochs. Those remain in the existing admission/signer authority.
"""
def __init__(
self,
*,
config: LiveProtocolConfig,
owner_policy: OwnerAuthorizationPolicy,
authorization_ref: str,
transport: JsonHttpTransport | None = None,
agent_id: str | None = None,
scope_enrollment: Callable[..., Mapping[str, Any]] | None = None,
) -> None:
self.config = config.normalized()
self.owner_policy = owner_policy
self.authorization_ref = authorization_ref
if scope_enrollment is not None and (not callable(scope_enrollment) or not agent_id):
raise ValueError("scope enrollment requires a configured agent identity")
self.agent_id = agent_id
self.scope_enrollment = scope_enrollment
self.transport = transport or JsonHttpTransport()
self.secret_store = SecretHandleStore()
self.agent = LiveAgentTaskClient(
self.config,
transport=self.transport,
secrets_store=self.secret_store,
)
self.principals = LiveTaskPrincipalClient(
self.config,
transport=self.transport,
secrets_store=self.secret_store,
)
self.bridge = LiveBridgeClient(
self.config,
transport=self.transport,
principal_client=self.principals,
)
self.git = LiveGitReader(self.config.authority_git_dir)
def _assert_owner_scope(self, request: GovernedWriterRequest) -> None:
if self.authorization_ref != self.owner_policy.authorization_id:
raise AuthorizationDenied("authorization_ref does not match verified policy")
self.owner_policy.validate(request, now=time.time())
def acquire(
self,
*,
request: GovernedWriterRequest,
job_id: str,
route: str = "owner-authorized-governed-writer",
) -> LiveWriterSession:
self._assert_owner_scope(request)
runtime = self.bridge.runtime_status()
readiness = GovernedWriteReadiness.from_runtime_status(runtime)
if not readiness.governed_write_path_open:
raise LiveProtocolConflict(
"governed write path blocked: " + ",".join(readiness.blockers)
)
if readiness.legacy_global_writes_enabled:
raise LiveProtocolConflict("legacy/global writes must remain disabled")
task = self.agent.read_task(request.task_id)
if task.status != "QUEUED":
raise LiveProtocolConflict(
f"task must be QUEUED before claim; live={task.status}"
)
now = time.time()
if (
task.deadline_epoch is not None
and task.deadline_epoch - now
< self.config.minimum_remaining_window_seconds
):
raise LiveProtocolConflict("task deadline too close to safely claim")
agent_id, agent_token_handle = self.agent.register_agent(
task_id=request.task_id,
capabilities=sorted(request.required_capabilities),
route=route,
agent_id=self.agent_id,
)
claim = self.agent.claim(
task_id=request.task_id,
agent_token_handle=agent_token_handle,
)
running = self.agent.heartbeat(
claim=claim,
agent_token_handle=agent_token_handle,
phase=route,
)
if (
running.deadline_epoch is not None
and running.deadline_epoch - time.time()
< self.config.minimum_remaining_window_seconds
):
raise LiveProtocolConflict(
"remaining governed task window too short after heartbeat"
)
if self.scope_enrollment is not None:
# The existing issuer and Bridge still verify every assertion.
# Enrollment only happens after a real claim has established the
# agent/attempt/lease binding; failures stop before any assertion.
enrollment = self.scope_enrollment(
request=request, job_id=job_id, agent_id=agent_id,
claim=claim, task=running,
)
if (not isinstance(enrollment, Mapping)
or enrollment.get("status") != "ENROLLED"
or enrollment.get("task_id") != request.task_id
or enrollment.get("agent_id") != agent_id
or enrollment.get("job_id") != job_id):
raise LiveProtocolConflict("verified principal scope enrollment required")
live_head = self.git.branch_head(request.target_branch)
if live_head is None:
branch_mode = "CREATE_NEW"
expected_branch_oid = request.base_oid
else:
branch_mode = "CONTINUE_EXISTING"
expected_branch_oid = live_head
continuation_key = (
"gw-cont-"
+ hashlib.sha256(
(request.task_id + "\0" + job_id).encode("utf-8")
).hexdigest()[:32]
)
writer = self.bridge.acquire_writer_lease(
task_id=request.task_id,
job_id=job_id,
lease_handle=claim.lease_handle,
repository=request.repository,
target_branch=request.target_branch,
generation=request.generation,
base_oid=request.base_oid,
expected_branch_oid=expected_branch_oid,
branch_mode=branch_mode,
continuation_key=continuation_key,
)
return LiveWriterSession(
authorization_id=self.authorization_ref,
task_id=request.task_id,
agent_id=agent_id,
claim_ref=claim.claim_ref,
job_id=job_id,
branch_mode=writer.branch_mode,
expected_branch_oid=writer.expected_branch_oid,
writer_lease_id=writer.writer_lease_id,
continuation_key=writer.continuation_key,
principal_issue_schema=self.config.issue_schema,
agent_token_handle=agent_token_handle,
lease_handle=claim.lease_handle,
task_lease_receipt=dict(claim.public_receipt),
writer_lease_binding=dict(writer.raw),
)
def commit_and_readback(
self,
*,
session: LiveWriterSession,
request: GovernedWriterRequest,
files: Mapping[str, bytes],
expected_sha256: Mapping[str, str],
message: str,
) -> Mapping[str, Any]:
self._assert_owner_scope(request)
if set(files) != set(expected_sha256):
raise LiveProtocolConflict("commit file set differs from expected SHA set")
for path, data in files.items():
got = hashlib.sha256(data).hexdigest()
if got != expected_sha256[path]:
raise LiveProtocolConflict(f"pre-commit SHA mismatch: {path}")
response = self.bridge.commit_files(
session=session,
repository=request.repository,
target_branch=request.target_branch,
generation=request.generation,
files=files,
message=message,
)
new_head = self.git.branch_head(request.target_branch)
if new_head is None or new_head == session.expected_branch_oid:
raise LiveProtocolConflict("branch did not advance after governed commit")
readback = {
path: self.git.file_sha256(new_head, path) for path in expected_sha256
}
if readback != dict(expected_sha256):
raise LiveProtocolConflict("governed commit readback SHA mismatch")
return {
"commit_response": response,
"branch_head": new_head,
"readback_sha256": readback,
}
def submit_task_result(
self,
*,
session: LiveWriterSession,
result: Mapping[str, Any],
idempotency_key: str,
) -> Mapping[str, Any]:
return self.agent.submit_result(
task_id=session.task_id,
agent_id=session.agent_id,
agent_token_handle=session.agent_token_handle,
lease_handle=session.lease_handle,
idempotency_key=idempotency_key,
result_value=result,
)
File diff suppressed because it is too large Load Diff
+7 -106
View File
@@ -4,7 +4,6 @@ from __future__ import annotations
import hashlib
import json
import math
import re
import time
import uuid
@@ -75,13 +74,7 @@ def _now_ts() -> float:
def canonical_json(value: Any) -> str:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def compute_sha256_hex(value: Any) -> str:
@@ -395,8 +388,6 @@ class TaskRecord:
root_task_id: str
idempotency_key: str | None = None
failure_reason: str | None = None
owner_principal_id: str = ""
mission_id: str = ""
def with_status(self, status: str) -> "TaskRecord":
allowed = TASK_NEXT_STATES.get(self.status, set())
@@ -416,8 +407,6 @@ class TaskRecord:
root_task_id=self.root_task_id,
idempotency_key=self.idempotency_key,
failure_reason=self.failure_reason,
owner_principal_id=self.owner_principal_id,
mission_id=self.mission_id,
)
def to_dict(self, *, include_task=True, redact_payload: bool = True) -> dict[str, Any]:
@@ -433,9 +422,6 @@ class TaskRecord:
}
if include_task:
payload["plan"] = self.task_plan.to_dict(redact=redact_payload)
display_name = self.task_plan.metadata.get("aios_task_display_name")
if self.owner_principal_id and self.mission_id and isinstance(display_name, str):
payload["display_name"] = display_name
if self.idempotency_key:
payload["idempotency_key"] = self.idempotency_key
return payload
@@ -539,16 +525,10 @@ class EvidenceEnvelope:
worker_identity: str
payload: dict[str, Any]
created_at: float
expires_at: float
previous_evidence_hash: str
evidence_digest: str
signature: str | None = None
attestation_ref: str | None = None
signature_algorithm: str | None = None
signing_key_id: str | None = None
verifier_identity: str | None = None
authority_domain: str | None = None
sequence: int | None = None
@classmethod
def create(
@@ -564,17 +544,7 @@ class EvidenceEnvelope:
attestation_ref: str | None = None,
evidence_id: str | None = None,
created_at: float | None = None,
expires_at: float | None = None,
signature_algorithm: str | None = None,
signing_key_id: str | None = None,
verifier_identity: str | None = None,
authority_domain: str | None = None,
sequence: int | None = None,
) -> "EvidenceEnvelope":
issued_at = float(_now_ts() if created_at is None else created_at)
valid_until = float(issued_at + 300.0 if expires_at is None else expires_at)
if not math.isfinite(issued_at) or not math.isfinite(valid_until):
raise ValueError("evidence timestamps must be finite")
envelope = cls(
schema_version=1,
evidence_id=evidence_id or f"evd-{uuid.uuid4()}",
@@ -583,17 +553,11 @@ class EvidenceEnvelope:
kind=validate_type(kind, "evidence kind"),
worker_identity=str(worker_identity),
payload=redact_value(payload),
created_at=issued_at,
expires_at=valid_until,
created_at=float(created_at or _now_ts()),
previous_evidence_hash=str(previous_evidence_hash or ""),
evidence_digest="",
signature=signature,
attestation_ref=attestation_ref,
signature_algorithm=signature_algorithm,
signing_key_id=signing_key_id,
verifier_identity=verifier_identity,
authority_domain=authority_domain,
sequence=sequence,
)
return dataclass_replace(
envelope, evidence_digest=compute_sha256_hex(envelope.digest_payload())
@@ -609,14 +573,8 @@ class EvidenceEnvelope:
"worker_identity": self.worker_identity,
"payload": self.payload,
"created_at": self.created_at,
"expires_at": self.expires_at,
"previous_evidence_hash": self.previous_evidence_hash,
"attestation_ref": self.attestation_ref,
"signature_algorithm": self.signature_algorithm,
"signing_key_id": self.signing_key_id,
"verifier_identity": self.verifier_identity,
"authority_domain": self.authority_domain,
"sequence": self.sequence,
}
def verify(
@@ -626,40 +584,18 @@ class EvidenceEnvelope:
expected_claim_id: str,
expected_previous_hash: str,
attestation_verifier: Any = None,
now: float | None = None,
) -> bool:
try:
observed_at = float(_now_ts() if now is None else now)
finite_timestamps = all(
math.isfinite(value)
for value in (observed_at, self.created_at, self.expires_at)
)
expected_digest = compute_sha256_hex(self.digest_payload())
except (TypeError, ValueError, OverflowError):
return False
if (
not finite_timestamps
or self.schema_version != 1
self.schema_version != 1
or self.task_id != expected_task_id
or self.claim_id != expected_claim_id
or self.previous_evidence_hash != expected_previous_hash
or self.created_at > observed_at
or self.expires_at <= observed_at
or self.expires_at <= self.created_at
or self.evidence_digest != expected_digest
or self.evidence_digest != compute_sha256_hex(self.digest_payload())
):
return False
# An unsigned envelope has no proof of origin and must never verify.
if not (self.signature or self.attestation_ref):
return False
# Every proof form requires an injected verifier. A reference or
# signature must never become trusted merely because it is present.
if attestation_verifier is None:
return False
try:
return bool(attestation_verifier(self))
except Exception:
return False
if self.signature or self.attestation_ref:
return True if attestation_verifier is None else bool(attestation_verifier(self))
return True
def to_dict(self) -> dict[str, Any]:
return {
@@ -668,41 +604,6 @@ class EvidenceEnvelope:
"signature": self.signature,
}
@classmethod
def from_dict(cls, value: dict[str, Any]) -> "EvidenceEnvelope":
if not isinstance(value, dict):
raise ValueError("evidence envelope must be an object")
try:
envelope = cls(
schema_version=int(value["schema_version"]),
evidence_id=validate_identifier(value["evidence_id"], "evidence_id"),
task_id=validate_identifier(value["task_id"], "task_id"),
claim_id=validate_identifier(value["claim_id"], "claim_id"),
kind=validate_type(value["kind"], "evidence kind"),
worker_identity=str(value["worker_identity"]),
payload=redact_value(value["payload"]),
created_at=float(value["created_at"]),
expires_at=float(value["expires_at"]),
previous_evidence_hash=str(value["previous_evidence_hash"]),
evidence_digest=str(value["evidence_digest"]),
signature=value.get("signature"),
attestation_ref=value.get("attestation_ref"),
signature_algorithm=value.get("signature_algorithm"),
signing_key_id=value.get("signing_key_id"),
verifier_identity=value.get("verifier_identity"),
authority_domain=value.get("authority_domain"),
sequence=(
None if value.get("sequence") is None else int(value["sequence"])
),
)
if not math.isfinite(envelope.created_at) or not math.isfinite(
envelope.expires_at
):
raise ValueError("evidence timestamps must be finite")
return envelope
except (KeyError, TypeError, ValueError) as exc:
raise ValueError("invalid evidence envelope") from exc
class ProbeOutcome(StrEnum):
NOT_APPLIED = "NOT_APPLIED"
@@ -1,480 +0,0 @@
"""Strict adapters from existing authority readers into C6 observations.
Readers are injected by the trusted composition root. They are query-only:
this module has no controller/reconcile, signer, store, or network factory.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from datetime import datetime
from typing import Any
from .operation_view import (
AcceptanceObservation,
ClosureObservation,
DomainStamp,
EffectObservation,
Fact,
OperationBinding,
OperationObservation,
VerificationObservation,
binding_digest,
)
Reader = Callable[[OperationBinding], Mapping[str, object]]
_STAMP_FIELDS = frozenset({"domain", "object_id", "generation", "revision", "event_id"})
_FACT_FIELDS = frozenset(
{"value", "stamp", "binding_digest", "observed_at", "expires_at", "evidence_refs"}
)
_DECLARATION_FIELDS = frozenset({"schema", "declared", "installed", "callable"})
_TASK_FIELDS = frozenset({"schema", "preconditions"})
_AUTHORIZATION_FIELDS = frozenset({"schema", "authorized"})
_EFFECT_FIELDS = frozenset(
{
"binding_digest",
"outcome",
"receipt_ref",
"result_artifact_digest",
"actor_id",
"stamp",
"observed_at",
"expires_at",
"trust_status",
}
)
_GITOPS_FIELDS = frozenset({"schema", "effect"})
_VERIFICATION_FIELDS = frozenset(
{
"binding_digest",
"receipt_ref",
"result_artifact_digest",
"verifier_id",
"outcome",
"authenticated",
"observed_at",
"expires_at",
"verification_receipt_ref",
}
)
_VERIFIER_FIELDS = frozenset({"schema", "verification"})
_ACCEPTANCE_OBSERVATION_FIELDS = frozenset(
{
"binding_digest",
"receipt_ref",
"result_artifact_digest",
"verifier_receipt_ref",
"acceptance_revision",
"acceptor_id",
"outcome",
"authenticated",
"observed_at",
"expires_at",
}
)
_ACCEPTANCE_FIELDS = frozenset(
{
"schema",
"acceptance",
"expected_acceptor_id",
"expected_acceptance_revision",
}
)
@dataclass(frozen=True)
class OperationReaders:
declaration: Reader | None
task: Reader | None
authorization: Reader | None
gitops: Reader | None
verifier: Reader | None
acceptance: Reader | None
def __post_init__(self) -> None:
for name in (
"declaration",
"task",
"authorization",
"gitops",
"verifier",
"acceptance",
):
value = getattr(self, name)
if value is not None and not callable(value):
raise ValueError(f"{name} reader must be callable or None")
def _exact_mapping(value: object, fields: frozenset[str], name: str) -> Mapping[str, object]:
if not isinstance(value, Mapping) or set(value) != fields:
raise ValueError(f"{name} has unknown or missing fields")
if any(not isinstance(key, str) for key in value):
raise ValueError(f"{name} keys must be strings")
return value
def _text(value: object, name: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{name} must be a canonical non-empty string")
return value
def _optional_text(value: object, name: str) -> str | None:
if value is None:
return None
return _text(value, name)
def _datetime(value: object, name: str) -> datetime | None:
if value is None:
return None
if isinstance(value, str):
text = value[:-1] + "+00:00" if value.endswith("Z") else value
try:
value = datetime.fromisoformat(text)
except ValueError as exc:
raise ValueError(f"{name} is not an RFC3339 datetime") from exc
if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
raise ValueError(f"{name} must be timezone-aware")
return value
def _tuple_of_text(value: object, name: str) -> tuple[str, ...]:
if not isinstance(value, (tuple, list)) or isinstance(value, (str, bytes)):
raise ValueError(f"{name} must be an array")
return tuple(_text(item, name) for item in value)
def _stamp(value: object) -> DomainStamp | None:
if value is None:
return None
raw = _exact_mapping(value, _STAMP_FIELDS, "stamp")
return DomainStamp(
domain=_text(raw["domain"], "domain"),
object_id=_text(raw["object_id"], "object_id"),
generation=raw["generation"], # type: ignore[arg-type]
revision=raw["revision"], # type: ignore[arg-type]
event_id=_text(raw["event_id"], "event_id"),
)
def _fact(value: object) -> Fact:
raw = _exact_mapping(value, _FACT_FIELDS, "fact")
return Fact(
value=raw["value"], # type: ignore[arg-type]
stamp=_stamp(raw["stamp"]),
binding_digest=_text(raw["binding_digest"], "binding_digest"),
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
evidence_refs=_tuple_of_text(raw["evidence_refs"], "evidence_refs"),
)
def _unknown_fact(binding: OperationBinding, code: str) -> Fact:
return Fact(
value="UNKNOWN",
stamp=None,
binding_digest=binding_digest(binding),
observed_at=None,
expires_at=None,
evidence_refs=(f"operation-readback:{code}",),
)
def _fact_candidates(value: object) -> tuple[Fact, ...]:
if isinstance(value, Mapping):
return (_fact(value),)
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
if not value:
raise ValueError("fact candidate list is empty")
return tuple(_fact(item) for item in value)
raise ValueError("fact must be an object or non-empty candidate list")
def _select_fact(
value: object, binding: OperationBinding, *, expected_domain: str
) -> Fact:
candidates = _fact_candidates(value)
digest = binding_digest(binding)
if any(item.binding_digest != digest for item in candidates):
return _unknown_fact(binding, "BINDING_MISMATCH")
if any(item.stamp is None for item in candidates):
if len(candidates) == 1:
return candidates[0]
return _unknown_fact(binding, "INVALID_CANDIDATE_SET")
stamps = [item.stamp for item in candidates]
assert all(stamp is not None for stamp in stamps)
domains = {(stamp.domain, stamp.object_id) for stamp in stamps if stamp is not None}
if len(domains) != 1 or next(iter(domains))[0] != expected_domain:
return _unknown_fact(binding, "CROSS_DOMAIN_CANDIDATES")
highest = max((stamp.generation, stamp.revision) for stamp in stamps if stamp is not None)
current = [
item
for item in candidates
if item.stamp is not None
and (item.stamp.generation, item.stamp.revision) == highest
]
if len(current) > 1:
first = current[0]
# Event identity may differ for an idempotent duplicate. Every semantic
# field must still agree at the same authority revision.
semantic = (
first.value,
first.binding_digest,
first.observed_at,
first.expires_at,
first.evidence_refs,
)
if any(
(
item.value,
item.binding_digest,
item.observed_at,
item.expires_at,
item.evidence_refs,
)
!= semantic
for item in current[1:]
):
return _unknown_fact(binding, "SAME_REVISION_CONFLICT")
return current[0]
def _read(reader: Reader | None, binding: OperationBinding) -> Mapping[str, object] | None:
if reader is None:
return None
try:
value = reader(binding)
except Exception:
return None
return value if isinstance(value, Mapping) else None
def collect_observation(
binding: OperationBinding,
readers: OperationReaders,
*,
now: datetime,
) -> OperationObservation:
"""Query three authority seams and build a typed preflight observation."""
_datetime(now, "now")
declared = installed = callable_fact = _unknown_fact(binding, "DECLARATION_UNAVAILABLE")
raw = _read(readers.declaration, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _DECLARATION_FIELDS, "declaration reader result")
if doc["schema"] != "aios.operation-declaration-observation.v1":
raise ValueError("unsupported declaration schema")
declared = _select_fact(
doc["declared"], binding, expected_domain="declaration"
)
installed = _select_fact(
doc["installed"], binding, expected_domain="runtime"
)
callable_fact = _select_fact(
doc["callable"], binding, expected_domain="runtime"
)
except (TypeError, ValueError):
declared = installed = callable_fact = _unknown_fact(
binding, "INVALID_DECLARATION_OBSERVATION"
)
preconditions = _unknown_fact(binding, "TASK_ADAPTER_UNAVAILABLE")
raw = _read(readers.task, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _TASK_FIELDS, "task reader result")
if doc["schema"] != "aios.operation-task-observation.v1":
raise ValueError("unsupported task schema")
preconditions = _select_fact(
doc["preconditions"], binding, expected_domain="task"
)
except (TypeError, ValueError):
preconditions = _unknown_fact(binding, "INVALID_TASK_OBSERVATION")
authorized = _unknown_fact(binding, "AUTHORIZATION_ADAPTER_UNAVAILABLE")
raw = _read(readers.authorization, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _AUTHORIZATION_FIELDS, "authorization reader result")
if doc["schema"] != "aios.operation-authorization-observation.v1":
raise ValueError("unsupported authorization schema")
authorized = _select_fact(
doc["authorized"], binding, expected_domain="authorization"
)
except (TypeError, ValueError):
authorized = _unknown_fact(binding, "INVALID_AUTHORIZATION_OBSERVATION")
return OperationObservation(
binding=binding,
declared=declared,
installed=installed,
callable=callable_fact,
authorized=authorized,
preconditions=preconditions,
)
def _unknown_effect(binding: OperationBinding) -> EffectObservation:
return EffectObservation(
binding_digest=binding_digest(binding),
outcome="UNKNOWN",
receipt_ref=None,
result_artifact_digest=None,
actor_id=None,
stamp=None,
observed_at=None,
expires_at=None,
trust_status="UNTRUSTED",
)
def _effect(value: object) -> EffectObservation:
raw = _exact_mapping(value, _EFFECT_FIELDS, "effect")
return EffectObservation(
binding_digest=_text(raw["binding_digest"], "binding_digest"),
outcome=raw["outcome"], # type: ignore[arg-type]
receipt_ref=_optional_text(raw["receipt_ref"], "receipt_ref"),
result_artifact_digest=_optional_text(
raw["result_artifact_digest"], "result_artifact_digest"
),
actor_id=_optional_text(raw["actor_id"], "actor_id"),
stamp=_stamp(raw["stamp"]),
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
trust_status=raw["trust_status"], # type: ignore[arg-type]
)
def _unknown_verification(binding: OperationBinding) -> VerificationObservation:
return VerificationObservation(
binding_digest=binding_digest(binding),
receipt_ref=None,
result_artifact_digest=None,
verifier_id=None,
outcome="UNAVAILABLE",
authenticated=False,
observed_at=None,
expires_at=None,
verification_receipt_ref=None,
)
def _verification(value: object) -> VerificationObservation:
raw = _exact_mapping(value, _VERIFICATION_FIELDS, "verification")
return VerificationObservation(
binding_digest=_text(raw["binding_digest"], "binding_digest"),
receipt_ref=_optional_text(raw["receipt_ref"], "receipt_ref"),
result_artifact_digest=_optional_text(
raw["result_artifact_digest"], "result_artifact_digest"
),
verifier_id=_optional_text(raw["verifier_id"], "verifier_id"),
outcome=raw["outcome"], # type: ignore[arg-type]
authenticated=raw["authenticated"], # type: ignore[arg-type]
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
verification_receipt_ref=_optional_text(
raw["verification_receipt_ref"], "verification_receipt_ref"
),
)
def _unknown_acceptance(binding: OperationBinding) -> AcceptanceObservation:
return AcceptanceObservation(
binding_digest=binding_digest(binding),
receipt_ref=None,
result_artifact_digest=None,
verifier_receipt_ref=None,
acceptance_revision=None,
acceptor_id=None,
outcome="UNKNOWN",
authenticated=False,
observed_at=None,
expires_at=None,
)
def _acceptance(value: object) -> AcceptanceObservation:
raw = _exact_mapping(value, _ACCEPTANCE_OBSERVATION_FIELDS, "acceptance")
return AcceptanceObservation(
binding_digest=_text(raw["binding_digest"], "binding_digest"),
receipt_ref=_optional_text(raw["receipt_ref"], "receipt_ref"),
result_artifact_digest=_optional_text(
raw["result_artifact_digest"], "result_artifact_digest"
),
verifier_receipt_ref=_optional_text(
raw["verifier_receipt_ref"], "verifier_receipt_ref"
),
acceptance_revision=_optional_text(
raw["acceptance_revision"], "acceptance_revision"
),
acceptor_id=_optional_text(raw["acceptor_id"], "acceptor_id"),
outcome=raw["outcome"], # type: ignore[arg-type]
authenticated=raw["authenticated"], # type: ignore[arg-type]
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
)
def collect_closure(
binding: OperationBinding,
readers: OperationReaders,
*,
now: datetime,
) -> ClosureObservation:
"""Read closure claims independently; absent adapters remain UNKNOWN."""
_datetime(now, "now")
effect = _unknown_effect(binding)
raw = _read(readers.gitops, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _GITOPS_FIELDS, "gitops reader result")
if doc["schema"] != "aios.operation-effect-observation.v1":
raise ValueError("unsupported effect schema")
effect = _effect(doc["effect"])
except (TypeError, ValueError):
effect = _unknown_effect(binding)
verification = _unknown_verification(binding)
raw = _read(readers.verifier, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _VERIFIER_FIELDS, "verifier reader result")
if doc["schema"] != "aios.operation-verification-observation.v1":
raise ValueError("unsupported verifier schema")
verification = _verification(doc["verification"])
except (TypeError, ValueError):
verification = _unknown_verification(binding)
acceptance = _unknown_acceptance(binding)
expected_acceptor_id = "unresolved-acceptor"
expected_acceptance_revision = "unresolved-revision"
raw = _read(readers.acceptance, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _ACCEPTANCE_FIELDS, "acceptance reader result")
if doc["schema"] != "aios.operation-acceptance-observation.v1":
raise ValueError("unsupported acceptance schema")
acceptance = _acceptance(doc["acceptance"])
expected_acceptor_id = _text(
doc["expected_acceptor_id"], "expected_acceptor_id"
)
expected_acceptance_revision = _text(
doc["expected_acceptance_revision"],
"expected_acceptance_revision",
)
except (TypeError, ValueError):
acceptance = _unknown_acceptance(binding)
return ClosureObservation(
effect=effect,
verification=verification,
acceptance=acceptance,
expected_acceptor_id=expected_acceptor_id,
expected_acceptance_revision=expected_acceptance_revision,
)
__all__ = ["OperationReaders", "collect_closure", "collect_observation"]

Some files were not shown because too many files have changed in this diff Show More