M5:百工与司命——能力注册表、授契行契请命、禁域裁决与观察标证

- 百工域 domains/baigong:capabilities(统一能力契约:执行类型 builtin/qiankun/mcp/http + 四维风险标注 + 来源版本)、mandates(授契 grant / 行契 charter 预算时间窗,撤销立即生效)、tool_calls(净化摘要 + 观察标证);请命复用 pending_confirmations 加 expires_at/brief_json(超时=拒绝执行 HJ-1302,六要素说明)
- 司命裁决器 siming.py:四维取最高档(能力/数据/后果/情境);禁域为代码常量不可配置(递归删根/格式化/写裸设备/fork 炸弹、file:// 协议、密钥材料外发)——专项测试断言恶意标注与篡改对象仍拒 HJ-1301
- 执行链:禁域→裁决(HJ-1303 未授契拒且双留痕)→请命→执行(乾坤经 SandboxClient HMAC;MCP 经 call_tool;内置无副作用)→净化标证(观察非事实,敏感遮蔽+体量截断)→行动账
- MCP 登记/启停/刷新自动派生与撤下 capabilities(博闻登记接入点);内置能力种子(echo/datetime/qiankun.shell/web.fetch)
- 端点:/capabilities /mandates /petitions /tool-calls + /tools 兼容投影;前端百工页四区(能力目录四维标注/授契行契/请命批准拒绝/行动账观察标证)
- 退役:modules/safety|skills|tool_runtime|agent、application/tool_runtime、services/skills|safety、tool_policies/skills/model_endpoint_overrides 表(迁移 006)、前端 SkillManager/ToolsConsole/旧 tools 页
- 验收(test_baigong_siming 四用例):未授契被拒留痕、请命超时不执行、禁域不可关闭、结果为观察不入忆;后端 454、前端 220、BFF 4、神识 5 包全绿(2 个预存环境失败不变)
This commit is contained in:
chenxin committed 2026-08-27 19:17:21 +08:00
1 parent 144415247e
commit 684261f4b4
97 files changed
+3075 -5554

No files matched your search

+48 -36
View File
@@ -1,14 +1,13 @@
"""司命台管理聚合服务(api-spec §19.1–§19.3)。
九枢健康度由各枢运行/治理数据派生(不落长期状态,§19.1);朱批待办复用
ConfirmationStore(§19.2);起居注审计复用 audit_events(§19.3)。
待决请命(§19.2);起居注审计复用 audit_events(§19.3)。
"""
import json
from sqlalchemy import func, select
from app.application.tool_runtime.runtime import ToolRuntime
from app.core.config import settings
from app.core.security import redact_text
from app.core.time import to_iso, utc_now
@@ -23,8 +22,7 @@ from app.models.governance import (
from app.models.idempotency import IdempotencyKey
from app.models.xirang import Memory
from app.models.runtime import Event, Run
from app.models.safety import AuditEvent
from app.modules.safety.confirmation import ConfirmationStore, PendingConfirmation
from app.models.safety import AuditEvent, PendingConfirmationRecord
from app.repositories import audit as audit_repo
from app.repositories import characters as character_repo
from app.repositories import governance as governance_repo
@@ -66,12 +64,22 @@ def _summarize_input(value: dict | None) -> str:
async def get_pivots_health(
*,
confirmation_store: ConfirmationStore,
tool_runtime: ToolRuntime | None,
owner_scope: str = settings.default_owner_scope,
) -> PivotHealthReport:
async with AsyncSessionLocal() as session:
pending_count = len(await confirmation_store.list_pending())
pending_count = len(
list(
(
await session.execute(
select(PendingConfirmationRecord).where(
PendingConfirmationRecord.status == "pending"
)
)
)
.scalars()
.all()
)
)
goals_n = await governance_repo.count(session, Goal)
world_n = await governance_repo.count(session, WorldEntity)
plans_n = await governance_repo.count(session, Plan)
@@ -80,7 +88,7 @@ async def get_pivots_health(
memories_n = await governance_repo.count(session, Memory, Memory.status != "tombstoned")
character = await character_repo.get_active_character(session, owner_scope)
has_personality = bool(character and character.current_personality_version_id)
tool_enabled = settings.enable_tool_runtime and tool_runtime is not None
tool_enabled = True # 百工域常驻(乾坤可用性按端点区分)
now = to_iso(utc_now())
raw: list[tuple[str, str, str, bool, str]] = [
@@ -128,38 +136,42 @@ async def get_pivots_health(
)
async def get_pending_approvals(
*,
confirmation_store: ConfirmationStore,
tool_runtime: ToolRuntime | None,
) -> list[PendingApproval]:
specs = {spec.name: spec for spec in tool_runtime.list_specs()} if tool_runtime else {}
async def get_pending_approvals() -> list[PendingApproval]:
async with AsyncSessionLocal() as session:
records = (
(
await session.execute(
select(PendingConfirmationRecord)
.where(PendingConfirmationRecord.status == "pending")
.order_by(PendingConfirmationRecord.created_at.asc())
)
)
.scalars()
.all()
)
approvals: list[PendingApproval] = []
for pending in await confirmation_store.list_pending():
spec = specs.get(pending.tool)
permission_policy = str(spec.permission_policy) if spec else "ask"
possible_impact = "可能产生副作用" if (spec and spec.side_effect) else None
approvals.append(_pending_to_approval(pending, permission_policy, possible_impact))
for record in records:
try:
input_payload = json.loads(record.input_json) if record.input_json else {}
except ValueError:
input_payload = {}
approvals.append(
PendingApproval(
confirmation_id=record.id,
character_id=record.character_id,
tool=record.tool,
risk_level=record.risk_level,
permission_policy="ask",
action_summary=f"调用能力 {record.tool}",
input_summary=_summarize_input(input_payload),
possible_impact=None,
reason=record.reason,
created_at=record.created_at.isoformat() if record.created_at else "",
)
)
return approvals
def _pending_to_approval(
pending: PendingConfirmation, permission_policy: str, possible_impact: str | None
) -> PendingApproval:
return PendingApproval(
confirmation_id=pending.confirmation_id,
character_id=pending.character_id,
tool=pending.tool,
risk_level=pending.risk_level,
permission_policy=permission_policy,
action_summary=f"调用工具 {pending.tool}",
input_summary=_summarize_input(pending.input),
possible_impact=possible_impact,
reason=pending.reason,
created_at=pending.created_at,
)
async def get_audit_timeline(
*,
pivot: str | None = None,