16 KiB
麦洛学习项目交接
交接基线: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} 链路
验收不变式:
- learner 发布模块仍走 /learn → /classroom → 原 Stage。
- learner 外层没有第二套 AssistantPanel;原 ChatArea/Roundtable 是唯一课堂问答入口。
- frozen bundle 带完整 Agent persona/voice、Stage/Scene 白板、四类 Scene、Action、 HTML、Quiz、PBL、媒体和 speech 音频。
- 大课模块仍复用原单课件 agent,没有第二套内容生成器。
- 拆分工作不得以修复无关 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 锁定完整持久化 classroom;ops 只在 server 返回 的 id/version/contentHash 逐项一致后保存带 sourceRevisionHash 的 publication receipt。
独立部署时,ops 把 metadata 与全部模块 ZIP 作为一个 multipart 请求发往 /api/internal/course-publish,server 只接受 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 | 课程目录与精确 manifest;legacy 返回 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} | 仍挂原 Stage;learner 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。
- learner API 对旧记录返回 409,这是预期 fail closed。
- 备份旧 singleton/history 文件。
- 在 ops 打开原 CourseRecord 并运行发布校验。
- 若缺 outputDigest、continuityInputRefs、Interactive HTML、音频或媒体,从最早失败模块 开始按顺序重生成。
- 人工审阅后重发课程。
- 新 manifest version 使用 schemaVersion=2 并精确 pin;旧版本保留审计但不可学习。
- 若原 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 --noEmit,exit 0 |
| 本次四文档格式检查 | Prettier,exit 0 |
| 受保护课堂广泛回归 | 130 files / 1,294 tests 中 1,291 通过,3 个 Chat 基线漂移 |
| 本次隔离 Chat 复现 | 3 files;40 passed / 3 failed |
| i18n key check | exit 1;9 个非 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
稳定失败:
- tests/lib/chat/pi/prompts.test.ts
- keeps concept and mechanism questions teacher-led before student reactions
- 当前 prompt 明确课堂只有 teacher + user,旧断言仍期待 assistant fallback 文字。
- 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 字符串。
- 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. 未完成事项
按优先级:
- 建立 Git 基线并备份 data/.env.local。
- 完成 learner 用户身份、classroom/job owner、资源级授权、课程权益和成本限额。 在此之前,不得宣称匿名 Chat、生成、TTS、搜索等高成本 API 已安全。
- 把 courseware/manifest metadata 迁到 Postgres、bundle 迁到私有对象存储,并在 CDN 层保留 manifest exact-pin 可见性门禁。
- 按 deployment-3-tier.md 的顺序拆 server → learner desktop → ops;内部整批 HTTP adapter 已完成,可直接迁移而不是重新设计。
- 将大课目录 localStorage 完成标记改为 runtime 进度聚合。
- 补齐 9 个 locale 的 180 个键。
- 在真实部署网络上补一条 ops → server → learner smoke;仓内已有两个隔离文件根、mock fetch 的无 LLM 跨进程契约测试。
拆分期间继续坚持:移动边界、替换 adapter、保持契约;不要复制或重写原 Stage/Chat。