/** * 能力可见性(任务 A3.5):按身份与白名单决定谁能看到哪些能力。 * * 两条边界(架构设计 5.1): * - 工具列表与能力清单必须来自同一次过滤,否则模型能看到用户看不到的能力 * - 权限归业务系统(D9),这里只管「平台把哪些能力暴露给这个身份」,不管业务系统内部的角色 * * 默认策略:开发期 defaultAllow=true(未配白名单时全部可见)。接入真实身份(A4.1)后应改成 false, * 由授权表逐条给出——这与治理模型里「未登记默认拒绝」是同一条原则。 */ import type { Capability } from '@aps/provider-sdk'; export interface CapabilityGrant { /** 空表示不限用户 */ userId?: string; /** 空表示不限租户 */ tenantId?: string; /** 允许的能力 id 前缀,如 aps.schedule 或 fake. */ allow: string[]; } export interface CapabilityVisibility { defaultAllow: boolean; denyWrite: boolean; grants: CapabilityGrant[]; } export interface VisibilitySubject { userId: string; tenantId: string; } const matchesPrefix = (capabilityId: string, prefixes: string[]): boolean => prefixes.some((prefix) => (prefix.endsWith('*') ? capabilityId.startsWith(prefix.slice(0, -1)) : capabilityId === prefix)); const grantApplies = (grant: CapabilityGrant, subject: VisibilitySubject): boolean => { if (grant.userId !== undefined && grant.userId !== subject.userId) return false; if (grant.tenantId !== undefined && grant.tenantId !== subject.tenantId) return false; return true; }; /** 这个身份能看到这个能力吗 */ export function isCapabilityVisible(capability: Capability, visibility: CapabilityVisibility, subject: VisibilitySubject): boolean { // 写能力在只读模式下对所有人不可见(演示、审计或只读部署用) if (visibility.denyWrite && capability.effect === 'write') return false; const granted = visibility.grants.some((grant) => grantApplies(grant, subject) && matchesPrefix(capability.id, grant.allow)); if (granted) return true; if (!visibility.defaultAllow) return false; // 默认允许模式下仍可用 denyWrite 收口;白名单里的 deny 规则暂不做,等 A4 的策略层 return true; } export function visibleCapabilities( capabilities: Capability[], visibility: CapabilityVisibility, subject: VisibilitySubject, ): Capability[] { return capabilities.filter((capability) => isCapabilityVisible(capability, visibility, subject)); } /** 能力集的指纹:用于判断运行时会话是否需要重建(工具集变了就得重建) */ export function capabilityFingerprint(capabilities: Capability[]): string { return [...capabilities.map((capability) => capability.id)].sort().join(','); }