From 3c050b923c558155dbedf6a179ff973bdc2fbe95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=AD=86?= <2685475410@qq.com> Date: Fri, 28 Aug 2026 10:06:35 +0800 Subject: [PATCH] =?UTF-8?q?M9=20=E8=A1=A5=E4=B8=81=EF=BC=9A=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E5=86=92=E7=83=9F=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94?= =?UTF-8?q?=E9=95=9C=E6=A0=91=E5=88=97=E8=A1=A8=20str=20=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E3=80=81=E5=86=85=E7=BD=AE=E8=83=BD=E5=8A=9B?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E4=BD=9C=E7=94=A8=E5=9F=9F=E4=B8=8E=E7=A7=8D?= =?UTF-8?q?=E5=AD=90=E6=8F=90=E4=BA=A4=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真实进程冒烟(灵台+神识起进程 → 登录 → 能力目录 → 授契 → 调用)抓到三处缺陷,全量测试因分支未覆盖而漏网: ① 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 接真神识) --- backend/app/db/governance_seed.py | 4 ++- backend/app/domains/baigong/service.py | 35 +++++++++++++++++----- backend/app/domains/mirror_tree/service.py | 11 +++---- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/backend/app/db/governance_seed.py b/backend/app/db/governance_seed.py index caa6c3a..ae8e267 100644 --- a/backend/app/db/governance_seed.py +++ b/backend/app/db/governance_seed.py @@ -4,6 +4,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.domains.baigong.models import Capability +from app.domains.baigong.service import PLATFORM_SCOPE _BUILTIN_CAPABILITIES = [ # name, 描述, 执行类型, 执行器, 四维风险, 数据范围, 目的地, consent @@ -71,7 +72,7 @@ async def _seed_capabilities(session: AsyncSession) -> None: session.add( Capability( name=name, - owner_scope="local-user", + owner_scope=PLATFORM_SCOPE, description=desc, executor_type=executor_type, executor_ref=ref, @@ -89,3 +90,4 @@ async def _seed_capabilities(session: AsyncSession) -> None: async def ensure_governance_seed(session: AsyncSession) -> None: """启动幂等登记内置能力。""" await _seed_capabilities(session) + await session.commit() diff --git a/backend/app/domains/baigong/service.py b/backend/app/domains/baigong/service.py index 1a4c69c..17feaf4 100644 --- a/backend/app/domains/baigong/service.py +++ b/backend/app/domains/baigong/service.py @@ -26,10 +26,20 @@ from app.models.safety import AuditEvent, PendingConfirmationRecord 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 with AsyncSessionLocal() as session: 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) session.expunge(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 with AsyncSessionLocal() as session: - caps = ( - ( - await session.execute( - select(Capability).where(Capability.owner_scope == owner_scope) + caps = [ + c + for c in ( + ( + await session.execute( + select(Capability).where( + (Capability.owner_scope == owner_scope) + | ( + (Capability.owner_scope == PLATFORM_SCOPE) + & (Capability.source == "builtin") + ) + ) + ) ) + .scalars() + .all() ) - .scalars() - .all() - ) + ] mandates = ( ( await session.execute( diff --git a/backend/app/domains/mirror_tree/service.py b/backend/app/domains/mirror_tree/service.py index 1b94d1a..e02806b 100644 --- a/backend/app/domains/mirror_tree/service.py +++ b/backend/app/domains/mirror_tree/service.py @@ -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 with AsyncSessionLocal() as session: - character_ids = [ - c.id - for c in ( + character_ids = list( + ( await session.execute( select(Character.id).where(Character.owner_scope == owner_scope) ) - ).scalars() - ] + ) + .scalars() + .all() + ) if not character_ids: return [] stmt = select(MirrorTree).where(