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

402 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""现场(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,
)