Files
openmaic/docs/handover.md
2026-08-16 14:58:47 +08:00

16 KiB
Raw Permalink Blame History

麦洛学习项目交接

交接基线2026-08-15

项目根:/Users/inmanw/项目/麦洛学习

代码:/Users/inmanw/项目/麦洛学习/OpenMAIC

文档:/Users/inmanw/项目/麦洛学习/docs

1. 一句话现状

三端能力边界、大型连续课程生成、完整 classroom 冻结发布和精确版本学习链路已经落地; 当前仍是一份 OpenMAIC Next.js 16.1.2 应用与文件仓库learner desktop / ops / server 尚未物理拆分,发布层尚未迁到 Postgres/对象存储learner 身份与资源 owner 也尚未完成。这两项都是公网生产上线前的硬前置。

2. 接手前必须知道的本机事实

项目 当前事实
OpenMAIC 版本 package version 0.3.2
Next.js 16.1.2
版本管理 OpenMAIC 当前不是 Git worktree没有可依赖的 commit/diff/rollback
环境文件 .env.local 当前存在;不要输出、分享或提交其中内容
.env 当前不存在
代码与文档位置 文档在 OpenMAIC 同级的 docs不在代码目录内
存储 课程发布主链路仍使用 data 下的文件仓库
写入拓扑 只能单 Node.js 写实例

在建立 Git 基线前,任何后续改动都应小批次、保留触及文件清单,并先备份 data 与 .env.local。

3. 产品与能力矩阵

能力 learner desktop ops server
普通文本单课件后台生成 同构代码仍可用 当前承载 job/API
材料/Interactive/职教前台生成 同构代码仍可用 提供生成 API
原单课件编辑与 Stage 模块审阅
大型课程主 Agent 当前同进程运行
框架人工确认、模块重试
classroom 冻结与课程发布 冻结并发起 整批事务接收与发布
courseware / manifest 读服务 消费 发布 主责
大课目录与精确模块学习 验收 提供数据
Agent/Chat/白板/PBL/音频 原样保留 审阅时原样保留 供数据/模型端点

当前部署角色为 all、ops、server、learner。生产默认 learner开发/测试默认 all。 大型课程运营能力由 role + ACCESS_CODE 会话保护;浏览器不再持有发布 token。 server 角色已是无头服务:所有页面以及运营/API 家族返回 404但这不等于 其他 API 已有 learner 身份和资源级授权。当前已额外隐藏 provider 验证、 MP4 export、开发态 persistence、usage、全局 job 列表和未鉴权媒体代理; 原 Chat/Quiz/PBL/TTS 与单课件生成等待正式 learner capability不得直接删除。 全局 job 列表的 HEAD 与 GET 同样隐藏,内部整课发布在尾斜杠形式下也仍是 POST-only。ops 远程发布成功后会用当前 publication receipt 保护 source classroom 发布和 job/source 删除还有同进程互斥。这不包含被重生清除的历史回执,也不是 跨副本锁;多实例生产化时必须改为持久发布历史和数据库事务/分布式锁。

ACCESS_CODE 会话默认 TTL 为 7 天cookie Max-Age 与服务端验证同步;登录只接受 同源 application/json实际请求体最大 8 KiB且有进程内失败限流。所有 cookie 授权的 ops 写请求还会校验同源 Origin。 反向代理部署要配置 OPS_PUBLIC_ORIGIN 作为 canonical origin该校验不信任 X-Forwarded-Host。

4. 不可跨越的保护边界

本项目的原则是:不改原课堂内核,只改外围编排、权限、发布、目录和存储。

