aps-agent/server/core/registry/visibility.ts

66 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 能力可见性(任务 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(',');
}