447 lines
17 KiB
Python
447 lines
17 KiB
Python
"""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)
|