Parcourir la source

chore: auto bump version and changelog [skip ci]

iwt il y a 2 jours
Parent
commit
3eb7865259

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-027ca7de8b00f0e19541d120fd30fa791ad5c9c9
+be2f5e837aabcae82593f82389a9daf20612ed2e

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1494",
+  "version": "1.0.1495",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 13 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,19 @@
 
 ---
 
+## v1.0.1495 (2026-10-04)
+
+### Bug 修复
+- 修复后端编译错误 - 通知功能语法错误 + 日志变量遮蔽 + KeyPersonService 缺 logger
+
+### 其他
+- 2. PointsService: 局部变量 PointsLog log 遮蔽 SLF4J logger → 改名 plog
+- 3. EnergyService: deductEnergy 引用不存在的 sourceType 变量 → 固定为 'manual'
+- 4. KeyPersonService: 补充 @Slf4j(新增能量奖励日志需要)
+- 修复后 mvn clean compile 通过(BUILD SUCCESS)
+- 
+
+
 ## v1.0.1494 (2026-10-04)
 
 ### Bug 修复

+ 14 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1494
+> 当前版本: v1.0.1495
 
 ## 历史版本
 
@@ -8,6 +8,19 @@
 
 ---
 
+## v1.0.1495 (2026-10-04)
+
+### Bug 修复
+- 修复后端编译错误 - 通知功能语法错误 + 日志变量遮蔽 + KeyPersonService 缺 logger
+
+### 其他
+- 2. PointsService: 局部变量 PointsLog log 遮蔽 SLF4J logger → 改名 plog
+- 3. EnergyService: deductEnergy 引用不存在的 sourceType 变量 → 固定为 'manual'
+- 4. KeyPersonService: 补充 @Slf4j(新增能量奖励日志需要)
+- 修复后 mvn clean compile 通过(BUILD SUCCESS)
+- 
+
+
 ## v1.0.1494 (2026-10-04)
 
 ### Bug 修复

+ 1 - 0
docs/superpowers/PROJECT-OVERVIEW.md

@@ -476,6 +476,7 @@
 | `2026-09-12-doa-family-goal-design.md` | 🟢 设计完成 | DOA 家庭打卡个人目标-周计划-周复盘设计(并列共同挑战新模块,5 领域/自选周期/家人可见/代管不能登录成员;设计稿:2026-09-12-doa-family-goal-design.md) |
 | `2026-09-12-vendor-order-review-design.md` | ✅ 已实施 | 供应商订单审核设计(pending 订单可修改价格/配送方式/添加临时优惠券/赠品,自动重算金额) |
 | `2026-09-19-task-orchestration-design.md` | 🟡 设计已确认(待写实现计划) | 任务编排系统设计(含环 DAG:多前置 AND/OR + 后置 timeout/failed 兜底 + 计数/条件循环终止 + Quartz 轮询 + jsPlumb 可视化画布;节点执行创建 tasks 实例复用积分体系;4 张新表) |
+| `2026-10-04-plan-review-workbench-design.md` | 🟡 设计已确认(待写实现计划) | 规划师审核工作台设计(A1:新增 6 个 `/api/guide/families/*` 端点 + 3 个前端页面;授权源统一为 `guide_families`(`status='binding'`);修复 3 处阻塞缺陷——`GuideFamilyTaskController` 7 个带 `{familyId}` 端点跨家庭越权、`confirmBind` 重绑不复活 status、`rejectPlan` 缺状态守卫;并使 approve 任务生成失败不再静默。轨 A 后续:A2 提醒/消息中心、A3 测评链路;轨 B:B1 合伙人分佣、B2 免费/收费服务合同) |
 
 ---
 

+ 408 - 0
docs/superpowers/specs/2026-10-04-plan-review-workbench-design.md

