M9 补丁:启动冒烟修复——镜树列表 str 属性访问、内置能力平台作用域与种子提交缺失
真实进程冒烟(灵台+神识起进程 → 登录 → 能力目录 → 授契 → 调用)抓到三处缺陷,全量测试因分支未覆盖而漏网: ① mirror_tree.list_trees:select(Character.id).scalars() 已返回 id 字符串,c.id 属性访问在有 character 的 owner 上必炸(M2a 引入,测试只走了无 character 分支)——改为直接收集 ② 内置能力种子写死 owner_scope=local-user,真实用户(UUID owner)查不到能力目录——引入 PLATFORM_SCOPE:builtin 能力挂平台域全局可见可授契,自定义能力仍按 owner 隔离 ③ ensure_governance_seed 缺 session.commit——种子数据未提交即随启动 session 回滚(直调误读为成功因同 session 可见未提交数据) 冒烟证据:未授契调用 403 HJ-1303 留痕 → 授行契 → 调用 ok 返回观察标证;能力目录 4 项(builtin.datetime/echo/qiankun.shell/web.fetch);全量回归 349 passed 不变 另:本地 .env 补入神识段(SHENSHI_BASE_URL=127.0.0.1:8080 接真神识)
This commit is contained in:
1 parent
2483a25f63
commit
3c050b923c
3 files changed
+31
-9
No files matched your search
@@ -4,6 +4,7 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.domains.baigong.models import Capability
|
from app.domains.baigong.models import Capability
|
||||||
|
from app.domains.baigong.service import PLATFORM_SCOPE
|
||||||
|
|
||||||
_BUILTIN_CAPABILITIES = [
|
_BUILTIN_CAPABILITIES = [
|
||||||
# name, 描述, 执行类型, 执行器, 四维风险, 数据范围, 目的地, consent
|
# name, 描述, 执行类型, 执行器, 四维风险, 数据范围, 目的地, consent
|
||||||
@@ -71,7 +72,7 @@ async def _seed_capabilities(session: AsyncSession) -> None:
|
|||||||
session.add(
|
session.add(
|
||||||
Capability(
|
Capability(
|
||||||
name=name,
|
name=name,
|
||||||
owner_scope="local-user",
|
owner_scope=PLATFORM_SCOPE,
|
||||||
description=desc,
|
description=desc,
|
||||||
executor_type=executor_type,
|
executor_type=executor_type,
|
||||||
executor_ref=ref,
|
executor_ref=ref,
|
||||||
@@ -89,3 +90,4 @@ async def _seed_capabilities(session: AsyncSession) -> None:
|
|||||||
async def ensure_governance_seed(session: AsyncSession) -> None:
|
async def ensure_governance_seed(session: AsyncSession) -> None:
|
||||||
"""启动幂等登记内置能力。"""
|
"""启动幂等登记内置能力。"""
|
||||||
await _seed_capabilities(session)
|
await _seed_capabilities(session)
|
||||||
|
await session.commit()
|
||||||
@@ -26,10 +26,20 @@ from app.models.safety import AuditEvent, PendingConfirmationRecord
|
|||||||
from app.schemas.auth import CurrentUser
|
from app.schemas.auth import CurrentUser
|
||||||
|
|
||||||
|
|
||||||
|
# 内置能力挂平台作用域(全局可见可授契;自定义能力按 owner 隔离)
|
||||||
|
PLATFORM_SCOPE = "__platform__"
|
||||||
|
|
||||||
|
|
||||||
|
def _capability_visible(cap: Capability, owner_scope: str) -> bool:
|
||||||
|
return cap.owner_scope == owner_scope or (
|
||||||
|
cap.owner_scope == PLATFORM_SCOPE and cap.source == "builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _get_capability(capability_id: str, owner_scope: str) -> Capability:
|
async def _get_capability(capability_id: str, owner_scope: str) -> Capability:
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
cap = await session.get(Capability, capability_id)
|
cap = await session.get(Capability, capability_id)
|
||||||
if cap is None or cap.owner_scope != owner_scope:
|
if cap is None or not _capability_visible(cap, owner_scope):
|
||||||
raise AppError("not_found", "能力不存在。", status_code=404)
|
raise AppError("not_found", "能力不存在。", status_code=404)
|
||||||
session.expunge(cap)
|
session.expunge(cap)
|
||||||
return cap
|
return cap
|
||||||
@@ -300,15 +310,24 @@ async def resolve_petition(petition_id: str, approved: bool) -> dict[str, Any]:
|
|||||||
|
|
||||||
async def list_capabilities(owner_scope: str) -> list[dict[str, Any]]:
|
async def list_capabilities(owner_scope: str) -> list[dict[str, Any]]:
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
caps = (
|
caps = [
|
||||||
|
c
|
||||||
|
for c in (
|
||||||
(
|
(
|
||||||
await session.execute(
|
await session.execute(
|
||||||
select(Capability).where(Capability.owner_scope == owner_scope)
|
select(Capability).where(
|
||||||
|
(Capability.owner_scope == owner_scope)
|
||||||
|
| (
|
||||||
|
(Capability.owner_scope == PLATFORM_SCOPE)
|
||||||
|
& (Capability.source == "builtin")
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
]
|
||||||
mandates = (
|
mandates = (
|
||||||
(
|
(
|
||||||
await session.execute(
|
await session.execute(
|
||||||
|
|||||||
@@ -120,14 +120,15 @@ async def create_tree(*, character_id: str, owner_scope: str, title: str, defaul
|
|||||||
|
|
||||||
async def list_trees(owner_scope: str, character_id: str | None = None) -> list[MirrorTree]:
|
async def list_trees(owner_scope: str, character_id: str | None = None) -> list[MirrorTree]:
|
||||||
async with AsyncSessionLocal() as session:
|
async with AsyncSessionLocal() as session:
|
||||||
character_ids = [
|
character_ids = list(
|
||||||
c.id
|
(
|
||||||
for c in (
|
|
||||||
await session.execute(
|
await session.execute(
|
||||||
select(Character.id).where(Character.owner_scope == owner_scope)
|
select(Character.id).where(Character.owner_scope == owner_scope)
|
||||||
)
|
)
|
||||||
).scalars()
|
)
|
||||||
]
|
.scalars()
|
||||||
|
.all()
|
||||||
|
)
|
||||||
if not character_ids:
|
if not character_ids:
|
||||||
return []
|
return []
|
||||||
stmt = select(MirrorTree).where(
|
stmt = select(MirrorTree).where(
|
||||||
|
|||||||
Reference in new issue
Block a user