402 lines
14 KiB
Python
402 lines
14 KiB
Python
"""现场(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,
|
||
)
|