单一事实源:本文件是 CGC 平台全部领域术语的权威出处。代码、文档、Issue、测试命名一律使用本表词汇,不引入同义词。 日期:2026-08-01 | 维护:领域建模工程师 | 依据:领域建模工作坊 + grill 决策(D1–D14 + D-A1–D-A7,2026-08-01)——原始设计/决策文档已按「code 为真相源」清理,历史版本见 git 归档 状态:已整合 BYO/MCP 时代术语(D1–D14 决策全量落盘)+ workflow 引擎术语(D-A 系列)
网站 = 业务中枢 + MCP server;用户自带 OpenClacky 做 Agent 执行(BYO,自带模型)。 网站不再自行运行 LLM/Agent,只负责业务状态、鉴权、审计;一切 AI 执行发生在用户本地的 OpenClacky,通过 MCP 协议调用网站的领域能力。
- 定义:用户自带本地 OpenClacky 作为其 Agent 执行环境,平台不承担任何 LLM 推理成本与执行环境;平台只提供领域数据与业务能力。
- 架构位置:连接范式。决定产品形态——模型调用与对话始终发生在用户自己的电脑上,平台零 AI 成本(D3);课程学习页的对话栏经 CGC 本机 Host 驱动学员电脑上的 agent,网站本身不执行 agent(D4 已被 ADR-0022 修订)。
- 定义:网站暴露的、供用户 OpenClacky 调用的协议端点。技术选型 anubis_mcp(Elixir/Phoenix,活跃维护)。全平台只暴露一个 MCP server(D6)。
- 工具鉴权立场(架构深化 C):豁免声明 = 工具模块自身
use Anubis.Server.Component的meta:opt(workspace_id: :optional免 workspace_id 必填|membership: :deferred成员门槛下沉工具层授权|membership: :public公开浏览族——任何持连接 token 的登录用户可用,匿名姿态读在工具层,KTD2/KTD3|membership: :platform_admin平台治理族——is_platform_admin全局标记判定、无工作台作用域,role-agent-journeys-v2 S2);Wrapper 经组件注册派生 name→meta 门控(:persistent_term缓存 + Server 模块 md5 指纹防陈旧)。未声明 meta 的工具 = member-only + workspace_id 必填(fail-closed 默认)——例外不再维护于 Wrapper 静态清单。 - 工具可见性分层(#1085,ADR-0021):门控之外,工具经
use Anubis.Server.Component的scopes:(tutor|workspace_admin|platform_admin|缺省 = 全员可见)声明所属可见层;tools/list只列调用者所属最高层累计可见的工具,越层tools/call在 anubis 层即拒绝(文案forbidden: <tool> requires <角色>)并经Wrapper.record_denied/4补写 forbidden 审计。scope 由Cgc2046.Mcp.Scopes每请求按current_user跨工作台并集重算(tutor = 任一台持 tutor/owner/admin;workspace_admin = 任一台持 owner/admin;平台管理员级联全部三层),角色变更下一个请求即生效,客户端缓存的工具列表需重连刷新。分类原则:可见面不比授权更严;scope 是粗检查,Wrapper / 工具层 / 数据层授权全部保留。各层精确名单钉死在test/support/mcp_tool_tiers.ex——新增工具必须显式归层(anubis 默认「不声明 scope = 全员可见」,漏标不会自己报错)。 - 架构位置:B 通道主干(见下)。网站能力以"工具"形态暴露给 Agent。
- 定义:用户 OpenClacky → 网站 MCP server 的连接方向,是平台唯一的对外通道。
- 架构位置:D5。取代原 A 通道,承载一切 Agent 对平台的读写操作。
- 定义:原设计中"网站向用户 OpenClacky 派发任务"的通道。
- 架构位置:D5 明确废弃。网站不再主动派活;网站通过暴露 MCP server 被用户调用(出站、无隧道)。
- 定义:用户侧 OpenClacky 的官方扩展,id/名称统一为
cgc-2046;安装后提供 CGC 助手、本地 Skill 同步、MCP 连接读取等能力(D14)。负责自动配置 mcp.json——安装后自动检查并安装 MCP(网站只生成 token,用户不再手动粘贴配置片段,D-A7)。 - 架构位置:用户侧入口。
openclacky ext install <zip URL>安装(D13 步骤 2 / D-A7);扩展自动写入mcp.json的cgc-2046条目获取 URL + token,零额外配置。
- 定义:连接器扩展静态内置的通用 Agent。用户在 OpenClacky 中说"用教研 Agent"时,助手按任务指令模式拉取 Agent 定义并按定义工作(D10)。
- 架构位置:用户侧执行体,网站侧没有对应资源;它是"公共 Agent 分发"的执行载体。
- 定义:学员电脑上常驻的本机程序(仓库
cgc-host/,由学员本机的 Bun 运行,经 LaunchAgent 随登录自启)。它只监听127.0.0.1,只接受 CGC 站点与已配对浏览器的连接,替课程学习页启动并驱动学员的 OMP(ACP),并在会话里把学习凭证交给 OMP。 - 架构位置:ADR-0022。课程学习页对话栏与学员 agent 之间的唯一通道;不保存对话记录,会话历史由 OMP 自己保存。
- 定义:全局账号,一人多 Workspace。可带
is_platform_admin平台管理员标记(可多人)。 - 架构位置:全局资源(不按租户隔离),认证经
ash_authentication(Password 策略 + TokenResource)。
- 定义:用户的一种认证方式(provider),与 User 多对一。
- 架构位置:全局资源,登录态来源。
- Web 登录边界(ADR-0019 R3,2026-09-30 已接受):普通 Web 注册/登录主流程统一为微信快捷入口(小程序确认);已有账号保留手机号/邮箱+密码登录,仅存量保留;普通短信登录/注册 UI 已移除且不恢复邮箱注册(无微信新用户不能通过 Web 主流程注册);旧 OAuth 会话与后端 API、设置手机号换绑、闪念间短信用途仍视为线上契约(不可随登录主流程改动回收)。
- 定义:网站 UI 登录用的短期 JWT,存 httpOnly cookie;与 MCP 连接 token 是两种不同的 token(勿混)。
- 架构位置:全局资源,仅用于网站自身 GraphQL API 的认证。
- 定义:组织单元(如"北京 CGC 分会"),也是租户单元。UUID 主键 + 全局唯一 slug(展示用),带加入策略
join_policy(open | request | invite_only)。由平台管理员创建(申请审批 + 主动创建两级,D-A3)并指定 Owner,普通用户不可自助创建。一个 Workspace = 一个 Jido partition(D-A5)。:createafter_action 同事务 seed 五角色(Role.role_descriptions/0单源)+ 三份协议定义(curriculum / learning / course_preparation,published——#348:新租户教研链与学习 run 即刻可用;形状单源对齐 seeds.exs §3,任一失败整体回滚不留半 seeded workspace)。 - 架构位置:全局资源;所有租户资源以
workspace_id列过滤(Ash attribute 多租户策略);WorkflowRun 等 workflow 实体归属其 partition。
- 定义:Workspace 的三种加入方式:
open(公开可发现、直接加入、无需审批)、request(公开可发现、申请后审批)、invite_only(私密、仅邀请链接可加入)。 - 架构位置:Workspace 属性,Owner 可设置;驱动 Invitation / JoinRequest 行为。
- 定义:1 条/人/租户的成员记录(user_id + workspace_id + 加入时间 + 邀请人)。成员资格本身即成员身份;无差异标签的普通成员只有 membership、无 MembershipRole。
- 架构位置:成员身份的事实来源;授权链第 2 步"定位身份"读它。成员基准能力(
view_workspace/access_invite_only)由任意 membership 授予,不再经member角色判定。
- 定义:membership ↔ Role 的 N:M 关联表。一人多角色由此表达。
- 架构位置:权限判定时"所有角色权限取并集"的数据基础。
- 定义:「actor ↔ 工作台」成员资格**读取与入座(写入)**面的唯一归属:
- 读取面:
membership_of/2、role_names/2(角色名多角色并集,Rbac 同名委托)、memberships_of_actor/1(跨租户)、owner_count/1、resolve_workspace_id/1(policy 场景的 filter/changeset 目标工作台解析,含 Ash filter struct 提取钉测)。 - 入座写入面:
admit_member/3(加入工作台不变量的唯一实现)——承担「existing 守卫 → 建 Membership → 按角色名入座 MembershipRole → 并发 unique 处理 → 真 DB 故障上抛」全流程;Invitation.accept / JoinRequest.approve / Workspace.join 三处 after_action 的入座段委托此处(2026-08-05 入座收敛,消除三处同构拷贝)。opts::on_conflict(:business_errorInvitation/JoinRequest 转「已是成员」业务错误|:idempotentWorkspace.join 幂等成功)、:error_message(文案随调用方视角:Invitation 对受邀人「你」、JoinRequest 对审批方「该用户」)。事务无关纯函数——事务边界由调用方 action 控制(:join的transaction?: false孤儿 membership 风险已知,升级路径单独决策)。
- 读取面:
- 旁路读取面(BypassReads):聚合读逃生舱——
member_count/1/owner_count/1(GROUP BY 聚合直读)+ 平铺展示字段(WorkspaceMembership.user_email/user_display_nameLEFT JOIN)契约归类;非全库唯一原始 SQL 出口(#217 契约改写,2026-08-20):resolver 旁路读取 14 处与原始 SQL 六类分布的中央契约见该模块 moduledoc。安全契约成文:主查询仍受 policy 门控、旁路仅限聚合与平铺展示字段、新读路径先查此处不发明新逃生舱(2026-08-02 ③ 逃生舱收敛)。 - WorkspaceShell(工作区管理壳):前端展示层 seam(2026-08-02 ⑤ 壳收敛;#79 IA)——sidebar/退出登录/未认证壳/共享 Icon 集的唯一实现,interface
slug + children(requireWs供 profile 家族关闭 ws 解析);members/permissions/profile/portfolio 四页退化为纯内容,加导航项不再改 4 个页面;导航激活态由 pathname 派生;管理项按myAbilities过滤(成员与角色=list_members、加入策略=update_join_policy,B-3 审批/邀请占位同组),普通成员仅见 概览/个人资料;profile 家族(requireWs=false)ws 不解析 → 能力为空 → 管理项恒隐藏(保守方向,页面级门控不变,后端 policy 权威拦截)。 - 架构位置:Rbac(判定)与 WorkspaceActorIsOwnerOrAdmin policy / CurrentMembershipInfo 计算字段(读取方)之间的数据 seam(2026-08-02 ② 成员资格读取收敛):读取形状唯一实现,Ash 升级只炸本模块一处;判定语义仍在 Rbac。入座写入面(
admit_member/3)收敛三处加入流程的建 Membership + 角色入座 + unique 处理同构拷贝(2026-08-05 入座收敛,近 10 次提交反复修同类 bug 的 locality 缺失被根除)。
- 定义:租户内差异标签实体,权限挂在角色上。当前为静态五枚举(plan 017 / ADR-0006,退役
member):owner / admin / tutor / volunteer / learner。成员资格 = 存在WorkspaceMembership;Role 只表达差异(管理、教学、志愿、学员)。learner仍有真实语义(学习 run 的 StepRole / enrolled_learner)。「自定义角色」为未来能力(#71):触发条件 = 真实工作区角色差异化需求,届时增量落地 permissionMatrix 租户查询 + Role 能力配置。 - 架构位置:RBAC 核心;Step/Agent 上存
role_id引用。
- 定义:全局标记(
is_platform_admin,非租户角色),可多人;负责创建 Workspace 并指定 Owner。 - 不变量:系统必须维持 ≥1 名平台管理员;降级最后一名管理员被拒绝(不变量由
User :demote_platform_adminaction 守卫)。 - 架构位置:User 上的布尔标记,跨租户生效。判定唯一真源 =
Cgc2046.Accounts.Policies.PlatformAdmin(2026-08-12 named-check 收敛):Ash check(match?/3)+ 纯谓词platform_admin?/1两个 surface,policy 字面量与 plug/live/graphql ad-hoc 判定全部收口。双面契约:policy 面放行跨租户治理读(含成员列表,load-bearing,实 bug7f925b7);能力面(Rbac abilities / myAbilities)不给非成员管理员管理类 ability(#66 P2)——两面刻意不同答,契约成文于该 module moduledoc。MCP 治理面(role-agent-journeys-v2 S2,R12–R16):admin_前缀十工具(读 4 + 确认流写 6)经显式membership: :platform_admin门控族开放(Wrapper 委托上述唯一真源判定,fail-closed,无工作台作用域);member-only 门「无 admin 豁免」规则不变——非成员平台管理员调 member 工具仍 Forbidden。治理读面只暴露操作元数据:admin_list_audit_logs三源投影结构性不读 params/metadata 列(不靠查询后裁剪)。
- 定义:每用户一个的 MCP 认证凭证。绑用户、不绑工作区;可访问用户加入的多个 Workspace,具体租户由每次调用的目标资源(workspace_id)判定(D6/D13)。在网站「MCP」页生成。
- 架构位置:MCP server 的
Authorization: Bearer <token>头;终端接入时由mcp.json配置。区别于网站登录 Token:登录 Token 是 httpOnly cookie,只用于网站 UI。 - 类型(ADR-0022):
kind=terminal(用户自建,权限随其角色)或learning_host(配对签发给 CGC 本机 Host,见「学习凭证」)。 - 生命周期:滚动过期(#211 裁决,2026-08-18)——连续 90 天未使用即失效(
last_used_at/inserted_at距今 ≥ 90d 时validate_token拒绝);正常使用不断、无需重签。仅手动撤销 + 每用户 active 上限 10 枚;无固定 TTL(与 D-A7 零配置接入冲突:静默到期会让 agent 断连且引导链路长,泄漏窗口收敛与滚动过期相当)。参考实现Cgc2046.Mcp.Token。 ⚠️ 注意:本术语取代更早调研中"token 绑 workspace_id"的旧设计(调研文档已归档)——D13 定稿为绑用户不绑工作区。
- 定义:配对时由已登录的课程学习页签发、交给 CGC 本机 Host 的连接 token(
kind = learning_host)。平台侧只放行学习循环所需的 5 个工具(精确名单钉死在test/support/mcp_tool_tiers.ex),由Wrapper.run/4强制;使用独立前缀与域分隔哈希,旧版代码校验不了它。 - 架构位置:ADR-0022。与终端用的
terminal凭证同表管理,遵守同一套撤销、10 枚上限与 90 天闲置过期规则;审计按token_kind区分两类凭证的调用。
- 定义:持有一枚有效学习凭证的 CGC 本机 Host。学员在课程学习页对话栏的「已配对的电脑」里查看并撤销;一台 Host 同一时间只为一个账号、一个浏览器工作,换账号或换浏览器需要重新配对。
- 架构位置:平台侧即
myMcpTokens里kind = learning_host的有效凭证;本机侧是 Host 状态文件里的配对记录。
- 定义:无状态的租户作用域。除豁免族外,所有 MCP 工具必填
workspace_id:meta: %{workspace_id: :optional}声明的工具(confirm_operation / cancel_operation / list_my_workspaces 等 actor 锚定族)、meta: %{membership: :public}的公开浏览工具(list_public_offerings / get_public_offering——跨工作区公开白名单口径,workspace_id 传入也不收窄,KTD3),以及meta: %{membership: :platform_admin}的平台治理工具(admin_ 前缀族——跨租户治理面无工作台作用域,role-agent-journeys-v2 S2)。其余工具每次调用据此鉴权 + 审计;服务端不存"当前工作区"会话状态(D12)。 - meta 载体纪律:
meta:仅存门控事实(workspace_id 必填性 / membership 豁免)——Anubis 会把非 nil meta 序列化进 tools/list 的_meta对 MCP 客户端可见,塞其他用途的键等于向客户端泄漏非门控信息(架构深化 C 遗留约定);可见性分层的scopes:不进 tools/list 序列化,不受此限,也不要塞进meta:。 - 架构位置:决定性事实——OpenClacky 的 MCP client 是 server 级全局长连接(
@clients = {name => Client},进程级共享),服务端存会话状态会跨会话串。因此 scope 必须无状态、每调用判定。
- 定义:用户在对话中"正在处理的工作区",由 CGC 助手维护,而非服务端状态;助手用
get_workspace_context(workspace_id)定向获取上下文(返回角色/可见 Agents/Skills/Steps)。 - 架构位置:对话侧概念,落在 CGC 助手提示词与工具调用参数里。
- 定义:平台自研的授权模型:角色(租户内 Role 实体)× 操作 × 资源;多角色取并集,命中任一角色即放行。
- 架构位置:MCP 调用 = 用户本人身份 + 网站 RBAC 强制(D6)。Agent 权限 = 用户权限,越权被拒。不采用编译期写死的
ash_rbac。 - 工具层角色谓词单源(2026-09-08 架构评审候选①):MCP 工具 authorize 两族判定收编于
Cgc2046.Accounts.Rbac——manage?(actor, workspace_id)(owner/admin 并集)与staff?(actor, workspace_id)(tutor ∪ 管理,课程教研工作面)。此前以私有函数散于 21 个工具文件靠注释互相指认;Curriculum.Prep.manage?/2与Learning.Authorization.staff?/2(候选③自Mcp.Tools.LearnerAuthorization迁域侧;web/MCP 两 seam 消费)保留 interface 改为委托。判定语义真源在 Rbac 不变,MembershipContext 仍为数据 seam。
- 定义:① 独立使用:Agent 自身声明"哪些角色可直接用我"(默认创建者角色 + Admin/Owner);② Workflow 内使用:跟随 Step 的执行角色。两层取并集。
- 架构位置:
AGENT_ROLE(独立使用授权)与STEP_ROLE(Step 执行角色)两张显式关联表,均引用ROLE.id。
- 定义:授权不落在 Workflow 整体,而落在每个 Step:每个 Step 声明"哪些角色可执行本步 + 本步用哪个 Agent"。
- 架构位置:堵死学员误用公共 Agent——学员只能执行自己角色对应的 Step,其他 Step 的 Agent 不可见。
- 定义:MCP 工具只定义"能做什么"(形状);同一个工具对所有人形状相同,能访问哪些数据由租户/角色过滤决定;每一次工具调用都必须落审计。
- 架构位置:D6 总原则,贯穿 MCP 工具集、workspace_id 作用域与 ToolCallLog 审计。
- 定义:平台向消费端(Web UI 与未来 MCP 工具)暴露的权限判定契约:
permissionMatrix(差异标签→能力矩阵,abilities 为通用[{name, allowed}]列表)、myAbilities(当前用户在目标工作台的能力并集,随meWorkspaces下发;任意 membership 即有成员基准view_workspace/access_invite_only)、角色名仅作差异标签展示词汇(徽章/筛选)。消费端不复写权限语义:不做角色名→能力推断、不维护本地矩阵副本;能力/角色词汇唯一真源在后端Role/Rbac单源,经backend/priv/rbac_contract.json契约工件做跨语言 golden-file 守卫(mix cgc2046.gen_rbac_contract再生成)。 - 架构位置:2026-08-02 契约收敛 + 2026-08-15 plan 017 / ADR-0006(member 退役):Web UI 判定全部消费后端下发能力数据;新增角色/能力只改后端单源与前端展示标签(
ROLE_NAMES/PERMISSION_ABILITIES),任何一边漏同步 = CI 红灯;权限映射页按「成员基准 + 差异标签」展示,无 member 独立行。
- 定义:每次 MCP 工具调用(谁/工具/参数/结果/确认/时间)的结构化记录;"无确认不落库"的写操作在确认后落库并审计。
- 架构位置:MCP 审计的事件账本与唯一原始记录——AgentRun 家族的一切聚合视图都是它的投影(见 §4 AgentRun 词条)。二期可接
ash_paper_trail增强。
- 定义:Jido 的多租户隔离单元。一个 Workspace = 一个 partition(D-A5):registry identity / persistence / lineage / telemetry 全部按 partition 隔离;WorkflowRun 归属其 partition。
- 架构位置:workflow 引擎侧的租户边界,与
workspace_id(Ash attribute 多租户)对应。
- 定义:Jido Storage 的 append-only 日志(审计/事件日志)+ Checkpoint(状态快照,断点恢复)+ Introspection(
provenance_chain溯源链 /execution_summary)。审计 context 的数据源(D-A5),不另造轮子。 - 架构位置:workflow 引擎的持久化与审计底座;与网站侧 ToolCallLog 审计互补。
workflow-first(D-A1):WorkflowDefinition(蓝图)+ WorkflowRun(执行实例)为核心模型,引擎选型 Jido(Elixir)生态;构建不做可视化 UI,经 OpenClacky 的构建器 Agent/Skill 产出 DSL 后部署。
- 定义:Runic.Workflow DAG + 元数据(id/name/type/version/输入 schema/节点定义)。教研 workflow 定义一次,被 Event/Course 实例化复用;带版本管理,改定义不影响已开始 run(D-A1/D-A2)。
- 架构位置:租户资源;设计者 = Admin/Owner(平台运维与教研模板),使用者运行实例不改定义;部署权限 Owner/Admin/Tutor。
- 定义:一个运行中的 DAG 实例。状态机
pending → running → waiting(人等信号) → succeeded/failed/cancelled;持输入快照、产物(facts)、signal 日志;归属 partition(D-A1/D-A5)。 - 架构位置:执行实例;人工步骤到达时
waiting挂起(hibernate 落 checkpoint,信号到达 thaw 恢复),不拆多段(human-in-the-loop 为主)。
- 定义:Workflow 的最小授权单元,声明执行角色集合 + 使用的 Agent(
STEP_ROLE关联表授权);按节点类型分四类:- 自动步骤(Jido Action,纯函数 + 副作用)
- 人工步骤(SignalMatch 门控等待外部信号,如报名表单提交、审批)
- 门控/分支
- 子 workflow(嵌套 DAG)
- 架构位置:租户资源;顺序解锁(Step 1 完成才能执行 Step 2);授权语义与旧 Workflow/Step 模型一致。
- 定义:workflow 引擎底层选型(Elixir,D-A1)。核心五件套:Action(纯函数步骤)/ Signal(CloudEvents 消息)/ Directive(副作用指令 Emit/SpawnAgent/Schedule)/ Strategy(执行策略)/ Agent(运行时容器 AgentServer)。
- 架构位置:引擎层;加 jido_runic(DAG 桥)与 ash_jido(业务 Action 桥)组成平台 workflow 底座。
- 定义:跨 context 通信的消息信封,
type字段路由到 action,source/subject/data标准化。workflow 与业务 context 解耦的纽带:workflow 发 Signal → 业务 context 订阅后更新自己的 aggregate;业务反向只发信号、不调引擎。 - 架构位置:D-A1/D-A6。同步写走 Action(强一致,8),衍生副作用走 Signal 异步最终一致(2)——如
enrollment.completed→ 赞助权益更新、通知志愿者、触发学习 workflow。
- 定义:编译期把 Ash Resource 的 action 生成 Jido Action 模块(context 需 domain/actor/tenant);业务实体 ↔ workflow 步骤的桥(D-A1)。
- 架构位置:依赖方向恒为 workflow → 业务 action 接口;workflow 步骤经它同步读/写业务数据(如
create_enrollment)。
- 定义:数据流 DAG 引擎——Step 是
input → output函数,Fact 是数据流单元,lazy 并发求值(分支/并行/扇出);react_until_satisfied/2执行;SignalMatch 按 signal type 前缀门控下游(人工步骤/等待事件原生机制);SignalFact 保留溯源链。官方~> 0.1.0-alpha,v1 锁版本 + 适配层隔离(D-A1)。 - 架构位置:WorkflowDefinition 的底层执行载体。
- 定义:公共 Agent 分发方式——Agent 定义(prompt/skills/授权)存网站,MCP 提供
get_agent_instruction(workspace_id, agent_id)(roadmap:随 Agent 资源落地实现,#211 裁决 1/3;v1 载体 =Cgc2046.Mcp.Playbooks模块常量——四角色版本化 playbook,经get_role_playbook分发,role-agent-journeys-v2 S1,见 §5「角色 Playbook」词条);用户说"用教研 Agent"→ CGC 助手拉取定义 → 按定义工作。公共 Agent 动态创建天然支持(D10)。 - 架构位置:替代"运行时下发文件"(热加载未验证、工程风险高)与"纯静态打包"(不支持动态公共 Agent)两条路。
- 定义:按"一次干活"聚合操作序列的概念(D9:操作摘要、关联对象、操作者,网站自动生成、不做用户侧上报)。实体不落地(#211 裁决 2/3,2026-08-18):原设计的聚合锚先后被架构演进移除——Agent 实体不建模(形态 X / BYO,R2 已删
Step.agent_id)、主链路不再经过 Step(学习/教研锚 user × course,切片 H),"按 Step 聚合"的实体形态已失效。 - 架构位置:审计义务由 ToolCallLog 事件账本承担(D9 的自动记录/防抵赖已兑现);AgentRun 语义 = ToolCallLog 之上的投影,可从流水回填重建,不建写模型。结果账本按 context 就位:LearningRecord(学习)/ ResearchOutput(教研)/ WorkflowRun.facts(Step 链路产物)。
- 重启条件:Agent 资源落地(roadmap:plan 020,
get_agent_instruction切库)或多宿主(OpenClacky / OMP / OpenCode / DSH)会话归因需求成真 → 从 ToolCallLog 投影重建;前置 = 流水补client_name/session_id归因维度(模型可重算、流水维度不记则历史不可回填,#228)。
- 定义:跑在学员自己电脑上、由该学员自己的账号与额度驱动的那一类 agent(BYO,ADR-0001;ADR-0023)。当前两种:OMP(
omp acp)与 Codex(@agentclientprotocol/codex-acp),都由 CGC 本机 Host 经 ACP 启动与驱动(ADR-0022)。 - 架构位置:类别名,不是某个具体 agent 的名字——个体的名字出现在课程页对话栏的选择器里(「由 OMP 驱动」/「由 Codex 驱动」),「本机 agent 对话」指对话栏这个面。平台不运行模型、不代管学员的 agent 登录;学习凭证经 ACP 的
mcpServers下发,平台只按 token 类型收窄权限。 - 扩展方式:在 Host 的注册表(
cgc-host/src/agents/)加一条记录、页面按status.agents自适应——第三个 agent(如 Claude Code)不改路由与页面结构。
- 定义:两种形态——个人 Agent(角色分身,仅本人可见可用)与公共 Agent(Workspace 级,按 Workflow 协作)。在 BYO 架构下,Agent 只是授权与配置登记:
type / allowed_roles / owner+ OpenClacky 配置引用(openclacky_profile / model / system_prompt / skills)(D2)。不包含执行逻辑——执行发生在用户本地 OpenClacky。 - 架构位置:授权/配置登记概念,实体未落地(roadmap:plan 020,与 AgentRun 重启条件同钩子,#211 裁决 2/3);v1 载体 =
Cgc2046.Mcp.Playbooks模块常量(四角色版本化 playbook,经get_role_playbook分发,role-agent-journeys-v2 S1),get_agent_instruction工具随实体落地(#211 裁决 1/3)。
-
私有教研增量(ADR-0012):公开四角色基础仍存模块常量;tutor 追加 release 内
priv/playbooks/tutor.md,仅开发可用CGC_PLAYBOOKS_DIR覆盖。tutor/owner/admin 可读取 tutor playbook,其他写权限与 tutor-only 教研入口不变。有效增量版本追加原始内容 SHA-256 前八位;生产镜像使用公开 SHA 与同一哈希标识。私有 PR 后需手动 Deploy;生产验收状态见运维《私有教研Playbook部署》。plan 020 导入 DB 时退役 checkout 与 deploy key。 -
定义:四角色(
platform_admin/workspace_admin/tutor/learner)工作模式说明书——「如何经工具面完成角色职责」的版本化文本(role-agent-journeys-v2 S1,R2/R6)。载体 =Cgc2046.Mcp.Playbooks模块常量 + 逐角色版本号(APIroles/0/fetch/1/version/1);分发通道 = MCPget_role_playbook(工具层四分支授权:learner 任何已认证用户 / tutor 与 workspace_admin 须持对应工作台角色 / platform_admin 须is_platform_admin全局标记)。playbook 只组织用户已有能力——面板隐藏、Agent 提示、本地缓存都不扩大网站 RBAC 权限(R6)。learner/tutor 两角色核心章节逐字吸收自已删除的Learning.AgentInstructions/Curriculum.AgentInstructions死代码模块。 -
架构位置:interface layer 资产,归
mcp/(playbook 是工具面使用说明书,非领域逻辑;Workflows 引擎域不持角色内容);DB-backed Agent 资源落地(roadmap plan 020)时整体替换,不留兼容层。架构评审裁决(2026-09-08):「内容即代码」中间态(prose 抽priv/playbooks/*.md+ 版本号改内容哈希派生)跳过——载体终局是 plan 020 整体 DB 化,中间态会被整体替换,编辑频率(~每月 2 次)不值一次弃置改造;若 playbook 编辑转高频(周级以上)再复议。扩展system_prompt.md静态清单收缩为 7 个跨角色公共工具(list_my_workspaces/get_role_playbook/list_my_tasks/ 公开浏览 2 / 确认流 2),角色专属工具由 playbook 携带——「平台加工具必须手改扩展 system_prompt」的脆点收敛为 playbook 单点维护。
- 定义:网站经 MCP server 暴露的工具面(D7 收窄 + 分层,#211 裁决 1/3,2026-08-18;ADR-0021 分层暴露,#1085,2026-10-03),当前 100 个(事实源 =
test/support/mcp_tool_tiers.ex的互斥四层名单;wrapper_gate_test钉死数量、meta 形状与名单完备)。 - 分层名单(ADR-0021,scope 由 anubis 按
frame.context.auth.scopes判定;scope 由Cgc2046.Mcp.Scopes每请求按 current_user 跨工作台并集重算;未声明 scope = 全员可见,由tool_scopes_test钉死):- 全员可见(34):
approve_prep/cancel_operation/confirm_operation/create_enrollment/create_invitation/discover_offerings/get_course_revision/get_enrollment_summary/get_learning_state/get_my_enrollments/get_order_status/get_prep_status/get_public_initiative/get_public_offering/get_role_playbook/get_step_output/get_workflow/get_workspace_context/list_event_moderators/list_members/list_my_tasks/list_my_workspaces/list_public_initiatives/list_public_offerings/list_recruitment_cohorts/list_workspace_courses/list_workspace_events/override_prep_gate/request_changes_prep/save_step_output/start_learning_run/submit_learning_attempt/submit_prep_for_check/submit_prep_quality_report。其中 5 个「实际授权比描述宽」的工具(create_invitation 志愿者可发邀请、list_event_moderators 主理人可读、approve/override/request_changes 未指定 reviewer 时任何成员)仍在全员可见面,收窄授权另案跟踪(#1084);另含 2 个 prep 提交工具(授权 = 被指派 tutor ∨ Owner/Admin,被指派者可能失去 tutor 角色,#1085 T3)。 - tutor(4,工具层硬门 =
Rbac.staff?,tutor ∪ Owner/Admin):claim_prep_authoring/get_course_content/get_course_learning_analytics/save_course_content。 - workspace_admin(34,工具层硬门 =
Rbac.manage?,Owner/Admin;delete_course/delete_event 为 Owner ∪ 平台管理员):approve_join_request/assign_event_moderator/assign_prep_tutor/assign_roles/batch_create_events/cancel_course/cancel_event/close_course/close_event/close_recruitment_cohort/confirm_enrollment/create_course/create_event/create_recruitment_cohort/delete_course/delete_event/launch_course/launch_event/list_attendances/list_enrollments/list_join_requests/list_workspace_orders/open_recruitment_cohort/preview_initiative_mount/refund_order/reject_enrollment/remove_event_moderator/retry_refund/update_course/update_event/update_join_policy/update_prep_policy/update_recruitment_cohort/waive_payment。 - platform_admin(28,
is_platform_admin全局标记,无工作台作用域;含 initiative 在线治理、flashback 外联、愿望治理、发布/收购统计、退押金计数):admin_approve_workspace_application/admin_cancel_initiative/admin_close_initiative/admin_create_initiative/admin_create_workspace/admin_demote_user/admin_get_initiative/admin_get_wish/admin_list_audit_logs/admin_list_initiatives/admin_list_reconciliation_findings/admin_list_users/admin_list_wishes/admin_list_workspace_applications/admin_list_workspaces/admin_open_initiative/admin_promote_user/admin_reassign_workspace_owner/admin_reject_workspace_application/admin_resend_flashback_outreach/admin_send_flashback_outreach/admin_soft_delete_wish/admin_soft_delete_wish_comment/admin_update_initiative/admin_upsert_initiative_rule/list_acquisition_stats/list_flashback_stats/unforfeit_order。
- 全员可见(34):
- 已死亡(标注取代后除名):
reply_learner_question(被 issue 卡 checklist 复盘 +save_learning_records取代,切片 H);get_learner_history(被get_learning_records取代);get_learning_records/save_learning_records(ADR-0011 S8 随 LearningRecord 退役删除,2026-08-30/09-07)。 - 架构位置:B 通道能力面;鉴权立场随工具走(工具自身 meta 声明 + Wrapper 派生门控,fail-closed 默认:未声明 = member-only + workspace_id 必填),每次调用鉴权 + 审计。删除类等低频管理操作维持 web 面(GraphQL + 设置页),真实 agent-first 需求出现时按「确认流 + RBAC 兜底」范式增量重开(S3 已以此范式重开课程生命周期/报名/订单/加入策略 13 工具;#676 draft 删除即该范式的又一次收窄重开——不可逆操作只给 Owner ∪ 平台管理员,admin 不放行,见 ADR-0015)。
- 定义:高风险 MCP 工具的两阶段提交(D8;实现为 two-tool 模式,D-D3——目标客户端均不支持 elicitation):
- Agent 调高风险工具 → 网站不落库,建 pending 记录 → 返回
needs_confirmation: {id, 摘要} - Agent 向用户展示摘要并征得同意(OpenClacky 语境经
ask_user原语弹可点击卡片) - 用户同意 → Agent 调
confirm_operation(id)→ 网站落库 + 审计;拒绝 →cancel_operation(id)(pending TTL 10 分钟) - 网站永远不偷偷执行:无 confirm 不落库
- Agent 调高风险工具 → 网站不落库,建 pending 记录 → 返回
- 架构位置:管理类写操作的安全闸门;已知风险:auto_approve 10s 倒计时自动决策(扩展 prompt 层防线已落地:ask_user 结果出现 auto_reply(无人值守)一律 cancel_operation,无人确认绝不执行)。
- 定义:确认流中"待确认"的高风险操作暂存,不落业务库(业务数据未写入),仅存确认所需信息。
- 架构位置:确认流的中间态;
confirm(id)前业务状态不变。
- 定义:网站对高风险工具调用的响应结构:
{id, 摘要},指示 Agent 进入确认流。 - 架构位置:MCP 协议层响应类型。
- 定义:OpenClacky 内置的提问原语(D8 方案二;gem 1.5.11 起由
request_user_feedback改名,request_user_feedback保留为退役别名);确认流用它把可点击卡片弹到 WebUI。 - 架构位置:OpenClacky 侧能力,非网站实现。
- 定义:确认流第 3 步——Agent 携带 pending id 调用内置工具
confirm_operation(id),网站才落库并审计;拒绝则cancel_operation(id)。 - 架构位置:确认流高风险工具族的配套动作;"无 confirm 不落库"的落点。
- 定义:OpenClacky 的预设工作流(SKILL.md + scripts/references),带触发描述、工具约束、隔离等 frontmatter。分三级来源:项目级
.clacky/skills/→ 用户级~/.clacky/skills/→ 内置。 - 架构位置:用户侧能力单元;工作区 Skill 定义存网站,经连接器同步到本地(D11)。
- 定义:把工作区 Skill 定义从网站同步到用户本地
~/.clacky/skills/的机制(D11):扩展 API handler 经@http_server访问@skill_loader(与 session_manager 同模式),调SkillLoader#create_skill / delete_skill运行时写文件 + 立即注册(源码验证可行)。 - 架构位置:连接器扩展 cgc-2046 的能力;触发方式:扩展静态打包
cgc-syncskill / 网站 aside 面板按钮。
- 定义:本地同步技能的命名前缀,含工作区 slug 与技能名,防多工作区撞名(D12/D14)。
- 架构位置:本地技能命名空间;网站生成 Agent 指令时引用带前缀的本地技能名;MCP server 条目名与扩展 id 统一为
cgc-2046。
- 定义:工作区 Skill 同步时,网站侧先按该用户角色过滤可见技能——扩展只拉到该用户角色可见的技能(D11)。
- 架构位置:同步链路的第一道闸门,安全边界在网站侧。
- 定义:用户接入平台的引导流程(D13 简化,D-A7):
- 装 OpenClacky(网站给安装命令)
- 安装连接器扩展:
openclacky ext install <zip URL> - 扩展自动检查并安装 MCP——扩展自动配置
mcp.json,网站只生成 token(绑用户不绑工作区)
- 架构位置:新用户接入路径;加入新工作区无需重新配置(token 通用,scope 靠 workspace_id)。
- 定义:OpenClacky 的 MCP 配置文件;由连接器扩展自动写入 cgc-2046 条目(URL + token),用户不再手动粘贴配置片段(D-A7,取代 D13 步骤 2 的手动粘贴)。
- 架构位置:用户侧连接配置唯一事实源。扩展自动检查并安装 MCP,网站只负责生成 token。
- ADR-0022 起:经 CGC 本机 Host 的网页对话不读 mcp.json——Host 在 ACP 会话里下发 MCP 服务器与学习凭证;mcp.json 仍是终端接入的配置点。
- 定义:BYO 架构下的已知限制:用户本地 OpenClacky 离线时,该用户的 Agent 任务不可用(D3,接受)。
- 架构位置:产品约束而非缺陷;平台不持有执行能力。
- 定义:加入 Workspace 的链接:token(存 hash)、expires_at、邀请人、预授权角色、目标邮箱(空 = 公开链接)、状态(active/used/revoked/expired)。审计字段:accepted_by(接受人)、accepted_at(接受时间)。Volunteer 可生成链接但不得赋予 Admin 级角色。
- 架构位置:租户资源;invite_only 空间唯一入口;撤销流程与到期清理已实现(惰性过期检查)。
- 定义:request 空间下的加入申请:申请人、Workspace、状态(pending/approved/rejected/expired);审批通过时分配角色。审计字段:approved_by(审批人)、approved_at(审批时间)、rejection_reason(拒绝原因)、approval_deadline(审批截止时间,默认创建后 7 天)、expired_at(过期时间)。
- 架构位置:租户资源;审批动作属于高风险管理工具,走确认流。过期转换采用惰性检查(读取时若 pending 且 deadline 已过 → 转 expired)。
- 定义:头像、简介、技能标签(含 Portfolio 作品展示)per-workspace(ADR-0004,2026-08-08 落地);
display_name/email为全局身份字段(不属于 Profile)。主题偏好(ui_theme_preference)同挂 per-workspace。 - 架构位置:租户资源(WorkspaceProfile,workspace_id + user_id 唯一);PortfolioItem 同属租户维度(加 workspace_id)。visibility=:workspace 语义 = 目标 workspace 成员可读(收窄自"任一 workspace")。
- 默认归属:默认 workspace "2046"(slug=
2046,open 策略)——新用户注册自动加入(无差异标签 membership),保证注册即有 profile 编辑上下文;全局/settings/account/profile入口已下线,redirect 到/w/2046/settings/account/profile。 - 二期需要聚合展示时再拆。
- 定义:平台级(跨工作台)的倡导活动;Event 可挂载到它,挂载时按四项规则(
deposit/age_gate/min_participants/deadline_rule)写入 Event 字段——locked: true的规则强制写入且此后本地改写被拒(#587 锁死传播 / #597 押金⇒档位不变量为其守卫),locked: false的规则只在挂载那一刻按当时取值快照。挂载 / 解除挂载的唯一入口 =Initiatives.RuleInheritance.prepare_event_changes/2(Eventcreate/update的 before_action,与 Event 写入同一事务);仅 draft 可挂载或变更挂载。 - 解除挂载语义(#624 方案 C:保留但不锁 + 来源标记):detach(
initiative_id→ nil)不回收平台锁死规则强制写入的值——值留在 Event 上、回归普通可编辑字段(deposit规则写的deposit_enabled+deposit_amount_cents一并保留);同时把「这些值来自哪个 Initiative 的哪条锁死规则」落进events.detached_rule_provenance(可空 jsonb,形状%{"initiative" => %{id,name,slug}, "fields" => %{<event_field> => %{value, source: "locked"}}},与 #596 写响应applied同源)。只标记此刻仍 locked 的规则字段(未锁默认项是挂载瞬间快照、之后场主可能已自改,标它 = 噪音);无 locked 字段 → nil。 - 标记的清除(首改即清):场主显式改写标记内某字段(web / MCP
update_event;判据 =changing_attribute?且值确有变化——同值回传不算「场主的决定」)→ 只删该键;键空 → 整列 nil;改写未标记字段(标题 / 时间等)不动标记。重挂载(nil → 非空)→ 整列清空(值重新归新 Initiative 治理,旧标记是撒谎)并按既有merge_event_value/4语义覆盖旧强制值。规则传播只写「挂载中的非终态场」(WHERE initiative_id = ...),标记非 nil 的场必已 detach——两条路径在initiative_id上互斥。 - 读面:治理细节,不进公开面——GraphQL
Event.detachedRuleProvenance(JsonString)经 field_policy 对匿名收窄(同 capacity/confirmed_count,公开宿主页恒 null);web 编辑页据此渲染「来自已解除的倡导活动《name》」逐字段标记,并提供minAge/minParticipants输入(挂载中锁定规则、以及挂载预览——新规则保存时覆盖——下禁用;未改动不下发,避免分钟级重序列化截断秒并误清标记)。规则锁态读面仍是 Owner/Admin 专属initiativeMountPreview(#596)。MCP 读面(#630):create_event/update_event写响应与list_workspace_events行投影恒带detached_rule_provenance(无标记null);公开 MCP 工具(list_public_offerings/get_public_offering/discover_offerings)为显式 DTO 投影,不含该键。 - 架构位置:
Cgc2046.Initiatives.RuleInheritance(唯一挂载边界);标记列在events表(writable?: false,客户端不可直设;filterable/sortable 关闭)。 - 跨工作台挂载边界(ADR-0013,2026-09-16 已接受):挂载是引用、不迁移所有权;被挂 Event 的授权归属原 workspace;跨工作台挂载须双向确认。三规则均为治理愿景、当前并无代码载体——当前实现同工作台挂载零校验即过,跨台挂载场景无入口(详见 ADR-0013);触发条件 = 第一个真实跨台挂载请求出现,届时与治理流程(审批链/撤挂/仲裁)一并落地。跨工作台挂载治理与 HQ 跨台报表两件下游能力当前均不立项:治理再立项条件 = 第一个真实跨台挂载请求;HQ 报表再立项条件 = 真实组织需求且前置交付物 = 一页指标口径定义。
- 定义:挂在 Workspace 下的活动与课程(结构决策,D-A3):Event 为场地形态(校园 / 咖啡厅 / 书店 / 联合办公空间),Course 为线上课程。事件级参与经 Enrollment(见下),不自动成为 Workspace 成员。
- 架构位置:租户资源(挂 Workspace);由 Owner 创建/编辑(单步 CRUD 用表单);筹备活动/开课程 = 跨角色 workflow;课程内容 = issue 卡集(见 Issue 词条,2026-08-16)。
- 解除挂载来源标记(#624):
detached_rule_provenance(可空 jsonb,writable?: false)只描述「已解除挂载后仍留在场上的强制值」——detach 保留值 + 逐字段来源、场主首改即清、重挂载整列清空,语义与读面见「Initiative(倡导活动,平台级)与挂载边界」词条;MCP 面(#630)写响应 /list_workspace_events行带出、公开 DTO 不含。 - 租户收紧读取端口(2026-09-08 架构评审候选①):MCP 工具层的课程存在性读取唯一入口 =
Course.fetch_scoped(workspace_id, course_id, opts \\ [])(取代 18 份工具内私有 fetch_course 拷贝)。不变量 = 必带tenant:(Course 全局资源,不带 tenant 全表读即跨租户越权面;他租户 id ≡ not found 不泄存在性)。两变体语义逐工具保真:默认authorize?: false(授权已在工具层发生);actor: actor走授权读(lifecycle 工具原样,Forbidden 映 forbidden 文案)。错误字符串是 interface 的一部分。 - slug 公开 URL 段不可变(ADR-0014):draft 随便改,发布(非 draft)后锁死,无 rename 后门(恢复 = 新建);锁定/撞 slug 拒绝带稳定 code
event_slug_locked/event_slug_taken/course_slug_*(#619,与 Initiative #588/#604 三资源同口径;格式校验资源级单源,锁定优先于格式错)。
- 定义:Course 的「当前标题是系统生成的临时占位」标记(role-agent-journeys-v2 S3,R21/AE1):
create_course缺省 title 时生成未命名课程 <hex8>并置 true;update_course设置正式标题即自动清回 false。命名门:provisional_title = true的课程不能:launch(域 action 层校验 + MCP 工具第一段快速失败双拦截),防「忘了起名就发布」。匿名可见性排除在 field_policy denylist(占位标题不出公开面)。 - 架构位置:Course 属性(writable? false,只能经 create/update 的 change 间接翻转);MCP 侧由
create_course/update_course/launch_course三工具承载语义。
- 定义:学员从发现到进入学习的完整通道(role-agent-journeys-v2 S7,R30-R35)。发现 =
discover_offerings(无参跨台合并流:全平台公开 ∪ 本人各 workspace 可访问,按 {kind,id} 去重、封顶 100 + total_count 截断前小计、条目带 workspace 块/pricing 概要/my_enrollment;invite_only 工作台对非成员 workspace 块落 nil——不泄露不可发现工作台)。报名 =get_enrollment_summary(摘要 +would_create_status委托域谓词单源Enrollment.auto_confirm_status/1(其判定落到Offering.payment_mode/1):open+免费→confirmed / open+收费或押金→payment_pending / request→pending / invite_only→nil;驱动因子 = offering 的 enrollment_policy 而非 workspace join_policy,#586)→ 客户端确认 →create_enrollment(唯一直接写工具——确认契约在客户端 R31,不进确认流;幂等重放:撞enrollment_duplicate_active返回既有活跃报名 +idempotent_replay: true非错误;reason 进 Wrapper 前摘除——审计不落自由文本)。支付 = payment_pending 附checkout_url(web 下单页/orders/new?enrollmentId=,外部浏览器完成)+get_order_status(本人最新订单白名单摘要——id/amount/provider/status/expires_at/paid_at,渠道凭据/单号/回调原文永不出面)。学习入口 =get_my_enrollments(全状态跨台封顶 100;扩展课程面板列表源——confirmed 课程即学习入口,零学习记录也显示)。 - 架构位置:
mcp/tools/learner_journey.ex(共享辅助:checkout_url/parse_required_kind;活跃报名读取真源 active_statuses/active_enrollment(s) 已收编至Admission.Enrollment公共函数,#355 P1-3 起 MCP 五工具与 GraphQLmyEnrollment查询共用)+ 五工具;两读面(discover/my_enrollments)optional+deferred双键跨台锚定,三面(summary/create/order)deferredworkspace 必填;Admission.Enrollment :create_enrollment域 action 单源(web/小程序/工具同语义);Course.published_content/2(显式 workspace_id 版,S7 增设——actor policy 读出的 workspace_id 列对非成员是 ForbiddenField)。扩展侧api/learner_routes.rb五 loopback 路由 + 发现面板 v2(报名确认卡/支付轮询 5s·10min cap/去支付外链)。 - 押金段(押金制,event-deposit R1–R11,2026-09-14 交付;ADR-0007 补记):缴费三态互斥(免费 / 定价档位 / 押金,同一缴费槽;押金不借用定价档位)。付费 = 押金场提交报名后落
payment_pending;学员在下单(createOrder)时才生成唯一活跃押金单(order_kind = :deposit,金额以活动现值为权威、订单一旦创建即钉死在tier_snapshot——不随改价追溯已创建订单,#749)→ 支付完成落confirmed并得 6 位核销码(仅本人可见、payment_pending不出示;扫码失败可手输补救)。到场 = 主理人(Event 主理人 / Owner·Admin / 平台管理员;主理人须为本工作台成员——#558/#542 决策 A1,指派时校验,KD7「不检查、不邀请」已修订)扫码或输码核销(checkInEnrollment):Attendance落行(每报名至多一行,到场事实)→ 同事务发起全额退款(核销即退),参与者当场收「押金已退」通知;退款完成后报名保持confirmed、名额不释放。未到场 = 活动结束(ends_at)T+48h 后仍未核销的paid押金单由DepositForfeitWorker结算为订单终态forfeited——押金不退、留作平台收入(workspace_payment_stats可导出)。取消 = 截止前自助取消全额退并释放名额,截止后取消只释放名额、押金不退;活动取消 / 未达成班无条件全额退优先于核销即退与 no-show。展示口径 = 「免费 / 收费 ¥xx / 押金 ¥xx(到场退)」三态,不再「免费」与「押金」并列。Agent 面同口径(#586):缴费槽三态判定单源 =Offering.payment_mode/1(:free | :pricing | :deposit,押金优先),落点预测单源 =Enrollment.auto_confirm_status/1(非免费 →payment_pending);MCP 读面discover_offerings/get_enrollment_summary/get_public_offering/list_workspace_events条目附payment_mode与押金明细deposit(enabled/amount_cents/refundable_on_check_in)——pricing.enabled = false不等于免费(押金场即如此),押金金额缺失(历史脏行)时amount_cents落null、enabled仍为true,绝不显示 ¥0 也绝不改判免费。
- 定义:Event 的「本活动不使用教研链路」退出通道(轻聚会等形态),默认 true。Course 无此开关(2026-08-16 grill Q12 语义分家):issue 卡是课程内容本体,Course 恒走教研实例化,
courses.curriculum_enabled列已删除——对账规则④对 Course 无条件(draft/open 且无 published 教研定义 = 孤儿;UAT P2 扩 draft——prep run 始于 course.created,断流在 draft 期就该看见,Event 侧教研 run 始于 launched 故不扩),Readiness 教研项对 Course 无条件检查。 - 架构位置:Event 属性;
Curriculum.Instantiator.ensure_curriculum_enabled门控为 event-only 分支(按 key 前缀分叉);对账规则④仅 Event 侧保留 curriculum_enabled 过滤。Event 侧 UI 暴露留待真实需求。
-
命名说明:offering = 商业英语「提供物/供给物」,Event/Course 的上位词(course offerings = 开设的课程清单,非「课程设置」)。中文统一语言定名供给物;ADR-0009 拍板代码标识符保留
Offering不改名。 -
定义:「一行可指向 Event 或 Course」的统一读取 seam =
Cgc2046.Offering(2026-08-15 读取面收敛,架构评审候选④)。interface:fetch(kind, id, opts \\ [])({:ok, entity} | {:error, :not_found},默认authorize?: false;actor:+authorize?: true为 graphql 场景的 actor 感知读取;返回完整 entity 供 status/Readiness 消费)|fetch_by_signal_payload(data)(按event_id/course_id键分派)|fetch_titles_by_ids(ids_by_kind, tenant)(per-kind per-tenant 批量,消 N+1 不退化)|投影kind/1/title/1/workspace_id/1。错误形状统一坍缩:not_found单点。 -
命名空间区分:kind 原子
:event与 Sponsorshiplevel: :event(赞助级别)撞名但无语义关系——前者是读取分派键,后者是业务分类字段,勿混用。 -
架构位置:读取面 seam(已迁出 events/,位于 lib/cgc_2046/ 顶层 offering.ex);消费方 = Notifications.Subscriber / LearningInstantiator / PendingApprovals / GraphqlSchema(offeringReadiness)/ Curriculum.Instantiator;不碰 enrollment 裸 SQL 家族、Event/Course lifecycle change、sponsorship level 分叉。
-
发布语言读端口(ADR-0009 D5,目标态已落地):Events/Courses 对 Admission 的发布语言读端口(纯读投影契约,零写入),位于中立位置 lib/cgc_2046/offering.ex;共享纯函数内核(PriceTier / ScheduleValidation / EnrollmentBadge / Readiness)收进
offering/目录(PR②,KTD2;写原语不在其列——状态机条件 UPDATE 单文件原语Cgc2046.StatusTransition居根部 lib/cgc_2046/status_transition.ex,ApprovalClaim 同款先例,offering/ 自此纯读零写)——纯函数共享不破坏 bounded context:无状态、无表)。消费面不变:Admission 校验、分享深链(target_kind/target_id)、PendingApprovals 标题、通知 target_title、GraphQL offeringReadiness、公开浏览族。
- 定义:Course 内容的原子单元,User-Story 式内容契约:
as_a / given(先修状态)/ goal(目标,Tutor 设定)/ materials(朴素参考列表,无 type 字段——动手卡 ≠ 技能)/ checklist,带kind二分——thoughtwork(知识型,证据在对话)与handwork(动手型,证据在产物,agent 必须实查产物判完成)。id 稳定纪律:issue id 发布后不改不删,内容编辑保 id(attempts 按 objective_id 引用,永远可追溯)。展示短码 issue key(PY-02式,课程短码-序号,派生非存储,单源Curriculum.Content.issue_key/2)。学习状态(S8 起)在 objective 粒度(见「学习评价账本」词条),issue 不再承载学习三态。 - 架构位置:存储于
Curriculum.Output(kind=:issues);教研 Agent 起草(Tutor 经 MCPsave_course_content活文档式更新,run 终态后仍可改);读取经get_course_content。写入一律带base_version乐观锁(纪律见「Curriculum」词条草稿版本段)。取代词汇:section / story 卡 / acceptance(2026-08-16 课程 issue 学习闭环设计;learning_objectives 的职责由 issue 内objectives字段承担,见「课程版本」词条 schema v2 段)。
- 定义:issue 内可自验条目清单(
{id, text},无测试题形态);handwork 条目指向可检查产物。S8(ADR-0011)起 checklist 不再承载学习语义——掌握与评价的唯一粒度是 issue 内objectives(见「学习评价账本」词条);checklist 保留为教研起草期的自验清单(Content.valid_v1?校验保留,v1 内容兼容),学习消费面已随 LearningRecord 退役。 - 架构位置:issue story 字段组。
- 定义:一条不可变的正式评价记录(role-agent-journeys-v2 S8,ADR-0011 L1;R42/R44):锚定三元组
(learning_run_id, course_revision_id, objective_id)+evidence(证据摘要)+rubric_results(逐条{criterion_id, met, note?})+passed+rationale(恒必填)+confidence(0..1)+agent_meta。只有 create + read——失败评价永不删除,重试写新行(无限重试,无 tutor 逐条审核)。掌握状态不落库:由Mastery纯投影派生(qualifying = passed ∧ confidence ≥ 0.8 ∧ rubric 精确覆盖且逐条 met)。语义修订(L3):旧「学习记录(记忆挂人不挂报名)」词条退役——新语义为「账本挂人(attempts 永久保留、跨 run 可审计可回放),掌握态挂 run × revision」;跨 run/跨 enrollment 的掌握延续 = deferred(投影不建表,未来按人×revision 重算即可)。 - 架构位置:
learning/learning_attempt.ex(learning_attempts 表);读面 = run 持有者 ∪ 本台 tutor/owner/admin(平台管理员刻意不放行——证据正文不进平台治理读面,R48);写面 = 仅 run 持有者本人(SimpleCheck fail-closed)。不开 GraphQL 面。审计红线:submit_learning_attempt的 ToolCallLog params 经 per-tool 白名单只留workspace_id/course_id/objective_id/passed/confidence(evidence/rubric_results/rationale/agent_meta 不落审计,AE12)。
- 定义:掌握四态 latest-attempt-driven 纯投影(L2,无表):无 attempt = unassessed;无一 qualifying = developing;最新 qualifying = mastered;曾 qualifying 最新失败 = needs_review(
ever_mastered粘性——完成判定用它,复习失败不倒退 AE10;first_mastered_at锚首条 qualifying)。run 完成判据 = 全部必修 objective ever_mastered(空必修集 → false,§B#14)。NextAction 五级优先(L5,R40):完成守卫先行 → review(S9 起ReviewSchedule.due/3真实队列)→ remediation(developing 的先修中有 needs_review 者)→ developing(最近活动者)→ next_required(内容序首个已解锁必修 unassessed)→ elective。unlocked?= 全部 prereq ever_mastered;锁定项报缺失先修 id+title,submit 工具拒评(R41 不可绕过)。 - 架构位置:
learning/mastery.ex/learning/next_action.ex(纯函数,判据单源)。
- 定义:learning WorkflowRun 的启动/查询/投影/完成判定/停滞口径的单源(L6):instance key =
learning_<enrollment_id>_<revision_id|none>(按 key 命中任意状态即 resume,§B#11——同版重进 = 续学,新版发布 = 新 run);revision 绑定走input_snapshot["course_revision_id"](enrollment 锚同款先例——引擎表零域列零 opt);learning_state/2= MCPget_learning_state与 GraphQLcourseLearningDetail共用投影(含 stale_revision 语义——run 绑旧版时投影自己版本的 objectives+states 不换底);完成判定complete_when_mastered/1(非同事务 §B#12:attempt 落库后即时调用,失败只记日志,5 分钟 worker 兜底);停滞口径 = 最新 attemptcreated_at,零 attempt 回退 runinserted_at(规⑦ detail 键last_activity_at)。 - 架构位置:
learning/runs.ex;Instantiator(enrollment.completed 异步路径)与start_learning_run工具(学员主动路径)共用同一 key,两路径幂等互通(R36)。MCP 面 =start_learning_run/submit_learning_attempt(六步校验链:本人 confirmed → 非终态 run → run 绑 revision → objective 存在且 rubric 精确覆盖 → confidence/evidence/rationale → 先修锁)/get_learning_state(get_learning_records/save_learning_records已随 LearningRecord 退役删除)。
- 定义:间隔重复复习到期队列的纯函数投影(L4,无表,R45):objective 首次掌握(
first_mastered_at)后进入复习队列,里程碑 = 首次掌握起 +1/+7/+30 天(intervals/0),按序消费——第 n 条「掌握后 qualifying attempt」(严格晚于first_mastered_at)满足第 n 个里程碑,前提是晚于上一里程碑锚点(防突击刷档;迟到复习照常按序);失败复习不消费任何里程碑且使 objective 翻needs_review(恒立即到期,due_at = 失败 attempt 时间,全里程碑消费完后再失败 →milestone_days: nil= 「再次合格恢复 mastered」语义);三里程碑全满足后不再到期。due/3时间全部由调用方注入。完成不撤销(AE10):复习失败翻转 needs_review 不影响已产出的 run 完成结果;v1 边界 = 已 succeeded run 无复习提交通道(L4 已知代价,详见 ADR-0011)。 - 架构位置:
learning/review_schedule.ex;消费方 =Runs.learning_state/2(review_queue键)、submit_learning_attempt(next_action 第三参真队列)、GraphQLcourseLearningDetail.reviewQueue;NextAction.next/3review 分支由此接通(review 优先于 developing,R40)。
- 定义:tutor ∪ owner/admin 的课程学习数据回流聚合读面(R49/R50,AE14):
for_course/2(IO)+compute/5(纯函数,now 可注入)两层。数据范围 = 课程全部 learning run(input_snapshot["course_id"]课程级锚定,无 user 过滤)+ 其 attempts + 当前 published revision objectives。输出五键:run_stats(total/active/completed/completion_rate,零 run null)、objectives(当前 revision 逐行:掌握四态按 run 计——仅计绑定 revision 含该 objective 的 run;qualifying 按 attempt 所属 run 绑定版 rubric 判;重试热点avg_attempts_to_first_mastery仅统计曾掌握的 run)、orphan_objectives(当前版已移除 objective 的 attempts 汇总)、drop_off.stale_run_count(Runs.stagnant_cutoff口径同源,内存算)、generated_at。红线(R49):纯聚合计数,永不返回 evidence/rubric_results/rationale 正文。 - 架构位置:
learning/analytics.ex;MCP 面 =get_course_learning_analytics(member-only + 工具层 tutor ∪ manage roles;租户收紧 not found 不泄存在性);tutor playbook「数据回流」章节(R50:分析 → 判断内容问题 → 新 Revision 草稿重进教研链,永不自动改/发布当前 Revision)。
- 定义:学员侧学习的限界上下文(ADR-0010 A3 归位;S8 起为 ADR-0011 Learning v2):
Attempt(不可变评价账本)+Mastery/NextAction(派生投影纯函数族)+Runs(run×revision 投影单源;候选③起并持my_learning_runs/1——myLearningRuns 的 enrollment→run→投影编排自 graphql_schema 抽离归此)+Authorization(学员侧三层授权判定单源:成员 ∪ confirmed enrollment ∪ run 持有者;候选③自Mcp.Tools.LearnerAuthorization迁域侧,斩断 web→mcp/tools 反向 seam,MCP 工具与 webCourses.CourseProjection同为消费面)+LearningInstantiator(订阅 enrollment.completed 种 learning run;SignalSubscriber,consumer_keylearning_instantiator钉死;S8 起 key 含 revision、course 报名绑Course.current_revision_id进 input_snapshot)+RunProjection(GraphQL myLearningRuns 行组装,⑥a 自 graphql_schema 抽离,#217 旁路读取锚链注释随迁;S8 切 objective 口径薄壳Runs.learning_state)+LearningProgressWorker(停滞扫描/完课判定,就地改逻辑不改名——Oban jobs.worker 字符串雷区)。Learning.Progress与LearningRecord已随 S8 删除(issue/checklist 口径投影由 Mastery/Runs 取代)。web 课程读者投影四姿态(公开 course_map / 学员三层 learning_detail+content / staff draft)=Courses.CourseProjection(候选③同批自 graphql_schema 抽离,仿 RunProjection 先例)。 - 架构位置:独立 context(
learning/);消费 Curriculum 已发布内容(revision 经Curriculum.revision_by_id/2读契约),被 GraphQL/MCP 消费。
- 定义:Event/Course 的事件级参与者记录,归Admission(报名)context(ADR-0009,取代 D-A4「归活动 context」):由报名 workflow 同步调
create_enrollmentAction 创建(强一致:名额/唯一性);不自动成为 Workspace 成员。报名轻量表单;免费是默认(Event/Course 不配置定价),收费路径经 Order 缴费(2026-08-15 缴费 grilling 拍板,取代 Learner Q3「全免费」约束)。 - 架构位置:Admission context 资源(现状位于 lib/cgc_2046/admission/,已随 ADR-0009 PR① 迁出 events/);与 WorkspaceMembership(长期成员)两类关系并存。「报名」对 Event/Course 语义逐字相同 = 同一概念,不随两者分家复制。
- 定义:报名生命周期的限界上下文(ADR-0009,2026-08-28 拍板):Enrollment + InviteBatch + 名额账本。Events/Courses 是其上游——经 Offering(供给物)发布语言读契约供 status/capacity/deadline/price_tiers;下游 = Payments(Customer/Supplier,Order 锚
enrollment_id单一引用)。 - 名额账本(D2):占位/释放的原子 CAS 在 Admission 自己的账本表内完成(PR⑤ U6 落地,详见下「名额账本」词条);offering 上的
confirmed_count退化为展示投影(capacity.synced信号最终一致同步,PR⑤ U7)——消除系统三处跨 context 写点(enrollment.ex reserve / release 裸 SQL、order.ex expire 链名额回落、confirmed_count check constraint 耦合;ADR-0009 实施期更正,原称「唯一」)。capacity 调小的同步窗口由账本 CAS 拒单 + 对账规则兜底,不构成超卖。 - 架构位置:独立 context(
admission/,已随 ADR-0009 PR① 落地);Enrollment/InviteBatch 位于 lib/cgc_2046/admission/。
- 定义:名额事实源(
admission_capacity_ledgers表,ADR-0009 D2,PR⑤ U6 落地):每个 offering(Event/Course)唯一一行,持status / capacity / registration_deadline缓存列与权威occupancy / sync_version;占位/释放的原子 CAS 收编于本表(reserve 三守卫:open + 截止未过 + 未满员;release 守卫occupancy > 0),Admission 独占写权。 - 建行与缓存同步(KTD5/R16):三族信号一个动作——
event.launched/course.launched建行(与报名路径懒建 upsert 的竞态由(offering_kind, offering_id)唯一索引幂等吸收)、offering.capacity_changed编辑传播、*.ended回查 status;订阅器Admission.CapacityLedgerSubscriber一律经 Offering 端口回查实体最新值覆盖式同步(:state_based幂等,乱序自收敛)。 - 展示投影回路(KTD4/R15):账本写成功后同事务发布
capacity.synced(权威 occupancy + 单调 sync_version);Events/Courses 各自订阅自写本表confirmed_count列(条件confirmed_count_sync_version < 新版本,覆盖式幂等 + 乱序收敛)——自写不算跨 context 写,反向直写(Admission 写 events/courses 表)禁止。 - 锁序(KTD7):invite_only 报名双 CAS 的锁获取顺序固化——账本行(offering 侧)永远先于 invite_batches 行获取;反向获取禁止(防死锁)。
- 对账兜底(R17):规则⑧ open 无账本行 / ⑨ occupancy ≠ 占位报名计数 / ⑩ 展示投影漂移超一拍 / ⑪ occupancy > capacity,四规则看护收敛(finding 语义见「对账扫描」词条)。
- 架构位置:
Cgc2046.Admission.CapacityLedger(lib/cgc_2046/admission/,不进 GraphQL);CapacityLedgerSubscriber 同目录。
- 定义:跨 context 共享的横切写原语与读模型(ADR-0010 G1-⑦ 命名,2026-08-29 词条化)——
ApprovalClaim(status 条件 UPDATE 原子抢占原语;7 资源 / 15 调用点,代码实测grep -rn "ApprovalClaim.claim" backend/lib:admission/enrollment(cancel_flow + enrollment 体)、sponsorship、accounts/join_request、accounts/workspace_application、accounts/invitation、events/speaker_invitation;详单见「原子抢占」词条)、ApprovalDeadline(审批截止计算口径)、StatusTransition(状态机条件 UPDATE 单文件原语)、PendingApprovals(跨 4 域「待我审批」CQRS 读模型)。 - 架构位置:刻意不归任一 context,根部驻留(lib/cgc_2046/ 顶层)——消费面横跨 Accounts/Admission/Events/Courses/Workflows,归任一域都会把横切原语伪装成域内资产并制造反向依赖;纯函数/裸 SQL 写原语、无自有表(写各调用方聚合的表)。进阶收编为
approvals/目录是开放选项(ADR-0010 G1-⑦),未拍板前维持根部驻留。
- 定义:教研 context 的英文命名(ADR-0009,2026-08-28 拍板)。教研 = 设计课程大纲/材料/学习活动,学科通用名 instructional design;命名取产出物本质(Curriculum = 课程编制)。Research 命名太宽泛退役;Teaching Research 为中式英语不采用。中文文档继续称「教研」。
- 边界:拥有教研产出物(outline/materials/issues/archive 的起草/审核/归档,现
Curriculum.Output家族;发布归档 =Curriculum.CourseRevision,S6)与教研实例化触发;Event/Course 引用其产出,Course 持「哪版内容已发布」投影(current_revision_id),Learning 经读契约消费已发布内容。 - 架构位置:独立 context(
curriculum/,已随 ADR-0009 PR③ 落地):Output(课程内容唯一持久层)/ Instantiator(实例化触发)/ Reaper(run 回收);另含Content(课程内容形状契约 + ContentValidation,ADR-0010 A4 自 workflows/ 迁回)与CurriculumProgressWorker;Curriculum.content_output/2为课程内容 Output(kind=:issues, key=course_)唯一读入口(A4 收敛:原 curriculum/两 worker/MCP/graphql_schema 五处同形查询全归并);发布内容读契约同域(S6):Curriculum.latest_revision/2/revision_by_id/2/revision_by_number/3为 CourseRevision 唯一读入口(消费方 = Course.published_content 投影与 get_course_revision 工具)。教研段 AgentInstructions 已随 role-agent-journeys-v2 S1 删除(内容由Mcp.Playbookstutor playbook 吸收)。research_* 命名已全代码退役(research_enabled → curriculum_enabled 等;信号 payload 键research_requirements与对账规则④⑤原子名为冻结例外,不随改名)。 - 草稿版本(S4,R9/R10,AE2):
Curriculum.Output持version列(bigint,默认 1,writable?: false);save_course_content以base_version为必填写契约(0 = 首次创建),CAS upsert(upsert_condition(version == base_version))成功后atomic_update(:version, version + 1);base_version > 0而无草稿直接version_conflict,冲突响应附最新version供调用方重载重试。双等价入口:MCP 工具 + 扩展面板POST /courses/:course_id/content共用同一工具实现(面板 409 → 弃本地编辑重载,10s 轮询在编辑/保存态挂起)。冲突文案单源Curriculum.Output.version_conflict_message/1(MCP StaleRecord 与无草稿分支共用)。
- 定义:每门新课程恰一个的教研流程实例(role-agent-journeys-v2 S5,R22-R28)——
course.created信号经Curriculum.PrepInstantiator(SignalSubscriber,idempotency: :state_based,consumer_key 钉死"course_prep_instantiator")幂等种出type=:course_preparation的 WorkflowRun(实例 keycourse_prep_<course_id>,非终态去重),并回写course.workflow_run_id。协议而非 DAG(与 learning run 同,TD3):实例化即:start(pending → running),不经 Engine,node_def 空 steps 合法。 - prep_state 五态 facts 状态机:
draft → authoring → quality_check → review → published(外加below_threshold回退 authoring),存于run.facts["prep_state"](缺省 draft);迁移一律经Curriculum.Prep域服务(前置断言「invalid prep_state transition」),facts 写走 WorkflowRun 专用:update_prep_factsaction(optimistic_lock version;成员 bypass,学员不放行)。 - 策略快照(R22):
prep_policy(review_required 默认 true / quality_threshold 默认 80 / reviewer_user_id 默认 nil=任何成员可审)创建时固化进input_snapshot不可变;Owner/Admin 经确认流update_prep_policy写facts["prep_policy_override"](override-first 合并,快照本体不动),进入 quality_check 即冻结。 - 认领 = run version 乐观锁 CAS(R24):tutor 认领未指派的 draft/authoring run,并发双认领恰一成一败(StaleRecord →「already claimed」业务错误),零裸 SQL。
- 结构门禁(R26,S6 加严 schema v2):
Curriculum.PrepGate.check/2纯函数——非临时占位标题 / 内容草稿存在 / goals 非空 / issues 非空 + Content v1 形状复核 + objectives 硬性要求(全课程 ≥1 objective 且 ≥1 必修;逐条规则:id 课程级唯一 / title 非空 / rubric 非空组内唯一 / prereq_ids 存在且 DAG——无环无自引用)。v1-only 旧草稿不能发布,无数据迁移。提交质量检查与发布前各跑一次(发布复跑不过 → 整体回滚)。 - 发布单事务(R23/R28/R29,plan §B#9/#10):
Curriculum.Prep.publish/4(撞 (course_id, number) 唯一索引重读 max 重试一次)单事务 = 防御性复跑门禁 → 创建不可变CourseRevision(number = max+1,草稿快照冻结)→ 调 Courses 发布端口Course.bind_revision_for_publish/3(绑定 current_revision_id + 课程 draft 时:launch——changeset contextvia_prep: true放行教研门,授权已在工具层完成故authorize?: false;已 open 只换绑)→ run:complete(succeeded,facts 记 published_revision_id/number)。教研门:存在非终态 prep run 的课程:launch一律拦截(「课程须完成教研流程后发布」),via_prep 或 prep_state=published 才放行;存量课程(无 prep run)不受影响。发布次周期:run 终态后get_prep_status/submit_prep_for_check经Prep.ensure_active_run/2懒开新 run(默认策略快照,assignee 沿用上任;advisor R1/R2 定稿:懒开与course.created首周期 producer 共用Prep.spawn_under_course_lock/3锁协议——事务内 course 行 FOR UPDATE + 锁内重读最新 status(白名单裁决,stale struct 无效化)+ 锁内重读非终态 run——同课程恰一个非终态 run;仅 draft(producer)/draft+open(懒开)课程建 run,closed/cancelled fail closed 无写副作用;course:close/:cancel同事务经Prep.stop_active_runs/1收口既有非终态 run——terminal 课程不遗留不可发布的孤儿 run)。 - 架构位置:
curriculum/prep.ex(域服务)/prep_gate.ex(纯函数)/prep_instantiator.ex(订阅器,application 注册于 curriculum Instantiator 后);MCP 面 = 九工具(get_prep_status 读 + assign/claim/submit×2/request_changes 直接写 + update_prep_policy/override_prep_gate/approve_prep 确认流),list_my_tasks按角色分派三类教研行(claimable/authoring/review);扩展面板GET /courses/:course_id/prep透出状态。
- 定义:教研流程发布步生成的不可变课程内容快照(role-agent-journeys-v2 S6,R29/R38)——
content是发布时点的完整 schema v2 内容(goals + issues + objectives),(course_id, number)唯一且 per-course 单调递增(发布事务内 max+1,撞唯一索引重读重试一次);资源层只定义 create + read(无 update/destroy——不可变纪律钉死),旧版本与其学习 run 永不被改写。prep_run_id/published_by_id/published_at为溯源审计列。 - schema v2(R38):issue 的
objectives字段——LearningObjective 是掌握单元,携带稳定 id(课程级唯一)、required必修/选修标志(缺省 true;全课程至少一个必修)、机器可读先修prereq_ids(引用存在的 objective 且构成 DAG)、activity / assessment / materials / 非空 rubric(≥1 条{id, text},组内 id 唯一)。形状契约在Curriculum.Content(valid_v1?与objective_violations分层报告);v1-only 旧草稿不能发布(tutor 补 objectives 后重过门禁,无数据迁移)。 - 架构位置:
curriculum/course_revision.ex(表curriculum_course_revisions;不开 GraphQL 面——消费走 MCPget_course_revision工具 + 域读入口Curriculum.latest_revision/2等);Courses.Course.current_revision_id为当前 published 版本投影(唯一写入口:bind_current_revision,发布端口bind_revision_for_publish/3在发布事务内写入——本计划唯一新增跨 context 端口);公开 courseMap 内容源 =Course.published_content/1(读 current_revision 指向版本,无 revision 的存量课程回退草稿读面——旧行为)。授权:资源层成员 ∪ PlatformAdmin;学员侧「仅最新 published 版本」细粒度判定在get_course_revision工具层(deferred 族)。
- 定义:报名 reason 自由文本的同步内容安全拦截(issue #230;advisor09 F1-F3 修订)。提交时 reason 经微信 msgSecCheck v2(
Cgc2046.Integrations.Wechat.Client.content_check/3,宿主 Wechat.Requester 直发POST /wxa/msg_sec_check,body%{content, version: 2, scene: 2, openid}——SDKSecurity.msg_check/2为 v1 已废弃)同步检查——违规内容拒绝提交且不落库(result.suggest为risky/review→ BusinessError codeenrollment_content_rejected);平台瞬时故障(errcode 非 0 含 45009/47001/61010 / 网络错误 / 非 200)fail-open 放行并记 telemetry[:cgc_2046, :content_check, :skipped](metadata 仅类别原子,不含 reason 明文)。 - 范围:v1 wechat-only——tt/xhs 显式 pass-through(各自平台审核独立,Phase 4 接入);检查字段仅
submission_payload.reason;reason 服务端前置校验(F3:必须 binary 且 ≤2500 字节合法 UTF-8,违者直接拒绝——检查产物 = 落库产物,无静默截断)。 - 平台判定:create 时 actor 无 platform(platform claim 在 JWT,User struct 不带),取
user_identities的 wechat uid 作 openid(order.ex 同款 SQL)——有 wechat identity → wechat 检查;无 wechat identity(tt/xhs 单平台 / web 无 identity)/ 查询失败 → pass-through 放行。 - 红线:reason 明文不进日志 / telemetry / BusinessError message;msg_check 请求走宿主 Wechat.Requester(debug: false 既有)。
- 架构位置:
Client.content_check/3(miniprogram facade,v2 错误分类单点)+Enrollment :create_enrollment的 before_action 首位(F2:外呼移至目标校验 / FOR SHARE 行锁获取之前,外呼不持锁);前端文案在miniprogram/src/domain/error-copy.ts(enrollment_content_rejected)。
- 定义:「learning run 锚定到哪条 Enrollment」的唯一读取真源 =
Cgc2046.Admission.Enrollment.anchor/1(+ 双键提取anchored_id/1;2026-08-17 架构深化 E)。三消费方(Workflows→Admission 依赖方向):StepAuthorization.enrolled_learner?(fail-closed→false)/LearningInstantiator(instantiate + ensure_create_guards + instance_key/input_enrollment_id,warning+:ok)/LearningProgressWorker(fetch_enrollment_or_nil→nil / remind_stagnant→:skipped)——三份私有拷贝已收编于此,删则复杂度回散三处。 - 双键超集语义:string 键优先、atom 键兜底(
Map.get(m, "enrollment_id") || Map.get(m, :enrollment_id))——可达输入全为 string 键(input_snapshot 经 JSONB 持久化;唯一写入方 LI 以 string 键构造 input),atom 分支仅激活于不可达的 in-memory 输入(安全方向,fail-closed 不放松)。 - 双错误语义:无锚 →
:no_enrollment_anchor(含 nil 防御 input_snapshot 可空);有锚读取失败/不存在 →:enrollment_read_failed(避开 payments 域同名:enrollment_not_found,防跨域误读)。错误原子零外部消费,仅进日志与 with 通配符。 - 边界不收:reconciliation_scan_worker ×2、graphql ×2(anchored_to_enrollment? SQL filter / 展示投影)、ActorIsEnrolledLearner(委托非拷贝)、payments 域。
- 配套(G):SignalSubscriber 骨架把
:claim_in_handle策略结构化为双回调——before_claim/2(校验链 →{:ok, ctx}|:skip|{:error,_})+effects/3(副作用),claim 时机由骨架持有(before_claim 后、effects 前),不再依赖模块自调;:skip/{:error,_}不烧 claim 归一化:ok(重投仍可推进),重复投递:duplicate→:ok(不重复执行 effects),声明策略但未实现双回调 →raise ArgumentError。历史 post-hoc 检测方案因无法区分「校验不过合法 skip」与「忘调 claim」被证伪,弃用。 - 架构位置:Admission context 资源(admission/)读取面;依赖方向 Workflows→Admission,三消费方坍缩语义各自保持。
- 定义:收费 Event/Course 的嵌入式定价配置(
pricing_enabled: true时的price_tiers字段),形状:id / name / amount_cents / available_until。金额下限 1 分,无 0 元档(免费场景 =pricing_enabled: false整场免费,或管理员免缴个例);available_until到期档位报名时自动隐藏。下单即快照(tier_snapshot + amount_cents),改价/删档不追溯已生成订单。 - 架构位置:Event/Course 属性(先例:
sponsorship_tiers嵌入式无独立表);与 SponsorshipTier 刻意成对但语义对立——本词条是真实收款金额,后者仅登记意向(见 §9)。
- 定义:一笔报名的缴费单,归属 Payments domain(
payments_orders表),资金事实源。持渠道关联键(out_trade_no我方单号 /transaction_id渠道单号)、tier 快照与金额(分)、provider(wechat_jsapi / wechat_native / alipay_page / alipay_wap)、状态机pending → paid → refunding → refunded(refunding → refund_failed渠道拒绝,refund_failed → refunding经 retry_refund 重入;cancelled / expired / refunded 为终态;押金单另有paid → forfeited一次性 CAS 迁入的终态——no-show 结算、不退、留作平台收入,ADR-0007 补记)。不变量:一个 Enrollment 至多一个非终态 Order(部分唯一索引);回调金额必须等于订单金额。退款即取消报名(一般路径):全额退款同时取消 Enrollment 并释放名额(ADR-0007);到场事实(Attendance 行)或免缴留痕在场时退款保留 confirmed 报名、不释放名额(押金核销即退 / 免缴,ADR-0007 补记);订单过期后渠道侧迟到扣款自动原路退回。 - 组织者查询面(organizer-payment U4/U7):Order 计算字段
event_id/course_id(expr(enrollment.event_id),Enrollment 无 GraphQL 对象类型的惯用替代)使workspaceOrders可按活动筛选;workspacePaymentStats收可选eventId/courseId(JOIN enrollments 收敛,四数口径同源);retryRefundmutation 暴露给客户端(此前只有后端 action)。活动经营面(详情页 OfferingPaymentsPanel,manage_events 门控)与工作区财务面(收款管理页 + 活动筛选)两层结构:定价与订单随活动走,工作区级只留汇总。孤儿定价配置页(/settings/pricing)已删除,TierEditor 共享组件嵌入创建/编辑表单。
- 定义:把订单推进到
refunding并入队PaymentRefundWorker的单一入口(Cgc2046.Payments.RefundCommencement.commence/2,#845;ADR-0007 更正补记第 3 点:自动 / 用户侧退款发起(start_refund/retry_refund路径)的唯一入口,不含管理员refund/retry_refund、unforfeit、no-showforfeit)。按状态分派:paid/expired/cancelled→start_refund、refund_failed→retry_refund(eligible 白名单由调用方传入);CAS 竞态只用一种办法收敛——重读一次、重新分类。返回封闭结果集:{:ok, :started | :retried | :already_in_progress} | {:error, {:ineligible, status} | reason};refunding/refunded恒为already_in_progress(race 契约在 seam 上定义一次,与 eligible 正交)。 - 入队归 Order(#845 D1):
start_refund/retry_refund/refund/unforfeit的after_action同事务入队,恰好一次,不依赖 Oban unique 兜底;CAS 失败无 after_action、不产生孤儿 job。调用方不再手动Oban.insert!。 - 职责边界:seam 只做分类与推进——哪些订单合格(eligible)、结果抛错 / 跳过 / 回滚都留在调用方(核销面把
forfeited转deposit_already_forfeited、批量退对跳过笔留 warning、自助取消失败即上抛回滚取消)。
- 定义:Owner/Admin 将
payment_pending报名直接置confirmed的特权操作,跳过支付、不建订单;个案级免费入口(志愿者/组织者参会),审计照走。区别于pricing_enabled: false(整场免费)与 0 元档(不存在,PriceTier 金额下限 1 分)。 - 关闭收费批量转换(organizer-payment U3/R9):Event/Course update 检测
pricing_enabledtrue→false 时,同事务对该活动全部payment_pending报名逐条复用免缴三元组(CAS 转确认 + 作废待付单 + 免缴审计行)+ 补发 completed 信号(Enrollment.waive_pending_for_offering/4,经Changes.WaivePendingOnPricingDisable挂接)。落账竞态 CAS 先到先得(先落账者保持已付);迟到扣款由落账 worker 按免缴审计行/作废单判定自动原路退回——审计行不可省(KTD4 正确性约束)。
- 定义:两级赞助 = Event 级(单场活动)+ Workspace 级(长期)(D-A3)。赞助方以账号身份参与赞助 workflow(意向 → Owner/Admin 审批 → 权益生效),不必成为成员。
- 架构位置:独立 context(
sponsorship/,已随 ADR-0009 PR④ 落地——D4:赞助不纯是 Event 的附属):Sponsorship / SponsorshipDelivery 聚合 + SponsorshipTier 纯函数族 + 履约订阅器同目录;Event 侧保持软引用;权益生效经异步 Signal。
- 定义:「谁是赞助审批人」规则(拍板 #4)的唯一真源 =
Cgc2046.Sponsorship.Policies.SponsorshipApprover.approver_roles/1(2026-08-17 架构深化候选 F):approver_roles(:event) -> Role.manage_roles()(owner/admin,角色清单变更自动跟随)|approver_roles(:workspace) -> [:owner](长期承诺加严;平台 Admin 备案二期,不参与审批)。三消费面只改此处即全链路跟随:写面match?/3(approve/reject policy,委托Enum.any?(roles, &(&1 in approver_roles(level))))|提醒面ApprovalReminderWorker每工作台两套收件人选择器按{:roles, approver_roles(level)}派生(收件人零变化,测试钉死)|读面PendingApprovals按角色集反查allowed_levels做 Sponsorship 行级过滤(level in ^allowed_levels下推到 pending/expired/count 三路径——admin 无 workspace 级行,与写面 policy 一致,看得到点不动的行不进待办读面)。 - 架构位置:横切判定面(policy 模块内纯函数薄壳,规则归属地不另起第二真源);消费方 = sponsorship approve/reject policy / ApprovalReminderWorker / PendingApprovals;
is_nil(event_id)不变量本体不动(仅 ARW 不再作分派依据)。
- 定义:Event 级邀请(D-A3):Owner 创建 → 邀请 workflow(接受/拒绝 → 分享材料产出 → 结束);分享完关系结束,不成为成员。
- 架构位置:活动 context 资源;邀请/接受状态由 Signal 驱动。
- 定义:闪念间(Flashback)公开的许愿条目;新愿望以
user_id为账号归属,person_id仅为可选档案关联(ADR-0017,2026-09-25 已接受)。允许没有历史档案的新用户许愿,不创建占位 Person;存量仅关联 Person 的愿望与有效 token 写入口继续表达真实历史作者身份,认领后按该 Person 的账号归属展示/授权/合并年度额度。 - 写入与额度:新小程序提交带稳定
requestId,同账号同请求重放返回原记录,改动正文或选项必须换请求号;年度额度统计账号愿望与已认领历史愿望(含软删),写入按 Person → User 顺序同事务加锁。账号注销级联删除其愿望;档案删除仅清理关联该档案的愿望,独立账号愿望不受影响。 - 署名隐私边界(ADR-0018,2026-09-25 已接受):
AlumniProjection.masked_name已生成的遮罩姓是快照,不随 display_name 溯源回溯;flashback_people.full_name只能作为生成遮罩姓的输入,不能原文写入愿望署名快照。Wish.signature= 愿望被创建那一刻写入的不可变快照(后续改 display_name / 档案资料不回冲)。 - 架构位置:
Cgc2046.Flashbackdomain。写面单源 =Flashback.WishWriting.create/4;无 MCP 写面;公开 GraphQL 写面 = 手写 mutationflashback_create_wish(mutations.ex:294,经import_fields(:flashback_mutations)/(:flashback_wish_mutations)进根 schema,调WishWriting.create;Ash 自动生成的 create action 不进 GraphQL 写面——wish.ex:78注释所指的「不进 GraphQL 写面」是这层);治理面 =admin_list_wishes/admin_get_wish/admin_soft_delete_wish/admin_soft_delete_wish_comment(见 platform_admin 层 28)。
- 定义:微信分享深链缓存的存储/复用面(plan 011,spike D1-A/D2-A 拍板):
miniprogram_share_schemes表(全局资源,Miniprogram domain(2026-08-28 自 Accounts 拆出;⑩ 方案 A 收缩后该 domain 仅剩本资源),无 GraphQL 面),UK(target_kind, target_id, platform)——同一目标/平台只留一份 scheme,未过期命中复用零外呼、过期重生成 upsert 覆盖(照Accounts.InvitationCode先例——原 Miniprogram.Code,⑩ 方案 A 迁域)。到期失效 =min(registration_deadline + 7d, now + 30d),deadline 缺失 →now + 30d(30 天为官方临时 scheme 硬上限;时间源经 plan owner 2026-08-18 应答修正:Event/Course 均无 endsAt,统一以 registration_deadline 为 clamp 代理)。 - 架构位置:生成/复用/clamp 唯一入口 =
Cgc2046.Miniprogram.ShareSchemeService.fetch_or_generate/2(外呼经UrlScheme.create_link/3,errcode 保真传播不落库);触发 =Workflows.ShareSchemeInstantiator订阅event.launched/course.launched→ Oban job(Workers.ShareSchemeWorker,maintenance 队列)异步预生成——外呼不进信号同步路径,:not_foundwarning 不重试、平台错误走 Oban 默认重试。scheme query/path 只含id+kind(安全红线,永不携带 token/凭据/openid);前端配套 = event-detail 分享 title 兜底 +Taro.onAppShow热启动路由(resolveAppShowRoute纯函数,scene 优先)。 ⚠️ 已建未接线(2026-09-10 审计确认,非缺陷):fetch_or_generate/2的生产调用方只有ShareSchemeWorker自己——表读策略forbid_if(always())(:54-58)、GraphQL/MCP/admin/短信邮件全无 scheme/openlink 面,产物零消费方。这是 spike §6 D5-A「不做 GraphQL 面、由渠道内部触发」的原样落地:渠道(短信/邮件/活动页取链接)当年未实现。scheme 的站内价值为零——微信内分享走useShareAppMessage卡片直达小程序,不经 scheme。唯一真实需求是站外投放(海报/公众号/浏览器)。同批确认的第二处缺口:target_kind只允许:event | :course(Ash 层约束,DB 无 check 约束),initiative 无 scheme/小程序码。- 站外投放的现行落点 = HTTPS 公开页,不是 scheme:
/events/<slug>、/courses/<slug>、/initiatives/<slug>游客可读,/initiatives/[slug]的page.tsx为 server wrapper 声明 canonical/hreflang(pageAlternates),三条路径全部进app/sitemap.ts动态条目(open 才进)。运营取链接出口 = admin 倡导活动列表「复制公开链接」+ MCPget_public_initiative/list_public_initiatives的url字段(单源Cgc2046.Initiatives.Public.public_url/1,base 取config :cgc_2046, :web_base_url)。小程序侧冷启动与热启动同判定(resolveEntry,useLaunch+Taro.onAppShow),id/slug深链冷启动不再被丢弃。
- 定义:敏感操作留痕的全局日志(二期接
ash_paper_trail);一期以 ToolCallLog 每次工具调用审计记录覆盖。 - 架构位置:二期基础设施。
- 定义:收件人解析 + 通知入队的唯一归属(2026-08-14 通知分发收敛,架构评审候选①,依赖异步链路 PR-B 合入后落地)。interface 三件套:
managers(workspace_id, selector)(租户内目标角色成员 →%{user_id => [identity]}平台身份分组)|identities(user_id)(单用户全平台身份)|deliver(recipients, template_key, data, job_meta, unique)(入队 args 形状 / identity_uid 展开 / unique 预设的唯一实现)。收件人选择器是数据不是谓词::manage(走Role.manage_roles/0唯一真源)|{:roles, [...]}(显式窄集,如赞助 Workspace 级仅 Owner,拍板 #4);unique 用命名预设:default|:reminder_7d,未显式传参时按 template_key 查NotificationWorker.type/1的 unique 预设(缺省:default,2026-08-18 架构深化候选 D D3)——Oban unique 语义不进 interface。错误内化:不崩、必 Logger + telemetry([:cgc2046, :notification_fanout, :deliver],失败可计数)。 - 架构位置:Notifications.Subscriber / SpeakerSubscriber(handle 体)与 ApprovalReminderWorker / LearningProgressWorker(按工作台预取分组复用,消 N+1——两段式 interface 的原因)四方调用的 seam;Notifications.Subscriber 退化纯订阅方(公共入队面删除,异步计划 Q4 backlog 落地);发送侧 Notifications.Service 与 NotificationWorker 不动;
target_title的 Event/Course 分叉不在此面(属 offering seam 候选)。 - 无身份收件人(#232):
managers保留已命中角色但身份列表为空的用户。耐久接受逐用户同事务写站内快照与渠道 outbox/jobs;真零身份保留既有哨兵,有身份但全无订阅能力仅写站内记录,channel count 仍为 0。deliver的 best-effort/claim_first 失败边界不变,失败不冒充已接受。
- 定义:用户本人最近 30 天内系统已生成并接受的通知及服务端已读状态,不是订阅授权余额、投递账本或送达回执。工作台管理者与平台管理员均无他人收件箱读取旁路。
- 生命周期:
inserted_at > now - 30 天才可读取或标记已读;日清理删除等于或早于截止时刻的正文。read_at首次写入后不再覆盖,换设备仍一致;本机仅缓存账号隔离的服务端快照,认证错误不能恢复缓存。 - 来源:既有通知类型在接受事务中写精简用户文本;同类型、同来源、同用户的多身份扇出只有一条站内记录,写入和查询均不消耗 Consent,不触发发送。不回填旧 outbox/本机记录,也不保存核销码、认证密钥或原始渠道载荷。
- 去重边界:不保永久幂等墓碑。保留行去重,旧 durable outbox 不作为历史导入来源;纯站内记录清理后,同一来源被重新接受可能产生新记录,不称永久 exactly-once,也不恢复旧
read_at。
- 定义:通知类型契约的唯一真源 =
Cgc2046.Notifications.NotificationWorker的@notification_types表(2026-08-18 架构深化候选 D;AEW@expiry_specs同款声明式规格先例),公开type/1(按 template_key 查条目 | nil)与types/0(全条目)读契约。条目字段:template_key(通知类型键,与 config:miniprogram_templates三平台 registry 键集双射;runtime.exs prod 块同键集注入亦由 D7 测试锚定——三面一致,防 prod 漏配静默失败)|id_key(stale 重查的资源 id 在 data 中的键,无重查 = nil)|data_keys/job_meta_keys(生产方构建 data / job_meta 的键集契约)|unique(Notifications.Fanout.deliver 缺省 unique 预设:default|:reminder_7d)|stale(重查规格{resource, required_status, :not_expired \| :running},nil = 不重查)。 - stale 语义(表驱动单解释器,D2):提醒类类型发送时重查——approval_reminder 同键两行(Enrollment / Sponsorship 面,由 data 携带的 id_key 分派)走
ApprovalDeadline.not_expired?/2放行谓词(nil 永不过期=投递、==now 不放行=跳过;禁用 overdue?/2——不对称对偶);learning_stagnation 走 WorkflowRunstatus == :running。非 required_status / 读失败 → 跳过(stale=true);未知类型 / 无 stale → 不重查直接投递。 - 收敛/不收边界:收敛面 = 键集契约(data_keys / job_meta_keys)+ unique 预设 + stale 谓词(D4);payload 值构建不收敛(生产方仍自构建 data / job_meta 值,D4/D6——
Payments.NotificationTemplates.payment_data/1是唯一 payload builder 先例,不扩此面);Notifications.Fanout 主体 / Notifications.Service / Integrations.Wechat.Client / config 面与 miniprogram SubscriptionScenario(独立数据面,event_reminder漂移仅 advisory,D5)不收。 - 架构位置:横切契约面(root Worker 单文件,AEW
@expiry_specs同款先例);消费方 = Notifications.Fanout(unique 缺省查表,D3)/ 生产方(moduledoc 契约描述引用type/1,D6)/ 表驱动契约测试(test/cgc_2046/workers/notification_worker_test.exs,D7)。 - 缴费闭环新增键(organizer-payment U5,R12/R13):
payment_received(收款到账 → workspace 管理者逐笔实时感知,data 含 title/tier_name/amount;落账 worker 挂点)与payment_expired(订单超时 → 学员 + 管理者,data 携带re_enrollable标志——报名截止未过才承诺可重新报名;过期 worker 成功分支挂点,尽力而为不影响释放)。推送尽力而为,可靠兜底 = 经营面面板;模板未配置时 provider_not_configured 静默跳过。
- 定义:审批截止时间的派生语义唯一真源 =
Cgc2046.ApprovalDeadline(2026-08-14 审批期限深化,架构评审候选②;2026-08-17 架构深化 A+B 补谓词端口not_expired?/2)。interface:derive/1(列实体读approval_deadline列;Invitation 读expires_at列;WorkflowRun =updated_at + definition.approval_timeout内存派生,调用方需先 load definition)|not_expired?/2(放行谓词:nil→true、否则严格> now;==now 不放行)|overdue?/2(扫中谓词:deadline 严格过点< now)|in_window?/3(提醒窗口(now, window_end]半开区间)|default_timeout_days/0(四资源创建期默认期限 7 天的唯一来源)。not_expired?与overdue?是不对称对偶(nil 侧相反:not_expired? nil→true / overdue? nil→false;==now 侧双双 false),语义分工:放行谓词(claim 守卫 / 投递守卫)vs 扫中谓词(过期扫描),不可互相代用;SQL 端口 = ApprovalClaim 的 deadline 守卫(:future↔ not_expired? /:passed↔ overdue?,测试对偶钉死)。 - nil 语义(单点):
derive/1返回 nil = 永不过期——不参与过期扫描(overdue?恒 false),也不进入提醒窗口。WorkflowRun 的definition.approval_timeout = nil(F7 方案 A)即此语义;列实体 deadline 列为空同此。 - 扫尾 specs:ApprovalExpiryWorker 六份过期扫描由
@expiry_specs声明式规格驱动({resource, status, deadline: {:column, atom} | :derived, tenant})——列实体保持 SQL 下推过滤,WorkflowRun 走:derived(load definition + 内存判断,绝不可并入纯 SQL 分支,否则 timeout nil 的 run 会被误扫);每记录转换仍走各资源:expire领域 action(D-A6)。 - 架构位置:横切读取面(root 单文件,Notifications.Fanout 同款先例);消费方 = ApprovalExpiryWorker / ApprovalReminderWorker(窗口大小 48h 仍为 ARW 私有常量)与四资源创建期(Enrollment 客户端可传覆盖 / Sponsorship 服务端固定的创建纪律差异另行决策)。
- 定义:条件 UPDATE 原子抢占唯一真源 =
Cgc2046.ApprovalClaim(2026-08-17 架构深化候选 A+B;架构评审候选 A)。interface:claim(record, opts)——table:(编译期枚举 atoms,拒绝任意字符串)|from:(状态守卫数组,status IN (...))|set:(列 → 字面值 |{:arg, atom}|{:sql, fragment})|deadline:({col, :future | :passed};:future→(col IS NULL OR col > $N),:passed→col IS NOT NULL AND col < $N;守卫复用 set 中{:arg, :now}占位符,SQL 端口 = ApprovalDeadline.not_expired?/overdue?)|extra_where:({sql_fragment, params},片段占位符$1起内部编号、claim 统一重编号到全语句连续编号——42P18 纪律单点化)|returning:(列原子列表,成功回读 DB 原始值)。占位符序:SET 值 → extra_where 参数 → id(固定最后一个参数)。 - 错误分工(D3):claim 只返回
{:ok, returned} | {:error, :not_claimed},DB 错误{:error, {:database, reason}}回传资源层——各资源现有错误原子/消息/发生层原样保留(graphql 契约字符串、Splode code、域错误原子均不动);sponsorship 读回消歧(approval_conflict/reject_conflict)、enrollment claim_cancellable RETURNING 值判读留各资源。 - 收编边界:7 资源 / 15 调用点(代码实测
grep -rn "ApprovalClaim.claim" backend/lib,勘误 2026-10-05:原文 14,事后 enrollment cancel_flow 又添一处):join_request approve、workspace_application approve、invitation accept、enrollment claim_pending/prepare_expire/claim_cancellable/claim_waive(4 处)+ cancel_flow 体(1 处)、sponsorship approve/reject/expire/end(4 处)、speaker_invitation claim_decision×2/claim_complete(3 处)。保留(D5):invitation accept_miniprogram(多表 JOIN+别名+双 deadline 列)、user demote(count 子查询聚合)、capacity ledger reserve/release counter(capacity_ledger.ex)+ enrollment consume_invite_quota counter、validate_pending_status 快照守卫×2、transition.ex、payments/order.ex 私有 claim/4、其余非 approval deadline 守卫(registration_deadline/invite_batches/sponsorship_deadline + FOR SHARE)、Ash expr 全部(ARW/AEW/pending_approvals/reconciliation,D7 冻结面)。 - 组合序留在资源层:sponsorship approve 独占位 advisory lock(
Repo.acquire_lock/2;锁失败返回%BusinessError{}由 change 折进 changeset,#621)在 claim 前取得(锁序 lock→claim,D6);不加租户过滤(row id 已从租户隔离读面解析);不自己开事务/checkout(before_action 事务继承,savepoint 语义不变);成功不 force_change(回写留资源层)。plan 2026-08-15-010 D4「裸 SQL 本体不动」re-scope:claim 族是真同构(deletion test 失败=复杂度随资源数扩散),与 domain_error 族假同构(39 原子仅 1 共享)区分。 - 架构位置:横切写原语(root 单文件,ApprovalDeadline / Notifications.Fanout 同款先例);消费方 = 七个资源 action 的 before_action(事务内)+ 表驱动契约测试(
test/cgc_2046/approval_claim_test.exs)。
- 定义:平台级 best-effort 异步路径孤儿报告(E-10 #125)。
ReconciliationScanWorker每 10 分钟扫 15 条:① ~ ⑥ 在代码 rules_registry.desc 中沿**「E-10」溯源标签、⑦learning_run_stalled沿「E-9 #122/S8」**溯源标签(本源词为「E-10 七条」的相传口径;代码侧溯源标签与「E-10」不等同,这里按代码为准);⑧ ~ ⑫ 为 ADR-0009 U7 名额账本四条 + Fable 5 缓存漂移一条;⑬ 资金写频次告警(R3);⑭ 通知失败面(#556);⑮ refunding 无在途退款 job(#862),落Reconciliation.Finding(表 reconciliation_findings,全局资源,read 仅 PlatformAdmin)→ /admin/reconciliation 对账页可读。规则语义单源 =backend/lib/cgc_2046/reconciliation/rules_registry.ex(#852 C9 Q4 收编);本清单与之一一对应。 - 规则清单:①
confirmed_enrollment_without_runconfirmed enrollment 无 learning run(workflow_runs.input_snapshot->>'enrollment_id'joinworkflow_definitions.type=learning,BYO 无平台终态、存在即非孤儿)|②pending_without_deadlinepending 无 approval_deadline(enrollment/sponsorship/join_request/workspace_application 四资源 UNION,创建路径必写)|③active_sponsorship_signal_deadactive sponsorship 的sponsorship.active发布 job 处于 discarded(PR-A 同事务必入队,死信=信号链断连;原「无 signal_log」因 ADR-0003 入向局限不可实现而修正)|④open_entity_without_research_definitiondraft/open 但工作台无 published 教研定义——Course 无条件命中(curriculum_enabled 无此列,2026-08-16 Q12;UAT P2 扩 draft:prep run 始于 course.created,断流在 draft 期就该看见)、Event 仅 curriculum_enabled=true 命中(false = 轻聚会合法不命中;教研 run 始于 launched,draft 期无断流不扩;S6 起 event-only)|⑤nonterminal_research_run_for_closed_entityclosed/cancelled Event/Course 仍有非终态教研 run(instance keyevent_<id>/course_<id>,reaper 同约定)|⑥dead_letter_job信号族死信(SignalPublishWorker / NotificationWorker / DeliveryWorker / DepositForfeitWorker / PaymentRefundWorker 的 discarded job,Pruner 7 天窗口内判定,超窗交 Pruner,#862)|⑦learning_run_stalledlearning run 停滞(7 天无 facts 更新,与 LearningProgressWorker 同源判定)。规④/⑤原子名冻结research_*——DB 落库枚举值,不随 PR③ 改名。名额账本四条(ADR-0009 U7,R17):⑧open_offering_without_ledgeropen offering 无账本行(launched 信号建行 / 报名懒建双路均未到达,信号在途窗口下一拍自消)|⑨ledger_occupancy_mismatch账本 occupancy ≠ 占位报名计数(占位 = confirmed + payment_pending,与占位/释放路径口径一致)|⑩capacity_projection_drift展示投影滞后账本超一拍(confirmed_count / sync_version 与账本不一致且超一个扫描周期,宽限一拍对齐 10 分钟 cron)|⑪occupancy_exceeds_capacity账本 occupancy > capacity(capacity 调小后的合法超员窗口看护至自然释放收敛,R16/AE4)|⑫ledger_cache_drift账本三列缓存(status/capacity/registration_deadline)漂移于 offering 真值(ADR-0009 Fable 5 HIGH-1,无宽限;在途瞬时命中下一拍自消)。规⑬fund_action_burst(资金写频次告警 R3):窗口内同一 actor 同类资金写治理动作(:order_refund/:order_refund_retry/:waive_payment)超阈值(默认 1h/5 笔,app env 可调)→ 按 actor 一行 Finding;纯查询admin_action_logs不动资金链路;频率回落下一拍自消。规⑭notification_delivery_failed(通知失败面 #556):24h 内落:failed的notification_deliveries行逐行出 Finding(entity =:notification_delivery);窗口语义自清——超窗未命中删除;终态化本体在 DeliveryWorker 末拍。规⑮refunding_without_refund_job(refunding 无在途退款 job,#862):进refunding即同事务入队PaymentRefundWorker(C2 #845),无在途任务 = 任务被人工删除或重试耗尽丢弃,钱退不出去且无人发现;在途 state =available/scheduled/executing/retryable(Oban:incomplete的 Postgres 子集),15 分钟宽限纯防御;处置:MCPretry_refund重入退款链,或订单离开 refunding 后下一拍自消。 - 刷新语义:命中 upsert(唯一键
(rule, entity_type, entity_id),保 first_seen_at、刷新 last_seen_at),本次未命中删除——「无孤儿 → 空报告」由结构保证。共享驱动Finding.apply_rule/3按 sweep 模式分拍(#848)::full全量拍(upsert + 未命中删除,默认);:partial降级拍(扫描面残缺,只 upsert);:one_shot单事件拍(单事件触发无全量视图,只 upsert,命中仍刷 last_seen_at)。 - 死信窗口:规⑥只判 oban_jobs 7 天窗口内(与 Oban Pruner max_age 对齐)的 discarded 行;死信可见性由本扫描承担,不扩 Oban discard 插件。
- 七天上限(窗口语义,非 bug):规③/规⑥的有效窗口同受 Oban Pruner(max_age 7 天)约束——discarded job 被 Pruner 删除后,未消解的规③/规⑥孤儿会从报告静默消失(刷新语义按未命中删除,视为已消解)。
- 缴费对账(已落地):
payment_recon由 PaymentReconciliationWorker、payment_amount_mismatch由 PaymentSettlementWorker 写入同一 Finding 底座(「底座共享 + 扫描器归各域」,ADR-0009 D6;ADR-0007,webhook 丢失的长尾兜底)。sweep 模式(#848):payment_recon完整拍:full、渠道账单拉取失败的降级拍:partial(不删旧 finding);payment_amount_mismatch单事件拍:one_shot(重复命中刷新 last_seen_at,不做 stale 删除)——全部生产者走同一驱动,无旁路。 - 架构位置:
Cgc2046.Reconciliation.Finding(Reconciliation domain 全局资源,PR⑤ U8 归位、Api 退役)+Cgc2046.Reconciliation.ReconciliationScanWorker(maintenance 队列,unique 300s,规1/2/4/5 Ash 查询下推、规3/6 Repo 直查 oban_jobs、规8-11 Repo 直查账本/offering 表);配套 SignalSubscriber 骨架 telemetry[:cgc2046, :signal, :deliver](D7)与订阅方冒烟测试(#134-①)。
| 易混对 | 区别 |
|---|---|
| 登录 Token vs 连接 token | 登录 Token:网站 UI 的 httpOnly cookie(JWT);连接 token:MCP Authorization: Bearer 头,绑用户不绑工作区 |
| A 通道 vs B 通道 | A 通道(网站派活)已删除;B 通道(网站 MCP server)是唯一主干 |
| Agent vs AgentRun | Agent 是授权/配置登记(不含执行,实体系 roadmap);AgentRun 是 ToolCallLog 的聚合投影概念(实体不建,随 Agent 落地重启) |
| 个人 Agent vs 公共 Agent | 个人 = 角色分身仅本人可见;公共 = Workspace 级按 Workflow 协作 |
| Skill vs Agent | Skill 是预设工作流(SKILL.md);Agent 是带人格/面板/技能绑定的助手;工作区 Skill 经本地同步进 ~/.clacky/skills/ |
cgc-2046 vs cgc2046-<ws>-<skill> |
cgc-2046 是扩展 id / MCP 条目名;cgc2046-<ws>-<skill> 是本地同步技能的命名前缀 |
| 确认流 vs 直接执行 | 高风险管理工具必须 pending → confirm_operation 才落库(two-tool 模式);低风险写工具(save_* 族)直接执行 |
| PriceTier vs SponsorshipTier | PriceTier 是真实收款定价(收款即发生);SponsorshipTier 是赞助意向档位(v1 仅登记不收款,amount_suggestion) |
| WorkflowDefinition vs WorkflowRun | 蓝图(Runic.Workflow DAG + 版本)vs 执行实例(pending→running→waiting→succeeded/failed/cancelled,归属 partition) |
| 同步写 vs 异步 Signal | 业务核心状态主写入口走同步 Ash Action(强一致,8);衍生副作用/通知走 Signal 异步最终一致(2) |
| Offering vs 课程设置 | Offering = 供给物(Event/Course 上位词,发布语言读端口);course offerings 标准译法 = 开设的课程清单,非「课程设置」 |
| Research vs Curriculum | 教研 context 英文定名 Curriculum(ADR-0009);research_* 代码命名随重构 PR③ 退役 |
| 活动现值 vs 订单快照 | 活动现值:创单前 Enrollment.depositAmountCents / paymentMode 是计算字段,每次读都取活动当下配置,组织者改价即跟随;订单快照:创单时钉死的 tier_snapshot / order.amountCents,此后不随改价漂移(#749)。「报名快照」「押金快照」是已废弃叫法——报名那一侧从不是快照 |
miniprogram(旧 commit scope)vs mp-wechat / mp-xhs / mp-dy |
旧 scope miniprogram 笼统指全部小程序平台(已废);现 scope 按具体平台分化——mp-wechat 微信小程序、mp-xhs 小红书小程序、mp-dy 抖音小程序(ADR-0016 决 5;与发布账本的端标签平台同名词)。CHANGELOG 顶部「端标签节点与 commit scope 平台化」约定与本行同源同清单 |
-
ADR-0009 限界上下文重构序列✅ 已完成(2026-08-28,更正后五步:PR① Admission 抽出(含 Offering 端口化)→ PR② Courses/Events 分家 → PR③ Curriculum 独立 + research_* 改名 → PR④ Sponsorship 独立(原序列漏排,实施期更正)→ PR⑤ Payments 收敛 + 名额账本 + 展示投影回路 + Workflows/Learning/Reconciliation 归位、Cgc2046.Api退役) -
Invitation 撤销流程(revoked 状态 + 到期清理)✅ 已实现(slice-B) -
JoinRequest 审批的角色分配方式(申请人请求 vs 审批方指定)✅ 已定稿:审批方指定(slice-B 决策 2) -
确认流 auto_approve 模式的冷却期(二期)
-
平台管理员建 Workspace 的审计留痕✅ 已实现(accounts/workspace.ex:create挂LogAdminAction同事务留痕,#116 R10a) -
人工步骤超时/取消语义(workflow
waiting状态):✅ 已实现 approval_timeout 过点 → expired(ApprovalExpiryWorker@expiry_specs)与显式:cancelaction(workflow_run.ex);❌ 未做:按 offering registration_deadline 自动取消 run -
异步 Signal 路径的幂等键与重试策略✅ 已落地(signal_idempotency.exPostgres 唯一约束登记 +SignalEmitter缺省 idempotency_key + Oban max_attempts 8 + 订阅方 claim 去重)