427 lines
21 KiB
Markdown
427 lines
21 KiB
Markdown
# 麦洛学习三端部署与运维
|
||
|
||
> 事实基线:2026-08-15
|
||
>
|
||
> 当前是一份 Next.js 16.1.2 代码、三种部署角色,不是三个已经拆开的应用。
|
||
> 本手册先说明当前可运行方式,再给出 server → learner desktop → ops 的机械拆分顺序。
|
||
|
||
## 1. 角色解析与真实保护范围
|
||
|
||
| 值 | 用途 | 生产默认 | 大型课程运营能力 |
|
||
|---|---|---|---|
|
||
| learner | learner desktop 公共表面 | 未配置角色时采用 | 无 |
|
||
| ops | 内部运营工作台 | 必须显式配置 | 有,且生产必须配置 ACCESS_CODE |
|
||
| server | 课件/课程清单服务端 | 必须显式配置 | 无;可用 Bearer token 接收课件 |
|
||
| all | 历史单体兼容 | 不建议生产使用 | 有 |
|
||
|
||
解析规则:
|
||
|
||
- 服务端读取 OPENMAIC_DEPLOYMENT_ROLE,未设时才回退公开角色;
|
||
- 客户端入口读取 NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE;
|
||
- 生产未配置或配置非法时 fail closed 为 learner;
|
||
- development/test 未配置时为 all,保留上游单体开发体验;
|
||
- NEXT_PUBLIC_* 是构建期值,ops 与 learner 要做可靠 UI 隔离时应分别构建。
|
||
|
||
当前强制保护的范围:
|
||
|
||
- /courses、/courses/**、/api/courses、/api/courses/** 和 /api/ops/** 需要
|
||
manage_courses;
|
||
- 生产 ops 若没有 ACCESS_CODE 返回 503;
|
||
- 运营 API 除 middleware 外,还逐 route 调用 requireOpsAccess 校验 HMAC cookie;
|
||
- learner 请求运营 API 返回 403,访问运营页面回到首页;
|
||
- server 是无头服务:所有非 API 页面直接返回 404,/api/courses/**、
|
||
/api/ops/** 和 /api/access-code/** 也返回 API 404;provider 探测/验证、
|
||
MP4 export、开发态 persistence、usage、全局 job 列表与 `/api/proxy-media`
|
||
也不在专用 server 公开面;
|
||
- 全局 job 列表的 GET/HEAD 都会返回 404;
|
||
`/api/internal/course-publish` 只允许 POST,尾斜杠不会绕过方法边界;
|
||
- POST /api/coursewares 在 ops 同源部署校验 ACCESS_CODE 会话,在 server 部署校验
|
||
COURSEWARE_PUBLISH_TOKEN Bearer token,在 learner 部署拒绝;
|
||
- 公开 courseware、course manifest 读取仍是服务端的静态数据 API。
|
||
|
||
远程发布成功后,ops 通过当前 `CourseRecord.publication` 回执识别本地未挂载
|
||
registry 的已发布 source classroom,并拒绝覆写或随 job 删除。发布与删除还
|
||
共用同进程 source 互斥,避免在最终复核与回执落盘之间竞态删除。该保护只覆盖
|
||
当前回执且不跨进程;重生清除回执后,旧已发布源是否永久保留尚未定义,
|
||
多实例仍需持久发布历史和分布式锁/事务。
|
||
|
||
重要限制:上述是针对明确非课堂必需端点的部分 deny,不是完整的
|
||
learner identity/capability allowlist;
|
||
物理拆分之前,还应在反向代理层只暴露该角色需要的路由。
|
||
|
||
learner 身份、classroom/job owner、资源级授权、课程权益与成本额度尚未完成。
|
||
无头 server 边界只隐藏页面和运营 API,不代表其他生成、Chat、TTS、搜索等
|
||
高成本 API 已可安全向匿名公网开放。上线前必须先完成身份与授权设计,
|
||
并由网关执行最小 API allowlist。
|
||
|
||
## 2. 三端部署矩阵
|
||
|
||
| 项目 | learner desktop | ops | server |
|
||
|---|---|---|---|
|
||
| 角色 env | learner | ops | server |
|
||
| 面向用户 | 学习者/单课件作者 | 内部运营 | learner 与 ops |
|
||
| 单课件双路径 | 主能力 | 同构代码中仍存在 | 当前承载后台生成与模型 API |
|
||
| 大型课程创建/确认/重试 | 隐藏且拒绝 | 主能力 | 不提供运营 UI |
|
||
| 大课学习 | 主能力 | 验收可用 | 提供 manifest/bundle |
|
||
| 原 Stage | 学习和编辑 | 模块审阅 | 不渲染 |
|
||
| ACCESS_CODE | 可选全站访问码 | 生产必配 | 通常不配,发布走 token |
|
||
| COURSEWARE_PUBLISH_TOKEN | 不配 | 当前课程发布内部必配 | 跨实例发布必配 |
|
||
| LLM/TTS/媒体 provider | 单课件生成与原课堂按需 | 主 Agent、模块生成、TTS、媒体 | 仅其实际承载的模型端点按需 |
|
||
| 文件数据目录 | 可读课程缓存为浏览器数据 | classroom、job、framework | courseware、bundle、manifest |
|
||
| 多实例 | UI 可横向,但生成任务仍受状态约束 | 不可并发写文件 repo | 当前不可并发写文件 repo |
|
||
|
||
learner desktop 不是“无模型 key 的纯静态站”。它保留单课件生成和原课堂教学 Agent:
|
||
当前同构部署可通过 server-configured provider 提供模型;物理 desktop 拆分后可改为调用
|
||
受控 server API。只有目录、manifest 和 bundle 下载属于零 LLM 热路径。
|
||
|
||
## 3. 环境变量
|
||
|
||
### 3.1 角色与鉴权
|
||
|
||
| 变量 | 作用 | learner | ops | server |
|
||
|---|---|---:|---:|---:|
|
||
| OPENMAIC_DEPLOYMENT_ROLE | 服务端角色:all/ops/server/learner | 必配 | 必配 | 必配 |
|
||
| NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE | 同一非敏感角色名,控制 UI affordance | 必配 | 必配 | 可配 |
|
||
| ACCESS_CODE | HMAC 会话口令 | 可选 | 生产必配 | 可选 |
|
||
| ACCESS_CODE_SESSION_TTL_SECONDS | 会话 TTL,默认 604800 秒(7 天) | 按需 | 可配 | 不配 |
|
||
| OPS_PUBLIC_ORIGIN | ops 同源写校验的 canonical origin | 不配 | 反代部署必配 | 不配 |
|
||
| COURSEWARE_PUBLISH_TOKEN | 服务端发布凭据 | 不配 | 当前课程发布必配 | 跨实例接收必配 |
|
||
| COURSE_PUBLISH_SERVER_BASE_URL | 固定的跨进程发布 origin | 不配 | 独立部署必配 | 不配 |
|
||
| COURSEWARE_PUBLIC_BASE_URL | learner 可访问的课件公开 origin | 不配 | 不配 | 生产必配 |
|
||
|
||
不存在受支持的 NEXT_PUBLIC_COURSEWARE_PUBLISH_TOKEN。不要把发布密钥放入浏览器构建。
|
||
|
||
ACCESS_CODE cookie 使用 HMAC 签名并带签发时间,Edge 中间件与 Node route 使用
|
||
同一 TTL 规则,拒绝过期或未来时间 token。POST /api/access-code/verify 只接受
|
||
同源 application/json,对 Content-Length 和实际 chunked stream 都强制 8 KiB 上限,
|
||
并对失败尝试做有界进程内限流。所有使用该 cookie 的 ops 写操作也要通过
|
||
Origin 同源校验。进程内限流不能代替网关级的分布式限流。
|
||
同源校验不信任 X-Forwarded-Host;如果 ops 位于反向代理后,必须把浏览器真实
|
||
origin(例如 https://ops.example.com)配置为 OPS_PUBLIC_ORIGIN。
|
||
|
||
### 3.2 生成与模型
|
||
|
||
| 变量 | 作用 | 备注 |
|
||
|---|---|---|
|
||
| MODEL_ROUTES | 按 stage 路由模型 | 至少按需配置 generate-classroom、course-framework、scene-content、scene-actions |
|
||
| 各 PROVIDER_API_KEY / BASE_URL / MODELS | LLM provider | 以 .env.example 为准 |
|
||
| 各 TTS_* | 讲解音频与原课堂语音 | 大课发布要求每个 speech 有持久化音频 |
|
||
| IMAGE_* / VIDEO_* | 可选媒体生成 | 未生成或无法解析的媒体会在发布时 fail closed |
|
||
| Web search provider 变量 | 可选框架研究/单课件联网 | 未配置时相应生成路径降级 |
|
||
| OPENMAIC_ENABLE_VOCATIONAL | 服务端职教 task-engine 门 | 客户端开关本身不是安全边界 |
|
||
| NEXT_PUBLIC_SHOW_VOCATIONAL_TEST_UI | 显示实验入口 | 仅 UI |
|
||
| NEXT_PUBLIC_PI_CHAT_ENABLED | 原 Pi 课堂 Chat 开关 | 属于受保护课堂能力 |
|
||
| OPENMAIC_ENABLE_PI_WEB_SEARCH | Pi Director 联网门 | 独立于 Pi Chat 开关 |
|
||
|
||
主 Agent 模型示例:
|
||
|
||
MODEL_ROUTES={"course-framework":"openai:gpt-4o-mini","generate-classroom":"openai:gpt-4o"}
|
||
|
||
实际 provider:model 格式应与当前 server provider 配置保持一致,不要照搬旧文档中的
|
||
provider/model 斜杠写法。
|
||
|
||
### 3.3 文件仓库
|
||
|
||
| 变量 | 默认目录 | 角色 |
|
||
|---|---|---|
|
||
| CLASSROOM_DATA_DIR | data/classrooms | learner/ops 当前生成服务 |
|
||
| CLASSROOM_JOBS_DIR | data/classroom-jobs | learner/ops 当前生成服务 |
|
||
| COURSE_FRAMEWORK_DIR | data/course-frameworks | ops |
|
||
| COURSEWARE_DATA_DIR | data/coursewares | server;当前同源发布也直接写 |
|
||
| COURSEWARE_BUNDLE_DIR | data/courseware-bundles | server |
|
||
| COURSE_MANIFEST_DIR | data/course-manifests | server;当前 ops 同进程提交 |
|
||
| COURSEWARE_MAX_UPLOAD_BYTES | 314572800 | server;单模块 ZIP 上限 |
|
||
| COURSE_PUBLISH_MAX_UPLOAD_BYTES | 943718400 | ops/server;整批 multipart 上限 |
|
||
| COURSE_PUBLISH_TIMEOUT_MS | 600000 | ops 调用 server 的超时 |
|
||
| COURSE_PUBLISH_ALLOW_INSECURE_HTTP | false | 仅可在受信私网显式开启 |
|
||
|
||
### 3.4 通用持久化
|
||
|
||
| 变量 | 作用 | 当前边界 |
|
||
|---|---|---|
|
||
| NEXT_PUBLIC_PERSISTENCE=1 | 浏览器改用 HTTP persistence backend | 构建期变量 |
|
||
| NEXT_PUBLIC_PERSISTENCE_TOKEN | 开发共享 token | 不提供用户隔离,不得作为公网生产鉴权 |
|
||
| PERSISTENCE_DEV_TOKEN | 与公开开发 token 配对 | 仅开发 |
|
||
| DATABASE_URL | 通用 document/runtime/asset Postgres backend | 没有替换 courseware/manifest repo |
|
||
| ASSET_S3_BUCKET | 通用 asset 字节存储 | 没有替换 frozen bundle byte store |
|
||
|
||
不要因为配置了 DATABASE_URL 或 ASSET_S3_BUCKET,就把发布仓库描述为已经使用
|
||
Postgres/S3。
|
||
|
||
## 4. 当前启动方式
|
||
|
||
每个角色应使用独立构建目录或独立构建流水线,因为公开角色会在构建时内联。
|
||
|
||
### Docker Compose
|
||
|
||
Dockerfile 把 NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE 声明为 builder ARG/ENV,
|
||
Compose 为 learner、ops、server 分别构建独立镜像上下文,并在运行时固定同值的
|
||
OPENMAIC_DEPLOYMENT_ROLE。不要只在 container 启动时改 NEXT_PUBLIC 角色;那不会重写已经内联的
|
||
浏览器 bundle。
|
||
|
||
# learner:默认端口 3000
|
||
docker compose up --build learner
|
||
|
||
# ops:默认端口 3102,.env.local 必须有 ACCESS_CODE
|
||
docker compose --profile ops up --build ops
|
||
|
||
# server:默认端口 3103
|
||
docker compose --profile server up --build server
|
||
|
||
可分别用 OPENMAIC_LEARNER_PORT、OPENMAIC_OPS_PORT、OPENMAIC_SERVER_PORT 改写宿主机端口。
|
||
`.env.local` 仍负责 provider key、ACCESS_CODE 和 COURSEWARE_PUBLISH_TOKEN;Compose 中的硬编码角色会
|
||
覆盖 env_file 中同名角色,防止服务端权限与客户端入口不一致。learner、ops 和 server 默认使用
|
||
独立数据卷。ops 配置 COURSE_PUBLISH_SERVER_BASE_URL 后使用跨实例整批发布;
|
||
未配置时仅保留历史同进程共享文件库 fallback。
|
||
|
||
### 直接启动 Next.js
|
||
|
||
#### learner desktop 表面
|
||
|
||
OPENMAIC_DEPLOYMENT_ROLE=learner
|
||
NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE=learner
|
||
pnpm build
|
||
pnpm start -p 3101
|
||
|
||
若要使用单课件生成和原课堂模型能力,还需配置相应 provider 与 MODEL_ROUTES。
|
||
若只做发布课程的读取 smoke,可不配置模型,但这不代表完整 learner 产品无需模型。
|
||
|
||
#### ops
|
||
|
||
OPENMAIC_DEPLOYMENT_ROLE=ops
|
||
NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE=ops
|
||
ACCESS_CODE=<strong-secret>
|
||
OPS_PUBLIC_ORIGIN=https://ops.example.com
|
||
COURSEWARE_PUBLISH_TOKEN=<server-only-secret>
|
||
COURSE_PUBLISH_SERVER_BASE_URL=https://course-server.example.com
|
||
pnpm build
|
||
pnpm start -p 3102
|
||
|
||
ops 还需主 Agent、单课件生成、TTS 和媒体 provider,以及可持久化的 classroom、
|
||
job、framework 数据卷。
|
||
|
||
#### server
|
||
|
||
OPENMAIC_DEPLOYMENT_ROLE=server
|
||
NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE=server
|
||
COURSEWARE_PUBLISH_TOKEN=<same-server-only-secret>
|
||
COURSEWARE_PUBLIC_BASE_URL=https://learn.example.com
|
||
COURSEWARE_DATA_DIR=/srv/openmaic/coursewares
|
||
COURSEWARE_BUNDLE_DIR=/srv/openmaic/courseware-bundles
|
||
COURSE_MANIFEST_DIR=/srv/openmaic/course-manifests
|
||
pnpm build
|
||
pnpm start -p 3103
|
||
|
||
### 跨进程大课发布契约
|
||
|
||
ops 在本地 persisted classroom 上执行完整冻结和二次快照校验,然后向固定路径
|
||
POST /api/internal/course-publish 发送一个 multipart 请求:metadata JSON 加全部模块 ZIP。
|
||
该路由只在 server/all 角色开放,即使 server 环境意外带有 ACCESS_CODE,也只认
|
||
COURSEWARE_PUBLISH_TOKEN Bearer,不认 ops cookie。
|
||
|
||
server 在一个 course 单进程互斥边界内:
|
||
|
||
1. 对全部 incoming ZIP 运行与单课件发布相同的完整门禁;幂等命中前还会读取 exact 已存
|
||
ZIP,复核存在、内部 id/version/hash、重算 hash 与 complete;缺失或篡改会发布新版本,
|
||
不会返回伪成功;
|
||
2. 调用原 publishCourseware 逐模块分配/重写版本并以 unpublished 暂存;
|
||
3. 全部通过后切换 published,再调用原 publishCourse 写 schema-v2 manifest;
|
||
4. 大课 courseware record 私有携带 courseId/moduleIndex;公共 catalog、detail、download
|
||
只有在任一已提交 schema-v2 manifest 包含 exact id/version/hash pin 时才可见,因此
|
||
promote 到 manifest 提交之间以及失败补偿期间均返回 404/不进入列表;
|
||
5. 任一失败将本批状态补偿回 unpublished;
|
||
6. ops 只在回执的 module id/version/hash 与本地 ZIP 逐项一致后写入 publication receipt;
|
||
receipt 还保存每模块的 sourceRevisionHash。查询状态会重读完整 classroom
|
||
并比较该哈希;任意源修改或模块重生都使 receipt 失效。
|
||
|
||
contentHash v2 明确忽略 manifest.json 的 exportedAt。相同 metadata + 课堂内容的
|
||
远程超时重试、重复点击,以及同源共享文件 fallback 都幂等返回既有
|
||
manifest。无 contentHashVersion 的历史包仍使用 v1 原始字节哈希;learner 的旧内部
|
||
version 兼容只对能通过精确 registry 与重算 v1 hash 的真正旧包生效。若要
|
||
强制产生新版本,必须先改变课程内容,当前契约没有 `forceNewVersion` 开关。
|
||
|
||
安全约束:目的地由 server-only COURSE_PUBLISH_SERVER_BASE_URL 固定,不接受浏览器输入;
|
||
URL 禁止凭据/path/query/fragment,fetch 禁止 redirect;非 loopback HTTP 默认拒绝。server 在
|
||
formData 缓冲前对实际 stream 封顶,且解析后再校验每个 File.size。公开 bundleUrl 仅由
|
||
COURSEWARE_PUBLIC_BASE_URL 派生,不使用内网 request Host。失败响应稳定包含 errorCode、phase(request/
|
||
staging/commit/rollback)、可选 moduleIndex 和 details。
|
||
|
||
未配置 COURSE_PUBLISH_SERVER_BASE_URL 时,当前单仓运行仍使用同源共享文件库,但复用同一
|
||
批量事务/幂等/visibility helper;该 fallback 是迁移兼容,不是多实例事务。
|
||
|
||
## 5. 文件仓库为何只能单实例
|
||
|
||
当前正确性依赖以下进程内状态:
|
||
|
||
- courseware publishLocks;
|
||
- CourseRecord courseLocks;
|
||
- CourseManifest manifestLocks;
|
||
- 大课 runningRuns、AbortController 和当前 classroom job 集合;
|
||
- 版本号先读历史、再加一、再写回的 read-modify-write。
|
||
|
||
文件层虽使用原子 rename,bundle 使用 link 实现 create-if-absent,但 Map mutex 只在单进程内
|
||
有效。两个容器可能同时读到相同 nextVersion,或覆盖彼此刚写的 JSON history。
|
||
当前“事务”是失败后把本批记录补偿回 unpublished,不具有数据库事务的跨进程隔离性、
|
||
原子提交或崩溃一致性。
|
||
|
||
当前生产约束:
|
||
|
||
1. courseware、manifest、framework 的写入实例都保持 1;
|
||
2. 不用多副本共享 NFS 来伪装数据库事务;
|
||
3. 发布和生成期间避免滚动重启;
|
||
4. 备份整个版本历史 JSON 与 bundle 目录,并保持二者一致;
|
||
5. classroom/course runner 重启后需要通过记录自愈,再由运营点击继续/重试。
|
||
|
||
这些限制意味着:只要计划公网生产上线或多副本扩容,下述 Postgres 事务、
|
||
唯一约束与私有对象存储就是硬前置,不是可选性能优化。
|
||
|
||
未来多实例的最低实现:
|
||
|
||
- Postgres 表为 courseware_versions 和 course_manifest_versions 建唯一键;
|
||
- 在同一事务内锁定 course/courseware、分配 next version、写 metadata 与状态;
|
||
- bundle 写对象存储,key 包含 id/version/hash,使用 put-if-absent;对象在 manifest commit
|
||
前必须保持私有,CDN/签名 URL 也必须执行等价的 exact-pin visibility gate;
|
||
- manifest 只在全部对象与 metadata 可读后提交;
|
||
- 下载 URL 指向对象存储/CDN;
|
||
- 生成任务进入持久队列,worker 使用租约/幂等键;
|
||
- 失败补偿只改变本批 staged 记录,不覆盖历史已发布版本。
|
||
|
||
## 6. legacy manifest 重发操作
|
||
|
||
识别方式:
|
||
|
||
- GET /api/learn/courses/{id} 返回 409;
|
||
- 错误为 legacy course manifest must be republished;
|
||
- 文件可能是旧 singleton,也可能已经进入 history envelope,但记录本身没有
|
||
schemaVersion=2 或模块缺 version/hash。
|
||
|
||
安全重发流程:
|
||
|
||
1. 保留旧 manifest 文件并备份,不直接编辑旧版本。
|
||
2. 在 ops 打开对应 CourseRecord,确认 framework 和模块 classroom 仍存在。
|
||
3. 运行发布校验;如提示 MODULE_OUTPUT_UNVERIFIED、CONTINUITY_UNVERIFIED、
|
||
CONTINUITY_STALE、INTERACTIVE_HTML_MISSING 或缺音频/媒体,从最早失败模块开始
|
||
按顺序重生成。
|
||
4. 人工审阅修复后的模块,再由 ops 执行“发布课程”。
|
||
5. 新发布会生成下一 manifest 版本,schemaVersion=2,每模块固定
|
||
coursewareId + coursewareVersion + contentHash。
|
||
6. 用精确 manifest version 访问 learner 页进行 smoke。
|
||
7. 旧无 pin 版本保留审计,但继续拒绝 learner;不要把当前 latest 手工写成旧历史 pin。
|
||
|
||
若原 CourseRecord/classroom 已丢失,不能伪造历史身份。应按新课程重新生成、审阅、发布。
|
||
|
||
## 7. 机械拆分顺序与每步门槛
|
||
|
||
### Step 0:冻结基线
|
||
|
||
- 固定 bundle format、manifest schema v2、CourseRecord compat 和 protected core 清单;
|
||
- 保存当前针对性测试结果;
|
||
- 不在拆分 PR 中改变 Chat、Stage、Scene、Action、PBL 或白板语义。
|
||
|
||
回滚门槛:任何 learner bundle 无法进入原 Stage,立即停止拆分。
|
||
|
||
### Step 1:共享契约
|
||
|
||
- 移动 bundle types/hash/serialize、course manifest types、API DTO;
|
||
- 把环境读取留在应用 adapter,纯契约包不读取 process.env;
|
||
- ops、server、learner 同时消费同一版本,禁止复制类型。
|
||
|
||
回滚门槛:同一 fixture 的 contentHash 或 imported document 发生变化。
|
||
|
||
### Step 2:先拆 server
|
||
|
||
- 搬 app/api/coursewares/**、app/api/learn/courses/**、
|
||
lib/courseware-repo、lib/course-manifest-repo 和 bundle byte store;
|
||
- 新增只接受 server token 的 course manifest 提交接口;
|
||
- 现有 ops 先通过 HTTP adapter 调新 server,保留同源 adapter 作为短期回滚;
|
||
- reader 先迁、writer 后迁,确认旧 manifest 409 策略一致。
|
||
|
||
回滚门槛:精确 version/hash 查询、不可变下载头或发布门禁不一致。
|
||
|
||
### Step 3:先完成 DB/对象存储,再扩 server
|
||
|
||
- 文件 repo 双写只用于迁移核对,不作为长期架构;
|
||
- 对比记录数、版本历史、hash 与对象字节;
|
||
- 切读后保留文件只读备份,确认稳定再停双写;
|
||
- 未完成事务和唯一键前保持单实例。
|
||
|
||
### Step 4:再拆 learner desktop
|
||
|
||
- 整体搬 app/page、generation-preview、tasks、learn、classroom 和其依赖;
|
||
- 原 Stage 与 protected core 作为一个共享包/工作区依赖整体复用,不 fork;
|
||
- 只替换 API base URL、鉴权 adapter 与文件路径;
|
||
- 保留首页普通后台与材料/Interactive/职教前台双路径。
|
||
|
||
回滚门槛:出现第二套助教、发布课堂带 outlines、或 learner 不再进入原 Stage。
|
||
|
||
### Step 5:最后拆 ops
|
||
|
||
- 搬 courses UI、course-framework、publish-validation、module-digest 和发布发起端;
|
||
- classroom 生成/审阅仍使用同一单课件 agent 与 protected core;
|
||
- 浏览器只持 ACCESS_CODE 会话,不持 server token;
|
||
- 完成 ops → server 的 bundle 与 manifest transport。
|
||
|
||
### Step 6:收口
|
||
|
||
- 删除 all 的生产支持和同源 repo 直写;
|
||
- 给每个 app 配置显式 route allowlist、独立构建和最小 provider key;
|
||
- 做一次 legacy 重发、并发发布、失败回滚和旧 manifest 固定版本演练。
|
||
|
||
## 8. 验收命令
|
||
|
||
在 /Users/inmanw/项目/麦洛学习/OpenMAIC 执行。
|
||
|
||
### 类型与格式
|
||
|
||
pnpm exec tsc --noEmit
|
||
pnpm exec prettier ../docs/architecture-3-tier.md ../docs/deployment-3-tier.md ../docs/large-course-mode.md ../docs/handover.md --check
|
||
|
||
### 三端、大课、冻结发布定向回归
|
||
|
||
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
|
||
|
||
### learner/ops 浏览器边界
|
||
|
||
pnpm exec playwright test \
|
||
e2e/tests/home-to-generation.spec.ts \
|
||
e2e/tests/deployment-role-boundary.spec.ts \
|
||
--project=chromium
|
||
|
||
Playwright 默认 webServer 已显式固定 learner 角色。若本机已有 3002 端口的旧 dev server,
|
||
先确认它的角色,避免 reuseExistingServer 让边界测试误连 all/ops。
|
||
|
||
### 构建
|
||
|
||
pnpm build
|
||
|
||
### 读路径压测
|
||
|
||
node scripts/load-test-reads.mjs + --base http://localhost:3103 + --courseware <published-id> + --concurrency 20 + --requests 400
|
||
|
||
## 9. 已知受保护 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
|
||
|
||
当前结果为 3 files 中 40 passed / 3 failed;失败正好是:
|
||
|
||
- prompts:keeps concept and mechanism questions teacher-led before student reactions
|
||
- route-cue-user:hands successful web evidence to one child and clears it before later delegations
|
||
- route-cue-user:does not leak consumed web evidence after the selected child fails
|
||
|
||
它们属于既有 protected Chat director 的 prompt/evidence 断言漂移,本次三端改造未触及
|
||
lib/chat/** 或 /api/chat/pi。验收报告应原样记录这 3 项;不要在本改造中修改 Chat
|
||
内核或放宽测试来掩盖它们。
|