389 lines
16 KiB
Markdown
389 lines
16 KiB
Markdown
# 麦洛学习项目交接
|
||
|
||
> 交接基线: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 锁定完整持久化 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。
|
||
|
||
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 --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
|
||
|
||
稳定失败:
|
||
|
||
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。
|