@@ -0,0 +1,408 @@
+# 规划师审核工作台设计(A1)
+
+- 日期:2026-10-04
+- 状态:设计已确认,待实现
+- 范围:轨 A 子项目 A1(服务交付链路补全的第一块)
+
+---
+
+## 1. 目标
+
+补齐规划师侧的「方案审核」与「任务审核」前端工作台,并修复阻塞该功能的三类后端缺陷:跨家庭越权、绑定重绑失效、驳回缺状态守卫。
+
+完成后,家长保存的 `pending_review` 方案能被绑定该家庭的规划师实际看到并审核,审核通过后自动拆解为每日任务;用户提交的待审任务也能被规划师审核。
+
+**本子项目不含**:待审核提醒(A2)、测评链路断点修复(A3)、合伙人分佣(B1)、免费/收费服务合同(B2)。
+
+---
+
+## 2. 现状核实结论
+
+以下均为代码核实结果,非推测。
+
+### 2.1 方案审核后端已就绪,前端完全缺失
+
+`HealthPlanController.java:166-228` 已有四个端点:
+
+| 端点 | 行号 |
+|---|---|
+| `POST /api/health/plan/pending-review/list` | 166 |
+| `POST /api/health/plan/pending-review/update` | 183 |
+| `POST /api/health/plan/pending-review/approve` | 199 |
+| `POST /api/health/plan/pending-review/reject` | 215 |
+
+前端封装 `getPendingReviewPlans()` 位于 `cfc-frontend/utils/api.js:2536`,**无任何页面消费**。
+
+### 2.2 阻塞缺陷一:GuideFamilyTaskController 跨家庭越权
+
+`controller/guide/GuideFamilyTaskController.java` 共 10 个 `@PostMapping` 端点,其中 **7 个带 `{familyId}` 路径参数且完全无归属校验**——只检查 `role == "teacher"`,路径参数 `familyId` / `memberId` / `taskId` 直接进业务查询。`guideFamilyService` 在该控制器中仅被 `unbindByGuide` 使用(`GuideFamilyTaskController.java:51,69`)。
+
+受影响端点(`@PostMapping` 行号):
+
+| 端点 | 行号 | 危害 |
+|---|---|---|
+| `/{familyId}/overview` | 177 | 读任意家庭概览 |
+| `/{familyId}/children/{memberId}/tasks` | 245 | 读任意成员任务 |
+| `/{familyId}/children/{memberId}/pending-review` | 270 | 读任意家庭待审任务 |
+| `/{familyId}/tasks/{taskId}/review` | 290 | 审核任意家庭任务 |
+| `/{familyId}/tasks/batch-review` | 315 | 批量审核任意家庭任务 |
+| `/{familyId}/tasks/{taskId}` | 372 | 改任意家庭任务 |
+| `/{familyId}/tasks/{taskId}/delete` | 423 | 删除任意家庭任务 |
+
+其余 3 个端点无需绑定校验:`/unbind`(57)已具备归属校验(G6 已修);`/bound-families`(106)与 `/dashboard-stats`(453)只读调用者自身数据,不接受 `familyId` 参数。
+
+任何 `role=teacher` 的登录用户修改 URL 中的 ID 即可越权访问其他家庭。
+
+**前端无消费方(已核实)**:`cfc-frontend` 中仅 `utils/api.js:891`(`/api/guide/families` 列表)与 `utils/api.js:901`(`/unbind`)调用了本控制器的端点,上表所有家庭任务路径**无任何页面引用**。规划师任务页 `pages/teacher/service-tasks.vue:107` 实际调用的是另一域的 `/api/dan-execution/guide/my-tasks`。
+
+因此该缺陷当前**无已发布的 UI 路径可利用**,但任何持有 teacher token 的调用方仍可直接构造请求越权。修复必要性来自「新增审核台即将成为首个消费方」——若不先修,新页面会直接建在漏洞之上。
+
+### 2.3 阻塞缺陷二:confirmBind 重绑后永久失效
+
+`service/GuideFamilyService.java:74-87`:按 `guide_id + family_id` 查询时**未加 status 过滤**。命中已存在的行后,只更新 `serviceType` / `servicePrice` / `updatedAt`,**从不将 status 复位为 `binding`**。
+
+而 `isBound()`(`GuideFamilyService.java:152-160`)只认 `status = 'binding'`。解绑将 status 置为 `cancelled` 后重新绑定,该记录永久停留在 `cancelled`,授权源随之失效。
+
+### 2.4 阻塞缺陷三:rejectPlan 缺状态守卫
+
+`HealthPlanServiceImpl.java` 中:
+
+| 方法 | 行号 | 状态守卫 |
+|---|---|---|
+| `updatePlanContent` | 570 | 有:`pending_review` / `draft` |
+| `approveAndPublish` | 584 | 有:`pending_review` / `draft` |
+| `rejectPlan` | 602 | **无** |
+
+已发布或已驳回的方案可被反复驳回。
+
+### 2.5 approve 的任务生成失败被静默吞掉
+
+`HealthPlanServiceImpl.java:593-597`:`status` 已置为 `published` 之后,`generateDailyTasksFromPlan(plan)` 的异常仅 `log.warn` 捕获。结果是方案已发布但一条任务都没生成,用户与规划师均无感知。
+
+### 2.6 三套并行绑定模型,两条互不相通的流程
+
+| 模型 | 写入方 | 读取方 |
+|---|---|---|
+| `families.teacher_id` | `InviteCardService.java:227` | `AdminController.java:931/933`、`CircleMatchService.java:383`、`HealthPlanServiceImpl.java:712-714` |
+| `users.teacher_family_ids`(逗号分隔文本) | `InviteCardService.java:242` | `AdminController.java:688/1240/1381`、`GuideFamilyListController.java:46/50`、`GuideFamilyTaskController.java:112/117/462/463` |
+| `guide_families`(有 status/serviceType/servicePrice) | **仅** `GuideFamilyService.confirmBind`,经 `BindController.java:46`、`BindInviteService.java:76` | `isBound()`、`unbindByGuide` |
+
+已核实 `confirmBind` 不同步前两者(`GuideFamilyService.java` 中无任何 `setTeacherId` / `setTeacherFamilyIds` 调用)。因此存在两条独立流程:
+
+- **邀请卡流程**(`InviteCardService`)→ 写 `families.teacher_id` + `teacher_family_ids` → 决定 `bound-families` 列表
+- **绑定合同流程**(`BindController` → `confirmBind`)→ 写 `guide_families` → 决定 `isBound()`
+
+一个家庭可以出现在规划师的 `bound-families` 列表中,同时 `isBound()` 返回 false。
+
+---
+
+## 3. 关键决策
+
+### 3.1 授权源:`guide_families` 且 `status = 'binding'`
+
+| 候选 | 是否采用 | 理由 |
+|---|---|---|
+| `guide_families` | **采用** | 唯一具备完整生命周期(`binding`/`paused`/`cancelled`)且解绑真正生效的模型;也是 B1(引荐关系)、B2(免费/收费合同)将要扩展的同一张表 |
+| `users.teacher_family_ids` | 拒绝 | 解绑时不撤销:`unbindByGuide`(`GuideFamilyService.java:122`)只改 `guide_families`。用作授权等于规划师永久保留前客户数据访问权 |
+| `families.teacher_id` | 拒绝 | 无状态机,解绑后同样不清理,无法表达暂停/到期 |
+
+**接受的代价**:仅通过邀请卡绑定、从未走绑定合同的家庭,规划师在审核台看不到。这是数据一致性问题,需一次把 `guide_families` 补齐到与 `teacher_family_ids` 一致的数据迁移。**该迁移不属于 A1**,列为独立后续任务。
+
+### 3.2 方案审核端点:新增 `/api/guide/families/{familyId}/plans/*`
+
+原 `/api/health/plan/pending-review/*` **保留不动**,不标 410。
+
+原因:该组端点的 `familyId` 取自 `@RequestAttribute`(`HealthPlanController.java:171`),而 `JwtInterceptor.java:143-158` 从 `users.family_id` 服务端反查注入,不受请求参数控制。这使它天然服务「规划师作为家长审自家方案」场景。改造它以支持客户家庭会与自用场景逻辑互斥——规划师通常未与自己的家庭签服务合同,加绑定校验后反而无法审自家方案。
+
+新增端点把「规划师对客户的服务」收敛到 `/api/guide` 域,与任务审核同一套路径结构。
+
+### 3.3 未分配方案的可见范围
+
+用户决策:
+
+- **已绑定该家庭的规划师** → 可见该家庭方案的**全文**(含 `plan_content`、`plan_json`、`member_ids`、`member_name`),无论 `teacher_id` 是自己还是 NULL
+- **未绑定规划师的家庭,其未分配方案** → **仅 admin 可见**
+- 未绑定规划师 → 不可见
+
+因此不存在面向一线规划师的公开认领池,孤儿方案由 admin 人工指派兜底。
+
+### 3.4 审核台首页不使用 bound-families
+
+`/api/guide/families/bound-families` 读 `users.teacher_family_ids`,与授权源 `guide_families` 不同源,直接使用必然出现「列表里有、点进去 403」。
+
+审核台首页改用新增的「我的服务家庭」查询(按 `guide_families` 查),保证列表与授权同源。
+
+### 3.5 approve 的任务生成失败不再静默
+
+改为返回部分成功状态(`published=true, taskGenerated=false`),前端明确提示。理由:静默失败会让用户看到「方案已发布」却永远等不到任务。
+
+### 3.6 客户家庭成员列表需新增 guide 域端点
+
+项目已有成员列表统一入口 `POST /api/family/member/list`(`FamilyMembersController.java:62`,类级 `@RequestMapping("/api/family/member")`),按 `cfc-backend/AGENTS.md` 它是「唯一入口」。但它是 **JWT 作用域**的:
+
+```java
+// FamilyMembersController.java:63-79
+public Result<?> listMembers(@RequestBody(required = false) Map<String, Object> params,
+                             ...) {
+    Long userId = ...;                       // 来自 token
+    return Result.success(familyMemberService.listMembersWithFamily(userId));
+}
+```
+
+`familyId` 由 token 中的 `userId` 反查,**不接受调用方指定**。因此规划师调用它拿到的是**自己家**的成员,不是客户家的。
+
+`cfc-backend/AGENTS.md` 另列出 `POST /api/family/user/children/list`、`POST /api/user/children/list` 为**已废弃**(重复路由、只返回孩子),不得新增调用——本项目不去动它们。
+
+`/api/guide` 域不存在客户家庭成员列表端点:`GuideFamilyListController.java:65` 只统计 `memberCount`,不返回成员明细。
+
+**决策**:不去改共享的 `/api/family/member/list`。该端点被全角色所有页面共用,给它加可选 `familyId` 会让「普通家长能否传别人的 familyId」这个口子出现在全站最热的成员接口上,授权校验成本远高于收益。改为在 guide 域新增 `POST /api/guide/families/{familyId}/members`,显式接收 `familyId` 并只对它做绑定校验。
+
+这与 3.2 中 `HealthPlanController` 的 `familyId` 属同一类问题(服务端从 token 反查,与目标家庭无关),因此一致性要求两边都收敛到显式 `familyId` + 绑定校验。
+
+---
+
+## 4. 权限模型
+
+### 4.1 新增端点
+
+| 端点 | 用途 |
+|---|---|
+| `POST /api/guide/families/{familyId}/plans/pending` | 待审方案列表 |
+| `POST /api/guide/families/{familyId}/plans/{planId}/update` | 编辑方案内容 |
+| `POST /api/guide/families/{familyId}/plans/{planId}/approve` | 通过并发布 |
+| `POST /api/guide/families/{familyId}/plans/{planId}/reject` | 驳回 |
+| `POST /api/guide/families/{familyId}/members` | 客户家庭成员列表(见 3.6) |
+
+另需「我的服务家庭」端点 `POST /api/guide/families/my-families`,供审核台首页使用(见 3.4)。
+
+合计新增 6 个端点。
+
+### 4.2 授权规则
+
+统一收敛到一个私有方法,避免 7 个既有端点与 6 个新增端点各自散写:
+
+```
+role != teacher && role != admin   → 403 Access denied
+role == teacher → isBound(userId, pathFamilyId) 必须为 true,否则 403
+role == admin   → 跳过绑定校验
+```
+
+### 4.3 IDOR 防护
+
+`{planId}` 类端点**不信任前端传入的 familyId**:
+
+1. `selectById(planId)` 取出 `plan.getFamilyId()`
+2. 与路径 `familyId` 比对,不一致 → 403
+3. 用**反查出的** familyId 做绑定校验
+
+否则改 URL 即可越权,与 2.2 所修漏洞同型。
+
+### 4.4 列表查询条件
+
+```
+family_id = :familyId
+AND status IN ('draft', 'pending_review')
+AND (teacher_id = :userId OR teacher_id IS NULL)
+ORDER BY created_at DESC
+```
+
+- `teacher` 角色:`userId` 取 JWT,不接受前端指定
+- `admin` 角色:请求体可选传 `teacherId` 过滤;不传则返回该家庭全部
+- 沿用 `HealthPlanServiceImpl.listPendingReviewPlans`(`:557-564`)的查询构造
+
+---
+
+## 5. 页面结构
+
+任务审核端点为 `/{familyId}/children/{memberId}/pending-review`,必须先有 familyId 与 memberId,因此审核台不能做成「进入即跨家庭罗列任务」。
+
+```
+pages/teacher/review-workbench.vue           审核台首页:我的服务家庭列表
+pages/teacher/review-family.vue?familyId=X   单家庭双 Tab(方案 / 任务)
+pages/teacher/plan-detail.vue?planId=X&familyId=X   方案全文 + 编辑 + 通过/驳回
+```
+
+### 5.1 review-workbench.vue(首页)
+
+- 数据源:新增「我的服务家庭」端点(按 `guide_families` 查询,返回 familyId / familyName / serviceType / servicePrice / boundAt)
+- 点击家庭 → `review-family?familyId=X`
+- 空态:尚未绑定任何家庭
+
+### 5.2 review-family.vue(单家庭双 Tab)
+
+**Tab 1 方案**
+
+- 拉 `/api/guide/families/{familyId}/plans/pending`
+- 每条标记「已分配给我」/「未分配(我的客户)」,依据 `plan.teacherId` 是否等于当前 userId
+- 点击 → `plan-detail?planId=X&familyId=Y`
+- 批量审核:**本版本不做**(方案含富文本正文,逐条审核更合适;任务审核才需要批量)
+
+**Tab 2 任务**
+
+- 先选 member,数据源 `/api/guide/families/{familyId}/members`(新增,见 3.6)
+- 再拉 `/api/guide/families/{familyId}/children/{memberId}/pending-review`
+- 支持批量通过/驳走,复用 `/{familyId}/tasks/batch-review`
+
+### 5.3 plan-detail.vue(方案详情)
+
+- 展示 `planContent` 全文 + `planJson` 渲染内容
+- 操作:通过 / 驳回(均需填写 comment)/ 编辑保存
+- 时间显示遵循项目规范:`createdAt` / `reviewedAt` 取 `substring(0,19)`,将 `T` 替换为空格
+- 小程序限制:`:key` 用方法调用,不用可选链 `?.`,用 flexbox 不用 CSS Grid
+
+---
+
+## 6. 数据流
+
+```
+审核台首页
+  └─ /api/guide/families/my-families(guide_families)
+       └─ review-family?familyId
+            ├─ Tab1 方案
+            │    ├─ plans/pending → 列表(标记是否分配给我)
+            │    └─ plan-detail
+            │         ├─ update   → 反查 plan.familyId → 校验 isBound → 改内容
+            │         ├─ approve  → 反查 + 校验 → status=published + 记录审核人
+            │         │              → generateDailyTasksFromPlan(后端自动)
+            │         │              → 返回 published / taskGenerated
+            │         └─ reject   → 反查 + 校验 → status=rejected + 记录审核人
+            └─ Tab2 任务
+                 ├─ /api/guide/families/{fid}/members → 选 member
+                 └─ families/{fid}/children/{mid}/pending-review → 批量审核
+```
+
+approve 成功后前端提示「已发布并生成 N 条任务」;若 `taskGenerated=false`,提示「方案已发布但任务生成失败,请重试」。
+
+---
+
+## 7. 错误处理
+
+| 情况 | 后端行为 | 前端行为 |
+|---|---|---|
+| 未绑定访问他人家庭 | 403 `Access denied` | toast「无权访问该家庭」 |
+| planId 不属于路径 familyId | 403 `Access denied` | toast「无权访问该方案」 |
+| 方案状态非 `pending_review`/`draft` | 业务错误「方案状态不允许审核」 | 提示后刷新列表 |
+| approve 时任务生成失败 | 返回 `published=true, taskGenerated=false` | 明确提示部分成功 |
+| 家庭无待审项 | 返回空数组 | 空态文案 |
+| 规划师未绑定任何家庭 | 返回空数组 | 空态文案 |
+
+统一在 `GuideFamilyTaskController` 与新增 `GuidePlanReviewController` 中使用同一私有校验方法,避免实现漂移。
+
+---
+
+## 8. 待修复缺陷清单(均属 A1 范围)
+
+| # | 位置 | 缺陷 | 修复 |
+|---|---|---|---|
+| 1 | `GuideFamilyTaskController.java` 7 个带 `{familyId}` 的端点 | 仅校验 role,无绑定归属校验 | 接入统一绑定校验 |
+| 2 | `GuideFamilyService.java:74-87` | 重绑不复活 status | 命中已有行时显式 `setStatus("binding")`、`setBoundAt(now)` |
+| 3 | `HealthPlanServiceImpl.java:602` | `rejectPlan` 缺状态守卫 | 补 `pending_review`/`draft` 校验 |
+| 4 | `HealthPlanServiceImpl.java:593-597` | 任务生成失败静默吞掉 | 捕获后标记 `taskGenerated=false` 并随响应返回 |
+
+缺陷 1、3、4 涉及 `HealthPlanServiceImpl`,缺陷 3、4 需调整方法签名或返回结构以传递 `taskGenerated`——实现时优先采用新增返回 DTO 而非修改 `HealthPlan` 实体。
+
+---
+
+## 9. 测试与验证
+
+### 9.1 后端授权单测(最高优先级)
+
+直接对应 2.2 所述漏洞:
+
+1. teacher A 未绑定家庭 F → `plans/pending` 返回 403
+2. teacher A 绑定家庭 F 后 → 200,且只返回 F 的方案
+3. plan 属于家庭 F2、路径写 F1 → 403(IDOR)
+4. teacher A 请求 admin 专属的全量查询 → 403
+5. teacher A 访问未绑定家庭 F 的 `bound-families` → 200 但不含 F
+6. teacher A 未绑定家庭 F → `/families/F/members` 返回 403(对应 3.6)
+7. teacher A 绑定家庭 F → `/families/my-families` 返回的列表含 F,且不含未绑定的家庭 G
+
+### 9.2 缺陷回归用例
+
+| 用例 | 期望 |
+|---|---|
+| `confirmBind` → `unbindByGuide` → `confirmBind` | `isBound()` 恢复 true |
+| 对 `status=published` 的方案调 `reject` | 抛业务错误 |
+| `approveAndPublish` 且任务生成抛异常 | 响应含 `taskGenerated=false` |
+| teacher A 未绑定 F 时访问 `/{F}/tasks/batch-review` | 403 |
+
+### 9.3 前端验证
+
+```bash
+node --check cfc-frontend/pages/teacher/review-workbench.vue
+node --check cfc-frontend/pages/teacher/review-family.vue
+node --check cfc-frontend/pages/teacher/plan-detail.vue
+node --check cfc-frontend/utils/api.js
+```
+
+### 9.4 编译与测试验证环境(实测结论)
+
+Maven **可用**,位于 `/bwydata/maven/bin/mvn`(Apache Maven 3.5.3,Java 1.8.0_492)。加入 PATH 即可:
+
+```bash
+export PATH=/bwydata/maven/bin:$PATH
+```
+
+实测结果:
+
+| 命令 | 结果 | 说明 |
+|---|---|---|
+| `mvn clean compile -DskipTests` | **exit 0 通过** | 主代码编译干净,无任何错误 |
+| `mvn test-compile` | **exit 1 失败** | 2 个既有测试文件无法编译 |
+
+主代码可编译,因此本项目的唯一认可验证方式在本环境**可直接执行**,不构成阻塞。
+
+`mvn test-compile` 失败的 2 个文件与 A1 无关,是既有的签名漂移:
+
+| 文件 | 错误 | 根因 |
+|---|---|---|
+| `src/test/java/com/etotem/cfc/integration/controller/ProductControllerTest.java`(行 79/82/103/106/125/128) | `actual and formal argument lists differ in length` | `ProductService.list` / `ProductController.list` 签名已增加第 3 个参数 `String role`(`ProductService.java:73`),测试仍按 2 参调用 |
+| `src/test/java/com/etotem/cfc/orchestration/OrchestrationEngineTest.java`(行 148/170) | `incompatible types: void cannot be converted to boolean` | `engine.onTaskCompleted` / `onTaskFailed` 已改为返回 `void`,测试仍赋值给 `boolean` |
+
+**影响**:测试代码无法编译 ⇒ 本仓库当前**任何**新测试都无法执行,TDD 被阻塞。两者均为机械性小修(补第 3 个参数、去掉 boolean 赋值改用 `verify`),因此将「修复这 2 个测试文件」列为实现计划的**前置任务**,修完即可跑通全量测试。
+
+### 9.5 更正记录
+
+本规格早前版本曾断言「HEAD 存在 3 处既有语法错误(`EnergyService.java:1880/1911`、`PointsService.java:280`),均为 `vars.put("amount", amount > 0 ? "+" : "") + amount;`」。
+
+该结论**错误**,系隔离 `javac`(未带 classpath)产生的解析级联误报。实际代码为:
+
+```java
+vars.put("amount", (amount > 0 ? "+" : "") + String.valueOf(amount));
+```
+
+拼接在 `put()` 调用内部,是合法 Java,`mvn clean compile` exit 0 已证实。此错误断言已从规格中移除。
+
+---
+
+## 10. 明确不做(附理由)
+
+| 项 | 理由 |
+|---|---|
+| 待审核提醒与消息中心 | 属 A2。已核实 `notification_templates` **有**种子数据(`DatabaseInitializer.java:9388`),真实缺口是 `sendByTemplate` 的调用方只覆盖 `DanReportUploadService:430`、`EnergyService:1883/1914`、`PlatformPointsService:246`、`PointsService:283`,**方案链路(`HealthPlanServiceImpl`)从未调用**。需独立设计触发时机 |
+| 测评链路断点(套餐真实化、上传后确认、规划师录分) | 属 A3,牵涉支付 |
+| 合伙人分佣 | 属 B1。现役 `CfCommissionService`(`referral_tree` + `cf_rate_tier` + `user_platform_balance`)与已废弃但仍被 5 个订单服务调用的 `CommissionService` 并存,需先定架构 |
+| 免费/收费服务合同 | 属 B2。`guide_families.service_price` 为绑定级价格,无显式 `service_mode` 语义 |
+| `InviteCardService.java:237` 子串匹配缺陷 | `currentIds.contains(newFamilyId)` 使家庭 "1" 误匹配 "11,21"。属邀请卡流程,与 A1 授权源无关,另开任务 |
+| `teacher_family_ids` 解绑不撤销 | 数据模型问题。若 A1 改用该字段做授权将造成越权,故明确拒绝;治理需改模型,另开任务 |
+| `guide_families` 数据补齐迁移 | 保证 `guide_families` 与 `teacher_family_ids` 一致。独立数据任务 |
+| admin 指派家庭规划师端点 | 用户已确认未绑定家庭的方案仅 admin 可见,指派为人工流程;端点在 B1 一并设计 |
+
+---
+
+## 11. 文档同步义务
+
+按 `docs/superpowers/AGENTS.md`,本次变更须同步:
+
+- `docs/superpowers/api/API_REFERENCE.md` — 新增 6 个端点(建议编号 4.47),并补记 `GuideFamilyTaskController` 的绑定校验口径
+- `docs/superpowers/PROJECT-OVERVIEW.md` — 同步上述变更说明
+
+本项目无 DDL 变更,故**不需要** `DatabaseInitializer` 迁移,**不需要**改 `schema.sql`。
+
+---
+
+## 12. 遗留风险
+
+1. **数据不一致**:仅走邀请卡流程绑定的家庭在审核台不可见,需数据迁移处理。在迁移完成前,审核台覆盖的家庭数会少于 `bound-families` 返回的数量。这是已知且可接受的阶段性状态,但需在实现后向业务方明示。
+2. **`taskGenerated` 传递方式**:`approveAndPublish` 当前返回 `HealthPlan`。改为返回 DTO 会影响 `HealthPlanController.java:199` 现有端点的响应结构,而该端点无前端消费者,因此可安全变更;需在实现时同步更新 `API_REFERENCE.md` 4.46 的响应描述。
+3. **越权漏洞的历史影响**:缺陷 1 存在于生产代码。已核实无前端消费方(见 2.2),故已发布的 UI 未暴露该路径,但 API 层仍可被直接调用。无法从代码判断是否发生过实际越权访问,建议查审计日志(若 `operation_log` 表有记录)后再上线。