feat(education): establish operational independence contracts

This commit is contained in:
2026-07-31 12:54:08 +08:00
parent 428d4e10fd
commit f42ef76527
24 changed files with 740 additions and 20 deletions

View File

@@ -1,9 +1,9 @@
# EDU-015 — Operational independence and legacy exit
- **Status:** blocked
- **Status:** implemented-contracts
- **Type:** integration and deployment program
- **Phase:** 6
- **Blockers:** EDU-011, EDU-013, EDU-014 child decisions, all temporary-adapter owners and exit plans
- **Blockers:** production deployment evidence and implementation of each future EDU-011 worker/scanner workload
## Outcome
@@ -18,6 +18,17 @@ The target backend runs its selected education capabilities without depending on
- Reconcile migrated data and operational runbooks.
- Prove deployment, startup, Flyway, and core user flows.
## Delivered contracts
- `/admin-api/education/operations/health` exposes the runtime dependency registry plus durable Worker/Scanner and open dead-letter summaries.
- `JAVA_READ` performs a target-only PostgreSQL schema readiness proof; Scalar is explicitly `NOT_SELECTED` in that mode.
- `SCALAR_READ` is marked legacy, required only when selected, uses `FAIL_CLOSED`, and records success/failure telemetry without exposing URL or credentials.
- V4170 owns durable `education_operational_component` and tenant-scoped `education_dead_letter` contracts without retaining business payloads.
- `EducationTenantContextPropagation` re-establishes and restores tenant context for executor tasks.
- `tools/education-target-smoke/java-read-readiness.sh` asserts target-only provider selection against a running target deployment.
Production migration and deployment evidence remain release activities and are not claimed by this change.
## Acceptance criteria
- [ ] Every temporary legacy dependency has an owner, telemetry, failure policy, and exit date.

View File

@@ -13,18 +13,19 @@ yudao:
catalog-read-enabled: true
practice-write-enabled: true
pilot-tenant-ids: [<pilot-tenant-id>]
catalog-mode: SCALAR_READ
catalog-mode: JAVA_READ
scalar:
enabled: true
base-url: ${EDUCATION_SCALAR_BASE_URL}
token: ${EDUCATION_SCALAR_TOKEN}
enabled: false
owner: <required-only-when-scalar-read>
exit-date: <yyyy-MM-dd>
```
要求:
- `pilot-tenant-ids` 在 Pilot 环境必须显式配置,不能使用空列表。
- Scalar token 只能通过密钥管理或环境变量注入,不写入仓库、日志或测试报告
- 发布前调用管理端 `/admin-api/education/capability`,核对模块、题库读取、练习写入和 Pilot 租户数量
- Pilot 默认以 `JAVA_READ` 启动,只依赖目标 PostgreSQL不得启动旧 NestJS API、旧 Worker、Supabase 或旧资产扫描服务作为前置条件
- 仅在有明确负责人、告警、故障策略和退出日期的兼容窗口内切换 `SCALAR_READ`。Scalar token 只能通过密钥管理或环境变量注入
- 发布前调用管理端 `/admin-api/education/capability``/admin-api/education/operations/health`;后者必须显示 `java-read-postgresql=UP``scalar-catalog=NOT_SELECTED`
## 3. 发布步骤
@@ -32,9 +33,10 @@ yudao:
2. 使用 Server 配置的 PostgreSQL Flyway 执行 migrate 和 validate检查版本、脚本、checksum 与 success不得手工应用 Education SQL 或执行 rollback SQL。
3. 先以 `catalog-read-enabled=false``practice-write-enabled=false` 部署应用。
4. 验证 System、Infra、Member 基础 smoke。
5. 仅对 Pilot 租户开启题库读取,完成 Scalar 只读 smoke
5. 仅对 Pilot 租户开启题库读取,完成 JAVA_READ 只读 smoke并运行 `tools/education-target-smoke/java-read-readiness.sh`
6. 对 Pilot 租户开启练习写入,完成会话、答案、交卷、报告、错题和收藏 smoke。
7. 观察错误率、延迟和数据库写入后再扩大租户列表
7. 若部署 Worker 或 Scanner先确认其持续写入 `education_operational_component`,且 `/admin-api/education/operations/health``DOWN` 组件和未处理死信
8. 观察错误率、延迟和数据库写入后再扩大租户列表。
## 4. Smoke 清单
@@ -60,7 +62,17 @@ yudao:
## 5. 故障与回滚
### Scalar 故障
### JAVA_READ / PostgreSQL 故障
1. 设置 `catalog-read-enabled=false`,停止新的目录和题目读取;不得静默切回 Scalar。
2. 保持 `enabled=true`,使已有会话、报告、错题和收藏仍可访问。
3. 检查 `/admin-api/education/operations/health``java-read-postgresql` 结果和 Flyway 历史。
4. 如需冻结新写入,再设置 `practice-write-enabled=false`
5. 通过应用回滚或更高版本 Flyway 前滚修复,不执行 `flyway clean` 或手工回滚 SQL。
### Scalar 兼容窗口故障
仅当部署明确选择 `SCALAR_READ` 时适用:
1. 设置 `catalog-read-enabled=false`,停止新的 Scalar 读取。
2. 保持 `enabled=true`,使已有会话、报告、错题和收藏仍可访问。
@@ -88,7 +100,10 @@ yudao:
发布窗口至少观察:
- Scalar 请求成功率、4xx/5xx/timeout、P95/P99 延迟
- `/admin-api/education/operations/health` 的必需依赖、Worker/Scanner 心跳和 open dead-letter 数
- `education_operational_component``last_heartbeat_at`、最后成功/失败和 backlog
- `education_dead_letter` 仅保留负载指纹与脱敏错误分类,不得保存业务 payload、凭据或个人数据
- Scalar 兼容模式请求成功率、4xx/5xx/timeout、P95/P99 延迟;
- 练习创建成功/冲突数;
- 答案保存成功、幂等重放、版本冲突和旧序号拒绝数;
- 交卷成功、并发冲突和事务失败数;
@@ -97,6 +112,14 @@ yudao:
Scalar 日志只能记录脱敏路径、tenant ID、上游 request ID、状态、耗时和错误分类不得记录 Authorization、Scalar token、学生答案、正确答案或完整响应体。RuoYi access/error log 中的 trace ID 用于关联入口请求;验收时需保存一条从入口日志到 Scalar request ID 的关联证据。
### Worker、Scanner 与死信处置
1. Worker/Scanner 每次心跳使用固定 `component_key` upsert部署实例变化写入 `instance_id`
2. 心跳状态只能为 `STARTING/UP/DEGRADED/DOWN``detail` 必须脱敏且有界。
3. 重试耗尽后写入 `education_dead_letter`同一租户、组件、workload 只允许一个 OPEN 记录。
4. 排障后先把原 OPEN 记录标记为 `REQUEUED` 并填写 `resolved_at`/`resolution_note`,再通过所属业务服务重入队;禁止直接修改业务结果或把原 payload 写入死信表。
5. 未部署对应 Worker/Scanner 时不得伪造 UP 心跳;能力清单应保持未交付状态。
## 7. 验证命令
```bash