import Spec.Prelude /-! # ProjectGroup —— 飞书项目群作为协作空间(ADR-0001) ADR-0001 的核心:一个 project 对应一个**长生命周期**飞书项目群;群是协作空间,**不是锁 owner**(锁归 `AgentRun`,见 `Lock` / ADR-0002),不是临时处理 session。群可在无 Claude 处理时保持开启;教师离群/静音与项目权限、与 Claude 生命周期相互独立。本模块钉死 project↔group 的一对一绑定——likec4 画得出"project has group",画不出"恰好一个、且群 不持锁"。 **群失效态与绑定快照(`OPEN`, ADR-0001 Consequences):** 群解散/归档/不可达时, `GroupBinding` 快照应变为 `none`(解绑、允许 rebind)还是保持 `some` 但指向失效 chat (悬挂、待管理员干预)未决策。ADR-0001 已点名 "rebinding or group archival needs explicit product rules";本模块只刻画健康态 1:1 不变式,不臆造失效态语义。实现遇到 群解散必须 surface,不得默认任一方。 -/ namespace Spec.System variable (I : Identifiers) /-- 飞书项目群(`PINNED` 长生命周期协作空间, ADR-0001)。承载 project 与飞书 chat 的绑定; **不是锁 owner**(锁归 `AgentRun`,见 `Lock`);不是临时 session。 -/ structure ProjectGroup where /-- 群对应的飞书 chat(`PINNED` 关系, ADR-0001 "one project has one Feishu project group";chat 标识见 `Identifiers.ChatId`)。 -/ chat : I.ChatId /-- 项目↔群绑定表(`PINNED` 每项目至多一个群, ADR-0001)。`ProjectId → Option ChatId` 的 结构本身即"一个 project 至多绑一个群"(由 `Option` 自带);良构补另一半——单射。 -/ def GroupBinding := I.ProjectId → Option I.ChatId /-- 绑定良构:**单射**——不同 project 不绑同一 chat(`PINNED` 1:1 的另一半, ADR-0001)。 "每 project 至多一个群"由 `Option` 结构自带;这条钉死"每群至多属于一个 project"。群 rebinding/archival 是生命周期事件(ADR-0001 Consequences),不在本快照不变式内。 -/ def GroupBinding.WellFormed (b : GroupBinding I) : Prop := ∀ p₁ p₂ c, b p₁ = some c → b p₂ = some c → p₁ = p₂ end Spec.System