32 KiB
32 KiB
Coord 最终方案强制裁决
版本:1.0(历史文档) 日期:2026-07-09 决策者:coord(经用户确认) 适用范围:Edu 项目全部 15 份 AI 设计文档(ai01-ai06, ai08-ai16)
⚠️ 历史声明:本文件为批次模式时期(2026-07-09)的 coord 强制裁决记录。项目已于 2026-07-10 切换为全并行开发模式。
- 本文件正文保留原始裁决内容(含 ai07 引用),作为历史追溯凭证
- 当前权威文档:ai-allocation.md v2.0 + issues/README.md + issues/coord.md(新流程仲裁)
- ai07 已移除(classes 合并到 core-edu,ai08 负责)
- 新仲裁记录见 issues/coord.md
0. 裁决原则
用户反馈:"前面简略实现后面要重写,完全没必要,直接使用最好的方式工作"。
各 AI 设计文档中存在大量"P2 简化 → P3 重构 → P4 再重构"的渐进式过渡方案,导致前期代码后期必废弃。coord 裁决如下:
0.1 强制覆盖声明
- 本文档 > 各 AI 设计文档:凡本文档列出的裁决项,各 AI 设计文档中与之冲突的"中间过渡方案"一律作废,直接采用最终方案
- 不分阶段:最终方案在服务首次落地时即采用,不得以"阶段未到"为由先实现简化版
- 回写义务:各 AI 收到本文档后,必须在 3 个工作日内回写自己的 02-architecture-design.md,删除所有"中间过渡方案"描述,对齐最终方案
0.2 裁决依据
- 用户明确指示"直接使用最好的方式"
- project_rules.md §3.1 已规定企业级规范(如 Dockerfile 多阶段、Zod 验证、GlobalErrorFilter),无理由分阶段
- 黄金模板 services/classes/ 已实现全部最终方案,其他服务对齐即可
1. 通用裁决(适用全部服务)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 | 影响服务 |
|---|---|---|---|---|
| G1 | Dockerfile | "P3 重构"、"P5 重构"、"待落地" | 首次实现即多阶段构建(builder + runtime) | 全部 |
| G2 | /readyz 探针 | "P2 仅返回 ok"、"P3+ 补下游探针" | 首次实现即检查全部下游依赖(DB SELECT 1 / Redis PING / Kafka 连接 / gRPC 下游可达) | 全部 |
| G3 | /healthz | "P2 改造" | 首次实现即 liveness 探针 | 全部 |
| G4 | Logger | "待引入 slog"、"console.log → P3 修复" | 首次实现即结构化日志(TS pino / Go slog / Python structlog),禁止 console.log | 全部 |
| G5 | Metrics | "自定义业务指标待实现" | 首次实现即 /metrics 端点 + 基础业务指标 | 全部 |
| G6 | Tracer | "资源属性待补"、"span 待补" | 首次实现即 OTel SDK + OTLP exporter + 完整资源属性 + 全链路 span | 全部 |
| G7 | Zod 验证 | "P2 补全"、"P3 实施中"、"部分 → P4 全 parse" | Controller 层首次实现即全量 schema.parse(body/query/params) | 全部 TS |
| G8 | GlobalErrorFilter | "待落地"、"P4+ 对齐 ActionState" | 首次实现即注册 GlobalErrorFilter + 响应信封严格对齐 ActionState 结构 | 全部 |
| G9 | 优雅关闭 | "P2 补全" | 首次实现即 SIGTERM → app.close() → shutdownTracer(),按序关闭 HTTP→DB→Redis→Kafka | 全部 |
| G10 | DB 连接模式 | "const db vs getDb()" | 统一 getDb() 函数(对齐 classes 黄金模板) | 全部 TS 持 DB 服务 |
| G11 | ID 策略 | "cuid2 vs randomUUID" | 统一 cuid2(对齐 classes 黄金模板) | 全部 TS 持 DB 服务 |
| G12 | ESM import | 缺 .js 后缀 | 所有相对 import 带 .js 后缀(NestJS ESM 模式) | 全部 TS |
| G13 | import type | 混用 | 仅用于类型的导入必须 import type | 全部 TS |
| G14 | 错误码前缀 | api-gateway 无前缀 | 每个服务错误码必须用服务名大写前缀(GW_/IAM_/CORE_EDU_/CONTENT_/MSG_/DATA_ANA_/AI_/PUSH_/BFF_TEACHER_/BFF_STUDENT_/BFF_PARENT_/CLASSES_) | 全部 |
| G15 | 错误码子前缀 | 保留 EXAMS_/HOMEWORK_/GRADES_ | 统一用聚合域前缀 CORE_EDU_*,禁止子前缀 | 前端 + core-edu |
| G16 | Kafka topic 命名 | edu.notification.events 抽象名 | edu.<domain>.<aggregate>.<action> 具体动作(如 edu.notification.sent) | 全部 |
| G17 | proto 包名 | 不一致 | next_edu_cloud.<domain>.v1 | 全部 |
2. BFF 层专项裁决(teacher-bff / student-bff / parent-bff)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| B1 | API 风格 | "P2 REST → P4 GraphQL"、"P3 REST → P4+ GraphQL" | P2 起直接 GraphQL(GraphQL Yoga + DataLoader) |
| B2 | 对下游通信 | "P2 HTTP fetch → P3+ gRPC"、"P3 HTTP → P3+ gRPC" | 首次实现即 gRPC 调用下游(iam/core-edu/content/data-ana/msg/ai 全部 gRPC) |
| B3 | 权限装饰器 | "BFF 加 @RequirePermission"、"待仲裁" | BFF 豁免 @RequirePermission(权限由 Gateway JWT + 下游服务负责),BFF 仅校验 x-user-id 存在 |
| B4 | 越权防御 | "仅 teacher-bff 透传"、"student-bff 待仲裁" | 全部 BFF 强制自我越权防御:student-bff 比对 userId、parent-bff 校验 childId 绑定列表、teacher-bff 校验 teacherId 与班级关系 |
| B5 | 错误码前缀 | "STUDENT_BFF_ vs BFF_STUDENT_" | BFF_TEACHER_ / BFF_STUDENT_ / BFF_PARENT_(统一 BFF_ 前缀) |
| B6 | 缓存策略 | "不缓存 vs Redis 短缓存" | Redis 5-30s 短缓存(对齐 004 §6.2 BFF 混合读策略) |
| B7 | Kafka 订阅 | "P3 评估"、"P5 评估" | P2-P4 不订阅 Kafka(仅同步聚合),P5 push-gateway 落地后再订阅 |
| B8 | DownstreamClient 抽象 | "是否回写 teacher-bff" | 回写 teacher-bff,作为 BFF 模式 v2 标准抽象,3 个 BFF 统一使用 |
3. 业务服务专项裁决
3.1 iam(ai06)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| I1 | gRPC server | "P2 仅 REST → P3 启用 gRPC" | P2 即启用 gRPC server 50052,同时暴露 REST + gRPC |
| I2 | Outbox | "iam 自建 → core-edu 落地后回写 shared-ts" | 直接建 shared-ts Outbox 工具包,iam 首次实现即用 |
| I3 | PermissionGuard | "本地 ROLE_PERMISSIONS map → DB 驱动" | 首次实现即 DB 驱动 + Redis 缓存,废弃本地 map |
| I4 | AuthMiddleware | "P2 不注册 → Controller 直读 header" | 首次实现即注册 AuthMiddleware,Controller 通过 @Req() 获取用户上下文 |
| I5 | JWT 密钥 | "P2 本地文件 → P6 Vault" | P2 本地文件(IAM_PRIVATE_KEY_PATH / IAM_PUBLIC_KEY_PATH),P6 迁 Vault 单列后续 |
| I6 | 家长-学生关联 | "接口缺失 → P3 补" | P2 即补全:iam_student_guardians 表 + GetChildrenByParent RPC + GET /iam/children REST(P0 阻塞 parent-bff) |
| I7 | API 版本化 | "待 coord 审查" | 采用 /iam/v1/ 前缀*,Gateway 同步调整 |
| I8 | iam 端点路径 | /iam/permissions/effective vs /iam/effective-permissions | 统一 GET /iam/permissions/effective(RESTful 资源路径风格) |
3.2 core-edu(ai08)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| C1 | classes 合并 | "P3 初期 vs P3 末期" | P3 开始前直接合并:classes 代码迁移到 core-edu,classes 服务下线 |
| C2 | gRPC server | "P3 启用" | P3 首次实现即启用 gRPC server 50053 |
| C3 | Kafka console.log | "P3 修复" | 首次实现即用 logger.info,禁止 console.log |
| C4 | AttendanceService | "待 coord 补 proto" | P3 首次实现即补全 proto + 实现 |
| C5 | GetClassesByTeacher / BatchGetClasses | "待新增" | P3 首次实现即补全 |
| C6 | 排课 room_id | "P3 实现 vs 预留 vs P6+" | P3 预留字段(不实现逻辑,但 schema 含字段) |
| C7 | 成绩计算公式 | "scope 优先级待定" | class > subject > school(越细粒度优先) |
| C8 | 作业 grace_period | "0 秒 vs 300 秒" | 默认 300 秒(5 分钟宽限) |
| C9 | schema 强制校验 | "P3 JSON Schema vs 文档约束" | P3 仅文档约束,P6+ 引入 schema registry |
| C10 | exam.submitted 事件 | "包含完整 answers vs 仅 submission_id" | 仅含 submission_id(避免事件过大) |
| C11 | archived 考试 | "物理迁移 vs 软删除 vs P6+" | 仅软删除(is_archived 字段) |
3.3 content(ai09)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| N1 | gRPC server | "gRPC 待实现" | P4 首次实现即启用 gRPC server 50054 |
| N2 | /readyz | "仅 DB → P4 多依赖" | 首次实现即检查 DB/Neo4j/Kafka |
| N3 | QuestionService | "P5 阻塞待补 proto" | P4 即补全 QuestionService proto(不等到 P5) |
| N4 | ErrorFilter | "错误响应结构待对齐" | 首次实现即对齐 ActionState |
| N5 | ChapterService | "缺 GetChapter" | P4 首次实现即补全 |
3.4 msg(ai10)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| M1 | gRPC server | "gRPC 待实现" | P5 首次实现即启用 gRPC server 50056 |
| M2 | /readyz | "仅 DB → P5 多依赖" | 首次实现即检查 DB/ES/Redis/Kafka/PushGateway |
| M3 | ZodError | "未处理 → P5 处理" | 首次实现即处理 ZodError 分支 |
| M4 | 调用 push-gateway | "gRPC PushService.Push" | HTTP POST /internal/push(coord 仲裁豁免 gRPC) |
| M5 | 事件 topic | "edu.teaching.assignment.*" | edu.teaching.homework.*(与 core-edu 发布方一致) |
3.5 data-ana(ai11)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| D1 | ErrorFilter | "v2 对齐 ActionState" | 首次实现即对齐 ActionState |
| D2 | Dockerfile | "v2 设计多阶段" | 首次实现即多阶段 |
| D3 | ClickHouse DDL | "待 coord 建目录" | 直接建 infra/clickhouse/ddl/ |
| D4 | analytics.proto 扩展 | "待 coord 审议" | P4 即补全:4 端 Dashboard + Warning + MasteryDistribution + SubscribeMasteryUpdate Stream RPC |
| D5 | warning.triggered topic | "待 coord 登记" | 已登记,004 §7.2 + events.proto MasteryEvent |
3.6 ai(ai12)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| A1 | ErrorFilter | "待对齐" | 首次实现即对齐 ActionState |
| A2 | Dockerfile | "实现阶段需新增" | 首次实现即多阶段 |
| A3 | gRPC 端口 | "待 coord 补登" | 已补登 50058(见 infra/port-allocation.md) |
| A4 | ai.proto 补全 | "待 coord" | P5 首次实现即补全 GenerateLessonPlan / StreamGenerateQuestion |
| A5 | AIUsageEvent proto | "待 coord" | 已补全 events.proto |
| A6 | Prompt 模板存储 | "P5 YAML / P6+ DB" | P5 直接用 DB 存储(避免后期迁移) |
| A7 | 备课工作流 | "P5 Redis / P6+ Temporal" | P5 用 FastAPI BackgroundTasks + Redis(Temporal 引入成本高,P6+ 单独评估) |
| A8 | 多模态 | "P5 文本 / P6+ 多模态" | P5 仅文本,proto v2 预留字段 |
3.7 push-gateway(ai02)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| P1 | gRPC | "备选 gRPC PushService" | 彻底删除 gRPC 设计,纯 HTTP + WebSocket(coord 仲裁豁免) |
| P2 | X-Internal-Key | "→ X-Internal-Token P5 立即" | 首次实现即 X-Internal-Token(环境变量 INTERNAL_API_TOKEN) |
| P3 | Dockerfile | "P5 重构" | 首次实现即多阶段 |
| P4 | slog | "待引入" | 首次实现即用 slog |
| P5 | SSE 降级 | "P7+ 考虑" | 本期仅 HTTP 长轮询预留,SSE 不实现 |
| P6 | 断线重连 | "本期仅协议预留" | 本期仅协议预留(session_id + last_seq 字段),P6 实现补推逻辑 |
3.8 api-gateway(ai01)
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| W1 | 错误码前缀 | "无前缀" | GW_*(GW_UNAUTHORIZED 等) |
| W2 | 错误信封 | "recovery/ratelimit/circuit-breaker 待整改" | 首次实现即对齐 ActionState |
| W3 | slog | "待引入" | 首次实现即用 slog |
| W4 | /readyz | "待重构" | 首次实现即真实健康检查(轮询下游 /healthz) |
| W5 | 业务 metrics | "待实现" | 首次实现即基础业务指标(请求量/延迟/错误率/熔断) |
| W6 | tracer 资源属性 | "待补" | 首次实现即完整(version/env/host) |
| W7 | DevMode 防护 | "P1 待办" | 首次实现即生产防护(DEV_MODE 仅非生产环境生效) |
| W8 | per-服务实例熔断 | "P6 待办" | 保持共享 downstream 熔断,per-实例 P6 单独评估 |
4. 前端 4 portal 专项裁决
| # | 过渡项 | 禁止的中间方案 | 强制最终方案 |
|---|---|---|---|
| F1 | admin-portal 端口 | 3003 / 3000 / 4003 三处矛盾 | 统一 4003 |
| F2 | admin-portal MF 配置 | teacher_app@localhost:3000 | teacher_app@localhost:4000 |
| F3 | Web Vitals | FID vs INP 混用 | 统一 INP(CWV 2024 标准) |
| F4 | i18n key 命名 | bff.error / bffTeacher.error / bffStudent.error 混用 | 统一 error.<service>.<code_snake>(如 error.iam.invalid_credentials、error.bffTeacher.validation_error) |
| F5 | 错误码子前缀 | parent-portal 保留 GRADES_/HOMEWORK_ | 统一删除子前缀,用 CORE_EDU_* |
| F6 | iam 权限端点 | /iam/permissions/effective vs /iam/effective-permissions | 统一 /iam/permissions/effective |
| F7 | 权限点命名风格 | TEACHER_DASHBOARD_VIEW / STUDENT_DASHBOARD_READ / GRADES_READ_CHILD 混用 | 统一 <RESOURCE>_<ACTION>(如 DASHBOARD_VIEW、EXAM_READ、GRADE_READ),数据范围用后缀 _OWN/_CHILD(如 GRADE_READ_CHILD) |
| F8 | packages 归属 | "ai07/ai13 维护 vs coord 维护" | ai13 维护 ui-tokens/ui-components/hooks,coord 仅维护 shared-ts/contracts |
| F9 | GraphQL vs REST | "P2-P6 REST,未来切 GraphQL" | P2 起 BFF 用 GraphQL,前端用 urql/apollo |
| F10 | MF 暴露粒度 | "AppShell 整体 vs 细粒度" | 暴露 AppShell 整体,各 Remote 自行决定内部布局 |
| F11 | teacher-portal 归属 | "004 §15.1 写 ai07,ai-allocation §3.2 写 ai13" | ai13 负责 teacher-portal(ai07 负责 classes 黄金模板) |
| F12 | localStorage token | "P6 评估迁移 httpOnly Cookie" | P2 用 localStorage(httpOnly Cookie + CSRF Token P6 单独评估) |
5. 跨服务契约裁决(一次性补全,不分阶段)
5.1 proto 契约补全(coord 负责,P2 启动前全部就位)
| proto 文件 | 待补内容 | 阻塞阶段 | 完成期限 |
|---|---|---|---|
| events.proto | ✅ 已补全(UserEvent/RoleEvent/NotificationEvent/MasteryEvent/AIUsageEvent/KnowledgePointEvent/QuestionEvent) | — | 已完成 |
| iam.proto | GetViewports / GetEffectivePermissions / GetEffectiveAccess / Logout / GetPublicKey / BatchGetUsers / GetEffectiveDataScope / GetChildrenByParent | P2 | P2 启动前 |
| core_edu.proto | AttendanceService.* / GetClassesByTeacher / BatchGetClasses | P3 | P3 启动前 |
| content.proto | ChapterService.GetChapter / QuestionService.* / TextbookService.ListTextbooks 补全 | P4 | P4 启动前 |
| analytics.proto | GetTeacherDashboard / GetStudentDashboard / GetParentDashboard / GetAdminDashboard / GetWarnings / GetMasteryDistribution / SubscribeMasteryUpdate (stream) | P4 | P4 启动前 |
| ai.proto | GenerateLessonPlan / StreamGenerateQuestion / 字段扩展 | P5 | P5 启动前 |
| msg.proto | BatchSendNotification / GetUnreadCount / BatchMarkAsRead / MarkAllAsRead / RecallNotification + NotificationPreferenceService + NotificationTemplateService + 3 个 message | P5 | P5 启动前 |
5.2 接口命名统一
| 调用方 | 被调方 | 统一命名 | 说明 |
|---|---|---|---|
| core-edu | content | KnowledgeGraphService.GetPrerequisites / GetLearningPath | 禁用 ContentService 命名 |
| teacher-bff | data-ana | GetTeacherDashboard(无 Stats 后缀) | 统一命名 |
| msg / student-bff | push-gateway | HTTP POST /internal/push(body 含 user_id) | 豁免 gRPC |
| ai | core-edu | ExamService.GetExam | 方向:ai 调 core-edu,非反向 |
5.3 gRPC server 启用时机
| 服务 | gRPC 端口 | 启用时机 | 说明 |
|---|---|---|---|
| iam | 50052 | P2 | 首次实现即启用(覆盖"P2 仅 REST") |
| core-edu | 50053 | P3 | 首次实现即启用 |
| content | 50054 | P4 | 首次实现即启用 |
| data-ana | 50055 | P4 | 首次实现即启用 |
| msg | 50056 | P5 | 首次实现即启用 |
| ai | 50058 | P5 | 首次实现即启用 |
| push-gateway | — | 不启用 | 豁免 gRPC |
| BFF(3 个) | — | 不暴露 gRPC | 仅对下游走 gRPC |
6. 待 P6+ 单独评估事项(不阻塞当前阶段)
以下事项不属于"中间过渡方案",属于"未来扩展",保持原状:
| # | 事项 | 评估时机 |
|---|---|---|
| X1 | ai 发布 edu.insight.ai.generated / feedback / workflow 3 个新 topic | P6+ |
| X2 | admin-portal 8 个新 Kafka topic + events.proto message | P6 实施时 |
| X3 | admin-portal 审计日志/安全事件归属(iam vs audit-service) | P6 |
| X4 | ai 备课工作流迁移 Temporal | P6+ |
| X5 | ai Prompt 模板 DB 存储迁移(已在 A6 裁决 P5 直接用 DB) | — |
| X6 | admin-portal Grafana/Jaeger iframe 嵌入 | P6 |
| X7 | teacher-portal localStorage → httpOnly Cookie 迁移 | P6 |
| X8 | iam JWT 密钥迁移 Vault | P6 |
| X9 | api-gateway per-服务实例熔断 | P6 |
| X10 | schema registry 引入(events.proto 强制校验) | P6+ |
7. 各 AI 回写义务
各 AI 收到本文档后,必须执行以下回写:
7.1 回写内容
- 删除自己 02-architecture-design.md 中所有"中间过渡方案"描述
- 对齐本文档的最终方案
- 更新 §8 风险与假设(移除已裁决项)
7.2 回写期限
| AI | 服务 | 回写期限 | 阻塞阶段 |
|---|---|---|---|
| ai01 | api-gateway | P2 启动前 | — |
| ai02 | push-gateway | P5 启动前 | — |
| ai03 | teacher-bff | P2 启动前 | P2 |
| ai04 | student-bff | P3 启动前 | — |
| ai05 | parent-bff | P4 启动前 | — |
| ai06 | iam | P2 启动前 | P2 |
| ai07 | classes | 已是黄金模板,无需回写 | — |
| ai08 | core-edu | P3 启动前 | — |
| ai09 | content | P4 启动前 | — |
| ai10 | msg | P5 启动前 | — |
| ai11 | data-ana | P4 启动前 | — |
| ai12 | ai | P5 启动前 | — |
| ai13 | teacher-portal | P2 启动前 | P2 |
| ai14 | student-portal | P3 启动前 | — |
| ai15 | parent-portal | P4 启动前 | — |
| ai16 | admin-portal | P6 启动前 | — |
7.3 coord 验收
各 AI 回写完成后,coord 将重新运行 §8 交叉审查,确认无"中间过渡方案"残留。
8. 变更记录
| 日期 | 变更 | 决策者 |
|---|---|---|
| 2026-07-09 | 初始创建,覆盖 G1-G17 / B1-B8 / I1-I8 / C1-C11 / N1-N5 / M1-M5 / D1-D5 / A1-A8 / P1-P6 / W1-W8 / F1-F12 共 80+ 项裁决 | coord(用户确认) |