/** * 信箱**收件人生命周期面**([ref])—— core `MailboxStore` 契约里被点名、但机制**刻意不由 core 命名**的 * 那一半:「这个收件人正在被部署删掉」。 * * core 的原话(`core/store-contracts/mailbox-store-contract.ts` 的 `MailboxTombstonedRecipientContractHooks` * 逐字):「Put `(scope, handle)` into the deployment's PRE-DELETE state — whatever that is for this backend * (a cascade marking the session row, a `deleting` column, a tombstone table). Core does not name the * mechanism, only what `append` must then do.」 ⇒ 本文件就是 server 侧那个 "whatever"。 * * ## 两个动词,各答一个问题 * * · {@link MailboxRecipientLifecycle.retireRecipient} —— **只打标,不清仓**。打标之后 `append` 响亮拒 * (`MAILBOX_TOMBSTONED_RECIPIENT_CODE`),但**已入箱的消息、活着的租约一个都不动**。这不是口味问题: * core 的 T2 试剂盒有两格逐字钉它 ——「拒的是入口,不是清仓:被拒的 append 零副作用,已入箱的消息按原 * seq/内容原封不动」与「副作用轴 · 活租约:被拒的 append 不得动别人手上的租约」。所以会话删除级联要 * 清箱时,是 `retireRecipient` **之后再** `drop`(两个动词两件事,见下)。 * · {@link MailboxRecipientLifecycle.listRetiredRecipients} —— 目录用它把「正在删除中」那条记录 * (`liveness: "deleted"`)补出来。 * * ## 墓碑的寿命 = 箱的寿命(`drop` 即扫,**没有** revive 动词) * * `retireRecipient` 打的标随 `drop(scope, handle)` **一起消失**:盒的生命周期终结,收件人这个态也就没有 * 附着的对象了。这不是省事,是 core 自己的清扫规则 —— 它的参照目录 `createInMemoryPeerDirectory.sweep` * 逐字写着「a non-live row is removable ONLY while its box is EMPTY」,而 `drop` 恰恰是把箱清空那一步; * 契约对墓碑的要求只有「the host's deletion cascade writes it BEFORE dropping the box」(**写在丢箱之前**), * 没有要求它活过丢箱。 * * 🔴 **为什么这条设计比「墓碑永久 + 一个撤回动词」严格更好**(codex 对抗复审 r1 三条 [high] 之一,验真后重设计): * 会话 id 由调用方自选(`http/admission.ts:153` 只有长度门,`security.ts:187-199` 明写刻意不加形状门), * 删掉之后**同一个 id 可以被重新登记**(`run-local --session myproject`、留存腿删后用户再跑同名会话)。 * 永久墓碑于是需要一个「这个收件人回来了」的撤回口,而那个判据**在读面上判不出来** —— 会话删除级联是 * 「子腿先、`session_meta` 最后」(E21 次序契约),所以级联在飞的那一段里,活会话行与新墓碑是**同时** * 存在的:任何「有活行就撤标」的规则都会在删除进行中把围栏拆掉(实序:retire → 目录读到旧活行 → 撤标 → * 投递成功 → drop 把它删掉 = 一条有 `queued` 回执的消息被吞)。改成「墓碑随箱亡」之后: * · 撤回动词**不存在** ⇒ 那条竞态结构上不可达,读面也不再写任何东西; * · 重新登记的 id 天然可用(箱与墓碑在上一次 `drop` 里一起没了),不依赖任何时钟比较; * · 代价如实:级联**跑完之后**向那个 id 投递拿到的是「查无此会话」而不是「已删除」——「已删除」这一句 * 只在级联在飞的那段窗口内说得出。两者都是拒、都不 park,差的只有措辞。 * * ## `drop` 与 `retire` 仍是两个动词(别合并成一个) * * `retireRecipient` **只打标**:此后 append 响亮拒,但已入箱的消息与活租约一个都不动(core T2 试剂盒的 * 两格副作用判据)。`drop` 才清仓,并同时扫掉墓碑。会话删除级联两件都做,**先 retire 再 drop**。 * * ## 缺席形是**能力差**,不是兜底 * * 看不见收件人生命周期的后端(local 车道用的是 core 自带的 `FileMailboxStore`)由 * {@link createDropOnlyRecipientLifecycle} 服务:retire ⇒ 直接 `drop`(箱真清空、投递真拒 —— 只是拒的 * 措辞退化成「查无此会话」而不是「已删除」),墓碑清单恒空 ⇒ 目录不铸 `deleted` 行。这不是静默降级: * 后端选择点(`boot/stores.ts` 的 `mailbox_store_enabled` 行)逐次落 `recipientTombstones` 读数, * 披露在 `docs/DEPLOY-PREREQS.md`。方向上它也不放宽任何东西 —— 两种形都**不收**给已删会话的信。 */ import type { MailboxStore } from "@sema-agent/core"; /** 一条墓碑(= 一个已退役的收件人)。 */ export interface MailboxRecipientTombstone { /** 盒句柄(会话盒 = `session.<小写 id>`)。 */ readonly handle: string; /** 退役时刻(epoch ms)——目录据此铸 `diedAt`。 */ readonly retiredAt: number; /** 退役前的显示名(会话的删前 `title`);缺席 = 当时没有可用的名字。 */ readonly label?: string; } export interface MailboxRecipientLifecycle { /** * 把 `(scope, handle)` 置入**预删除态**:此后 `append` 一律以 `MAILBOX_TOMBSTONED_RECIPIENT_CODE` 拒。 * **只打标**——不删消息、不动租约、不碰盒行的高水位(理由 = core T2 试剂盒的两格副作用判据,见模块头)。 * 幂等(重复 retire 只更新时刻/名字)。 * * @returns 是否真的落了一张耐久墓碑。`false` = 本后端没有预删除态,箱已被直接 `drop` * (拒的方向不变,退化的只有拒绝措辞与「正在删除中」那一行的可见性)。 */ retireRecipient(scope: string, handle: string, at: number, label?: string): Promise; /** 本 scope 下句柄以 `handlePrefix` 开头的墓碑。无预删除态的后端恒返回空。 */ listRetiredRecipients(scope: string, handlePrefix: string): Promise; } /** * 看不见收件人生命周期的后端的诚实形:retire = 直接把箱 `drop` 掉(**不**打标,因为没有可打标的地方), * 墓碑清单恒空。 * * 🔴 与 `retireRecipient` 的 "只打标不清仓" 契约**有意不同**,且这不是违约:契约的那两格判据是给 * **有**预删除态的后端写的(它们要能在拒收的同时保住已入箱的消息给运维/留存腿看);没有预删除态的后端 * 唯一能做到「此后不再收信」的动作就是把箱清掉,`false` 返回值就是这件事的机器可读声明。core 的 T2 * 试剂盒**不挂**在这种后端上(它自己的注逐字:只挂看得见收件人生命周期的后端)。 */ export declare function createDropOnlyRecipientLifecycle(store: MailboxStore): MailboxRecipientLifecycle; //# sourceMappingURL=mailbox-recipient-lifecycle.d.ts.map