受保护范围:

  • components/stage.tsx
  • components/edit/PlaybackChromeRoot.tsx
  • components/chat/**
  • components/roundtable/**
  • components/scene-renderers/**
  • lib/playback/**
  • lib/chat/**
  • lib/action/**
  • lib/whiteboard/**
  • lib/pbl/**
  • 原 Stage / Scene / Action DSL 和 /classroom/{id} 链路

验收不变式:

  1. learner 发布模块仍走 /learn → /classroom → 原 Stage。
  2. learner 外层没有第二套 AssistantPanel原 ChatArea/Roundtable 是唯一课堂问答入口。
  3. frozen bundle 带完整 Agent persona/voice、Stage/Scene 白板、四类 Scene、Action、 HTML、Quiz、PBL、媒体和 speech 音频。
  4. 大课模块仍复用原单课件 agent没有第二套内容生成器。
  5. 拆分工作不得以修复无关 Chat 断言为理由修改 protected core。

5. 已实现主链路

5.1 learner desktop 单课件双路径

app/page.tsx 的当前分流:

  • 普通纯文本需求 → POST /api/generate-classroom → 持久化后台 job
  • 有材料,或 Interactive/职教开关打开 → lib/generation/foreground-session.ts 保存文件引用和 generationSession → 原 /generation-preview。

后台路径可以关闭页面后从 /tasks 查看;前台路径保留浏览器拥有的材料与原互动/职教生成。

5.2 大型课程

POST /api/courses
  → 主 Agent CourseFramework
  → framework_ready
  → 人工确认
  → 模块按序复用 classroom job
  → outputDigest / continuityInputRefs
  → 全部成功

主 Agent 输出 targetAudience、courseGoals、continuityContract以及每模块 generationPrompt、输入/输出知识边界和 excludedTopics。任一模块失败会暂停后续模块 修复后按顺序续跑。

所有大课模块固定 Interactive Mode 与 TTS。若没有生成的非空 HTML interactive Scene 在 persistClassroom 前失败。

5.3 发布

validate CourseRecord snapshot
  → persisted classroom 转完整 frozen bundle
  → 每模块暂存 unpublished 精确版本
  → 再验证来源未变化
  → 本批切 published
  → schemaVersion=2 manifest

发布保留完整课堂,并拒绝缺 Agent、音频、媒体、HTML、连续性或发生 digest 漂移的模块。 每个 manifest module 固定 coursewareId + coursewareVersion + contentHash。 发布校验还使用 sourceRevisionHash 锁定完整持久化 classroomops 只在 server 返回 的 id/version/contentHash 逐项一致后保存带 sourceRevisionHash 的 publication receipt。

独立部署时ops 把 metadata 与全部模块 ZIP 作为一个 multipart 请求发往 /api/internal/course-publishserver 只接受 COURSEWARE_PUBLISH_TOKEN Bearer。模块在 schema-v2 manifest exact pin 提交前不会进入公共 catalog/detail/download。未配置远程 origin 时的同源 fallback 复用同一内容幂等与可见性规则。

contentHash v2 忽略 manifest.json.exportedAt使相同内容的远程/本地重试返回既有 manifest无 contentHashVersion 的真正历史包仍按 v1 原始字节哈希验证。

5.4 学习

manifest 精确 pin
  → registry 精确版本
  → bundle 内外 id/version/hash/completeness 校验
  → 物化 Agent/音频/媒体/document
  → hash 隔离的本地 stage id
  → 原 Stage

发布文档不带 outlines所以课堂页不会触发场景/媒体恢复生成。进入原 Stage 后,原课堂 教师/助教、动态评分和 PBL 运行时仍可按原设计调用模型。

6. 关键代码地图

角色与鉴权

路径 作用
lib/config/deployment-role.ts 角色解析、生产 fail closed、manage_courses
lib/server/ops-access.ts ops role + ACCESS_CODE cookie route guard
middleware.ts 运营页面/API 第一层边界
app/api/coursewares/route.ts ops 会话或 server Bearer 的双发布入口
e2e/tests/deployment-role-boundary.spec.ts learner 隐藏/拒绝运营能力

单课件双路径

路径 作用
app/page.tsx 普通后台与材料/Interactive/职教前台分流
lib/generation/foreground-session.ts 文件持久化与原 preview session 适配
app/generation-preview 原前台生成
app/api/generate-classroom 后台 job
lib/server/classroom-generation.ts 原单课件服务端生成管线与 HTML 门

主 Agent 与连续性

路径 作用
lib/course-framework/types.ts CourseFramework、generationPrompt、digest/ref 契约
lib/prompts/templates/course-framework/system.md 主 Agent prompt
lib/course-framework/generate-framework.ts 严格解析、校验与重试
lib/course-framework/compat.ts 旧 CourseRecord 读取兼容
lib/course-framework/runner.ts 人工确认后的顺序单课件复用
lib/course-framework/module-digest.ts 实际产出摘要与 semanticHash
lib/course-framework/publish-validation.ts digest、连续性、HTML 发布门
lib/course-framework/store.ts 文件记录、原子更新、读时自愈

冻结与版本

路径 作用
lib/server/classroom-courseware-publish.ts persisted classroom → 完整 frozen bundle
lib/bundle/packager.ts bundle 组装、完整性和内容 hash
lib/bundle/serialize.ts Stage/Agent/Scene/Action portable manifest
lib/courseware-repo append-only 课件版本与 bundle bytes
lib/course-manifest-repo schema v2 课程清单历史
app/api/courses/{id}/publish 暂存、二次校验、提交、失败补偿

learner

路径 作用
app/api/learn/courses 课程目录与精确 manifestlegacy 返回 409
app/learn/course/{id} 课程模块列表和精确 pin 导航
lib/bundle/learner-load.ts 严格身份/哈希/完整性校验与回滚
lib/import/import-classroom-core.ts portable manifest → 原课堂 document
app/learn/{coursewareId} 加载并跳转 classroom
app/classroom/{id} 仍挂原 Stagelearner query 只加返回导航

7. 环境与数据目录

关键角色变量:

  • OPENMAIC_DEPLOYMENT_ROLE
  • NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE
  • ACCESS_CODE
  • ACCESS_CODE_SESSION_TTL_SECONDS
  • OPS_PUBLIC_ORIGIN
  • COURSEWARE_PUBLISH_TOKEN
  • COURSE_PUBLISH_SERVER_BASE_URL
  • COURSEWARE_PUBLIC_BASE_URL

严禁重新加入 NEXT_PUBLIC_COURSEWARE_PUBLISH_TOKEN。

关键目录变量:

  • CLASSROOM_DATA_DIR
  • CLASSROOM_JOBS_DIR
  • COURSE_FRAMEWORK_DIR
  • COURSEWARE_DATA_DIR
  • COURSEWARE_BUNDLE_DIR
  • COURSE_MANIFEST_DIR
  • COURSEWARE_MAX_UPLOAD_BYTES
  • COURSE_PUBLISH_MAX_UPLOAD_BYTES
  • COURSE_PUBLISH_TIMEOUT_MS

模型按需配置 MODEL_ROUTES 与 provider/TTS/image/video/web-search 变量。 大课至少需要 course-framework 与原 generate-classroom/scene stages 可解析; 发布需要所有 speech 的持久化音频。

通用 NEXT_PUBLIC_PERSISTENCE、DATABASE_URL、ASSET_S3_BUCKET 不等于发布仓库已经迁移。 完整矩阵和示例见 deployment-3-tier.md。

默认数据:

目录 内容
data/classrooms classroom JSON、本地 media/audio 子目录
data/classroom-jobs 单课件 job
data/course-frameworks ops CourseRecord
data/coursewares courseware 版本历史 metadata
data/courseware-bundles id/version ZIP
data/course-manifests course manifest 版本历史
data/usage 计量
data/tts-cache QA/TTS 缓存能力仍在代码中

8. 单实例限制与未来存储

当前 version 分配与 read-modify-write 依赖进程内 Map 锁。临时文件 + rename/link 只能保证 文件操作层面的原子性,不能协调多个 Node.js 进程。发布失败只会由应用层把本批 记录补偿回 unpublished不具有跨进程隔离性、原子提交或崩溃一致性。现阶段

  • ops/framework writer 单实例;
  • server courseware/manifest writer 单实例;
  • 生成和发布期间避免滚动重启;
  • runner 中断后依赖持久记录自愈,再由运营续跑;
  • 备份 metadata history 与 bundle bytes 必须成对。

公网生产上线或多实例前必须完成:

  • Postgres 唯一键和事务分配 courseware/manifest version
  • 行锁或 advisory lock 保护每个 course/courseware
  • 对象存储以 id/version/hash 为 key 并 put-if-absent
  • CDN 只分发已被 schema-v2 manifest exact pin 的 immutable bundle当前文件下载路由已用 同一门禁隐藏 promote→manifest 的中间状态,未来对象存储不能以公开 URL 绕过它;
  • 持久任务队列取代内存 runner
  • ops 已可通过内部 HTTP 一次提交 metadata 与全部 bundle独立部署必须配置 COURSE_PUBLISH_SERVER_BASE_URL不再直接写 server 文件。

9. legacy manifest 重发

不要手工给旧记录填 current latest。

  1. learner API 对旧记录返回 409这是预期 fail closed。
  2. 备份旧 singleton/history 文件。
  3. 在 ops 打开原 CourseRecord 并运行发布校验。
  4. 若缺 outputDigest、continuityInputRefs、Interactive HTML、音频或媒体从最早失败模块 开始按顺序重生成。
  5. 人工审阅后重发课程。
  6. 新 manifest version 使用 schemaVersion=2 并精确 pin旧版本保留审计但不可学习。
  7. 若原 CourseRecord/classroom 已丢失,只能按新课程重新生产,不能伪造历史身份。

10. 验收结果与命令

已确认结果

检查 结果
首页双路径 Playwright 3 passed
learner 部署边界 Playwright 1 passed
三端角色/ops access 定向单测 通过
主 Agent、连续性、Interactive 回归 通过
冻结发布相关合并回归 10 files / 85 tests 通过
本次文档交接定向回归 18 files / 108 tests 通过
本次 TypeScript 检查 pnpm exec tsc --noEmitexit 0
本次四文档格式检查 Prettierexit 0
受保护课堂广泛回归 130 files / 1,294 tests 中 1,291 通过3 个 Chat 基线漂移
本次隔离 Chat 复现 3 files40 passed / 3 failed
i18n key check exit 19 个非 zh-CN/en-US locale 各缺 180 个键

定向回归

在 OpenMAIC 目录执行:

pnpm exec vitest run \
  tests/config/deployment-role.test.ts \
  tests/server/ops-access.test.ts \
  tests/generation/foreground-session.test.ts \
  tests/server/classroom-outline-mode.test.ts \
  tests/server/classroom-generation-retry.test.ts \
  tests/course-framework \
  tests/bundle \
  tests/courseware

静态与构建

pnpm exec tsc --noEmit
pnpm build
pnpm exec prettier ../docs/architecture-3-tier.md ../docs/deployment-3-tier.md ../docs/large-course-mode.md ../docs/handover.md --check

浏览器边界

pnpm exec playwright test \
  e2e/tests/home-to-generation.spec.ts \
  e2e/tests/deployment-role-boundary.spec.ts \
  --project=chromium

i18n 已知缺口

pnpm check:i18n-keys

该命令当前预期失败,输出为 ar-SA、es-MX、fr-FR、ja-JP、ko-KR、pt-BR、ru-RU、 vi-VN、zh-TW 各缺 180 个相对 en-US 的键。运行时有 fallback但发布前应补齐。

11. 已知 3 个受保护 Chat 基线漂移

隔离命令:

pnpm exec vitest run \
  tests/lib/chat/pi/director-tool-wiring.test.ts \
  tests/lib/chat/pi/route-cue-user.test.ts \
  tests/lib/chat/pi/prompts.test.ts

稳定失败:

  1. tests/lib/chat/pi/prompts.test.ts
    • keeps concept and mechanism questions teacher-led before student reactions
    • 当前 prompt 明确课堂只有 teacher + user旧断言仍期待 assistant fallback 文字。
  2. tests/lib/chat/pi/route-cue-user.test.ts
    • hands successful web evidence to one child and clears it before later delegations
    • 旧 fixture 断言 URL 必须直接出现在 child prompt 字符串。
  3. tests/lib/chat/pi/route-cue-user.test.ts
    • does not leak consumed web evidence after the selected child fails
    • 同属 evidence attachment 与旧 prompt 字符串断言漂移。

大型课程和冻结发布改造未修改 lib/chat/** 或 app/api/chat/pi。按用户确定的保护边界 这 3 项只记录、隔离复现,不在本改造中修改 Chat 内核、prompt 或断言。

12. 未完成事项

按优先级:

  1. 建立 Git 基线并备份 data/.env.local。
  2. 完成 learner 用户身份、classroom/job owner、资源级授权、课程权益和成本限额。 在此之前,不得宣称匿名 Chat、生成、TTS、搜索等高成本 API 已安全。
  3. 把 courseware/manifest metadata 迁到 Postgres、bundle 迁到私有对象存储,并在 CDN 层保留 manifest exact-pin 可见性门禁。
  4. 按 deployment-3-tier.md 的顺序拆 server → learner desktop → ops内部整批 HTTP adapter 已完成,可直接迁移而不是重新设计。
  5. 将大课目录 localStorage 完成标记改为 runtime 进度聚合。
  6. 补齐 9 个 locale 的 180 个键。
  7. 在真实部署网络上补一条 ops → server → learner smoke仓内已有两个隔离文件根、mock fetch 的无 LLM 跨进程契约测试。

拆分期间继续坚持:移动边界、替换 adapter、保持契约不要复制或重写原 Stage/Chat。