/** * [ref] v2/F4 —— shared-memory 读面的**授权折叠**:principal → 可读 org scope 集。 * * 为什么复用 {@link OrgMemoryDirectory} 而不是另立一张表:「谁属于 org:acme」在一个进程里必须只有一个 * 答案。core 的记忆准入 seam、`/v1/memory/*` 的属主门、以及这里的共享库读面读的是**同一个目录实例** * (`createOrgMemoryAdmissionWiring` 的产物),因此共享同一份 TTL 缓存 / 退避窗 / gen 高水位——吊销窗内 * 三面不会公开分歧。各建各的目录 = 三个答案,那正是 [ref] §7 收编要消灭的东西。 * * 🔴 判别联合原样透传(N3/C5 裁定的下游一致性):目录说 `unavailable` 时**绝不**折成「你不属于任何 * org」。前者在模型面渲染成「连接不可用,恢复前拒读」(可重试),后者渲染成「本会话没有连接任何记忆库」 * (终局,模型就此放弃)。两句话是相反的指令,把前者塌进后者是一次静默的 fail-open。 * * 🔴 deployment-origin scope 的多租户切断:operator 在配置里自证的 org scope(`MEMORY_SCOPE=org:x`、 * 单用户部署的 projects 登记簿)只在**无租户边界**的部署里授予。多租户部署(`requirePrincipal`)下, * 一条部署级声明会同时授予每一个 principal —— 那是跨租户读,不是配置便利。装配点因此按部署形态把这个 * 集合择净后再传进来(见 main.ts 的构造行),本模块只忠实使用它:哪些 scope 算「部署自证」是装配点的 * 判断,不是这里的。 */ import type { SharedMemoryScopeAuthorizer } from "./plugins/shared-memory-store-sql.js"; import type { OrgMemoryDirectory } from "./org-memory-admission.js"; export interface SharedMemoryScopeAuthorizerOptions { /** 目录实例。**必须**与 core 准入 seam / memory-policy 面同一只(见头注)。 */ directory: OrgMemoryDirectory; /** * 部署自证的 org scope —— **取值口而非值**([ref])。 * * 🔴 类型上是函数是刻意的,不是风格:这组 scope 的真源是 `config.projects` 的 `defaultScopes`, * 而 `config.projects` 被 config-center **就地热应用**(`mutateInPlace`,整表替换),env 腿恒空 * ⇒ 这一域的**常态**就是运行期变更。此前这里收的是一个数组(装配点在 boot 期算一次),而且本模块 * 还 `[...new Set(...)]` 又复制一层,连「换引用」这条后路都封死 ⇒ 两个方向都错到进程重启为止: * · **新登记**一个带 `org:` 的项目 ⇒ 该 org 的共享库在快照里没有 ⇒ 折成 `{state:"connected", * stores:[]}`,对模型面是「本会话没有连接任何记忆库」这种**终局式**空集(不是可重试的 * unavailable),运维以为配好了; * · **撤销**方向更糟:项目/scope 下架后,principal-less 的单用户请求仍按旧快照继续被放行 —— * 一次运维撤销动作**静默不生效**([ref] 点名的静默类型),方向是 fail-OPEN。 * 而且它没被登进 `config-center/restart-signal.ts` 的 `RESTART_SLICES` ⇒ `/health` 连一句 * 「该重启了」都不会说。⇒ 收口是把读取时机对齐消费时机:每次 `resolve` 现取。 * (缺席 = 部署没有任何自证 scope,与传 `() => []` 等价。) * * 📌 **取值口契约**:交回来的数组必须**已去重**(生产实现 `collectDeploymentOrgScopes` 用 Set 建, * 天然满足)。本层不再替它去重 —— principal 缺席那支直接把它当结果交出去。 */ deploymentScopes?: () => readonly string[]; } /** * 活对象(闭包持有目录与取值口)⇒ `create*`(CLAUDE.md 工厂命名律)。 * * **代价与它的边界**(codex 交叉复审 R1-[medium] 的一半采纳,一半具名保留):取值口按调用现算, * 意味着每次 `resolve` 都要扫一遍 `config.projects`(O(项目数 × 每项 scope 数)的纯 CPU)。 * · **已采纳**:principal 缺席那支不再二次建 Set —— 生产取值口(`collectDeploymentOrgScopes`)本来就用 * Set 建、交回来已去重,这里再包一层是纯浪费。**去重责任因此上移到取值口**(契约见下面那个字段的注), * principal 在场那支的合并 Set 保留(它要与目录 scope 求并,那一次是真需要的)。 * · **具名保留**(不做 generation 缓存):`config.projects` 由 `mutateInPlace` **就地**改,对象身份恒定, * 没有可用的失效信号;而 config-center 的 `appliedGeneration` 是那个模块的私有 WeakMap,不在公面上。 * 要缓存就得先给 config-center 开一只「已提交世代」读口 —— 那是别人模块的接缝件,且**缓存失效写错 * 一次,就恰好把本条 finding 刚关掉的那个陈旧窗原样放回来**(还多一层代码)。规模面亦不支持先做: * projects 是运维手写的登记簿(量级十几~百),而这条腿的**同一次**调用里,principal 在场那支还要 * `await directory.lookup()`(网络/缓存),扫表在它旁边不构成瓶颈。⇒ 若哪天登记簿真长到会阻塞事件循环, * 正解是**先给 projects/defaultScopes 定尺**(发布期拒收超尺,与本仓其它 wire 上限同族),再谈缓存。 */ export declare function createSharedMemoryScopeAuthorizer(opts: SharedMemoryScopeAuthorizerOptions): SharedMemoryScopeAuthorizer; //# sourceMappingURL=shared-memory-scope-authorizer.d.ts.map