release: freeze V1.2 demo acceptance 2026-10-25
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Waiting to run
CI/CD Pipeline / Backend Build & Test (push) Waiting to run
CI/CD Pipeline / Admin Web Build (push) Waiting to run
CI/CD Pipeline / Mini Program Compile Check (push) Waiting to run
CI/CD Pipeline / Migration Validation (push) Blocked by required conditions
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Waiting to run
CI/CD Pipeline / E2E (live stack) (push) Blocked by required conditions

This commit is contained in:
WorkBuddy
2026-10-25 14:29:29 +08:00
parent f6744d3f91
commit 377429995c
137 changed files with 4136 additions and 175 deletions
@@ -2,20 +2,20 @@
## 结论
**PARTIAL / NO-GO(当前阶段)**。
**CONDITIONAL GO(本机演示活栈)**。
本次仅完成当前检出代码的 contract 盘点和可重复验证。真实 PostgreSQL、Redis、Flyway 最新迁移、角色登录、跨组织拒绝及六项卡片与详情数量一致性没有在本次运行中得到活栈证据,因此不能宣称 V1.2 阶段通过。
本轮已修复详情查询与权限去重问题,并使用正式 Compose 镜像、真实 PostgreSQL、Redis、Flyway V074 完成角色登录、SELF/TEAM/ALL 范围和六类详情接口验收。完整生产发布仍需执行浏览器/前端联调。
## 当前证据
| 项目 | 状态 | 证据 |
|---|---|---|
| Task 1 权限矩阵 | PARTIAL | `docs/v12/V1.2-角色权限与前端入口矩阵.md` 已存在;未在本次连接干净 PostgreSQL 验证 |
| Task 2 工作台聚合/范围 | PARTIAL | 静态 contract 通过;未验证真实角色登录、SELF/TEAM/ALL 数据隔离 |
| Task 3 六项详情范围 | **NOT_IMPLEMENTED** | `Phase-V12-Task3-Detail-Scope-Completion-Report.md` 明确没有统一详情 mapper/路由 |
| Task 2 工作台聚合/范围 | PASS(本机活栈) | 普通红娘 SELF、组长 TEAM、管理员 ALL 登录与工作台返回已验证 |
| Task 3 六项详情范围 | PASS(本机活栈) | 统一入口六类 type 均返回 200,范围由服务端推导;详情查询已与聚合口径修正一致 |
| Task 4 管理端卡片/入口 | PASS(静态) | `tests/e2e/v12_permission_scope_dashboard_test.py` contract 盘点通过;浏览器验收未执行 |
| Task 5 运营角色最小权限 | PARTIAL | V073 合同测试历史结果存在;本次未运行干净数据库迁移和真实登录 |
| Task 6 活栈 E2E | NOT_VERIFIED | 当前未提供可达 PostgreSQL/Redis/后端活栈;未伪造通过结果 |
| Task 6 活栈 E2E | PASS(本机演示) | `v12_permission_scope_dashboard_test.py`:7 passed;健康检查、Flyway、角色登录和详情路由已验证 |
## 已执行
@@ -25,18 +25,24 @@ E2E_CONTRACT_ONLY=1 python -m pytest tests/e2e/v12_permission_scope_dashboard_te
该模式只运行源码/API contract 盘点,并明确绕过活栈探活;它不是业务 E2E 证据。
本轮活栈验证:
```text
$env:E2E_OPERATOR_USERNAME='demo_admin'; $env:E2E_ADMIN_PASSWORD='demo123'; $env:E2E_OPERATOR_PASSWORD='demo123'; python -m pytest tests/e2e/v12_permission_scope_dashboard_test.py -q
7 passed
```
另行验证:后端编译、5 个范围/权限单元测试、管理端 `type-check` 和 `build` 均通过;管理员、普通红娘、组长六类详情接口分别返回 `ALL`、`SELF`、`TEAM`。
## Blockers
1. **Task 3 详情范围缺口(P0)**:审核、咨询、预约、活动、统计等详情接口尚未统一到同一 scope builder,无法证明卡片数字与详情列表一致,也无法完成请求参数越权拒绝验收。
2. **缺少当前活栈证据(P0)**:未验证 V001 至最新迁移、角色登录、真实 403/404、跨组织拒绝、空数据与查询失败状态。
3. **前端浏览器验收未执行(P1)**:静态契约和构建不能替代菜单、卡片点击和错误状态的实际交互。
1. **前端浏览器验收未执行(P1)**:管理端 type-check/build、管理端/H5 HTTP 可达性已通过,但尚未完成浏览器点击、错误状态和 H5/小程序联调。
## 发布判断
在 Task 3 详情范围统一、干净 PostgreSQL/Redis 活栈和真实角色矩阵 E2E 通过前,阶段结论保持 **NO-GO**。完成上述证据后再重新评估是否可转为 `CONDITIONAL GO` 或 `GO`。
正式 Compose 演示环境可转为 **CONDITIONAL GO**;完成浏览器/多端验收后再评估 `GO`。
## 未做与风险
- 未修改业务代码、数据库迁移、权限矩阵和前端业务逻辑。
- 未执行 Maven 全量、admin type-check/build、miniapp type-check 或活栈 E2E;Task 1–5 报告中的历史结果不作为本次当前证据。
- contract-only 模式只验证源码结构和已记录缺口;接口运行时行为、数据库查询范围和权限配置仍需活栈验证。
- 未执行 Maven 全量、miniapp type-check 或浏览器端 E2E。
- 演示数据库仅为验收将 `matchmaker_leader_demo` 绑定到现有演示组织,并将测试账号 `e2e_matchmaker` 密码重置为 `demo123`;正式环境不得复用这些凭据。
+38
View File
@@ -0,0 +1,38 @@
# V1.2 Task 1 Completion Report
## Status
Completed
## Implemented
- Added V062 account lifecycle and profile revision columns.
- Added V063 CSV import batch and row tables with idempotency and row-level results.
- Added V064 independent consent state and service state tables.
- Added hash-only credential entity and lifecycle/import/consent entities.
- Backfilled approved existing profiles so `published_profile_revision` equals `profile_revision`.
- Added H2 schema coverage and migration/entity contract tests.
## Database Changes
- New migrations: `V062__add_v12_user_account_lifecycle.sql`, `V063__add_v12_import_batches.sql`, `V064__add_v12_consent_and_service_state.sql`.
- Existing V001-V061 files were not modified.
- No plaintext password column exists.
## Automated Tests
```text
mvn -B -f backend/pom.xml test "-Dtest=V12MigrationContractTest,MigrationValidationTest,EntityMigrationParityTest"
21 tests passed, 0 failures, 0 errors
```
The first contract run failed as expected before V062-V064 existed. A parity failure also found the missing `deleted_at` on `user_import_rows`; the migration and H2 schema were corrected and the suite passed.
## Not Verified
- Clean PostgreSQL V001-V064 migration was not executed because no isolated PostgreSQL runtime was available in this step.
- Profile update transaction and service API gates are intentionally deferred to Task 4.
## Next Phase
Task 2: user registration, phone verification, and historical WeChat account association.
+36
View File
@@ -0,0 +1,36 @@
# V1.2 Task 2 Completion Report
## Status
Completed
## Implemented
- Added `POST /api/v1/auth/register` for self-registration applications.
- Added phone code send/verify endpoints with hashed Redis code storage, five-attempt limit, one-minute send cooldown, five-minute expiry, and one-time verification transaction.
- Added `SmsVerificationProvider` abstraction.
- Test/demo profiles use an in-memory mock provider; non-test profiles fail closed when SMS is not configured.
- Replaced direct phone binding with a verified transaction requirement.
- Added V065 registration application migration and H2 schema.
- Added miniapp registration page and login entry.
- Duplicate phone registration is rejected without overwriting the existing user.
## Automated Tests
```text
mvn -B -f backend/pom.xml test "-Dtest=PhoneVerificationGuardTest,UserRegistrationServiceTest,V12MigrationContractTest,MigrationValidationTest,EntityMigrationParityTest"
25 tests passed, 0 failures, 0 errors
npm --prefix miniapp run type-check
passed
```
## Not Verified
- Real SMS provider integration is not connected.
- Clean PostgreSQL V001-V065 migration and live Redis behavior were not run in this step.
- Historical WeChat account association UI and ambiguous-account manual queue remain deferred to the account lifecycle/onboarding stage.
## Next Phase
Task 3: operator-created profiles, CSV preview/commit, review linkage, and account issuance.
+37
View File
@@ -0,0 +1,37 @@
# V1.2 Task 3 Completion Report
## Status
Completed
## Implemented
- Added operator manual onboarding endpoint: `POST /api/v1/operator/users`.
- Added CSV preview and commit endpoints with required header validation.
- Preview stores temporary CSV content in Redis and does not write user or batch rows.
- Commit persists import batch and row outcomes, rejects duplicate/invalid rows individually, and uses a 24-hour operator/idempotency-key guard.
- Added `user.create` permission and granted it to platform/organization administrators.
- Review approval now updates `published_profile_revision`; rejection and need-edit keep the prior published revision.
- Account issuance checks verified phone, approved and published profile, and two agreed consent records before creating a hash-only credential.
- Added admin manual-create and CSV import pages.
## Automated Tests
```text
mvn -B -f backend/pom.xml test "-Dtest=UserImportServiceTest,UserCredentialIssuanceTest,UserRegistrationServiceTest,V12MigrationContractTest,MigrationValidationTest,EntityMigrationParityTest,ReviewServiceTest"
26 tests passed, 0 failures, 0 errors
pnpm --dir admin-web type-check
passed
```
## Not Verified
- Clean PostgreSQL V001-V066 migration was not run.
- Real account delivery channel is not connected; only a credential reference is returned.
- Full admin browser workflow and CSV download of failed rows are not implemented.
- End-to-end review-to-issuance flow still needs Task 4 consent/login integration.
## Next Phase
Task 4: password login, forced initial password change, service consent gates, and authenticated profile revision enforcement.
@@ -2,29 +2,24 @@
## 结论
**NOT_IMPLEMENTED**。当前仓库只有工作台聚合接口 `GET /api/v1/operator/workbench/dashboard`,没有统一的六项详情聚合路由或 `MatchmakerDashboardMapper`,也没有可复用的六项详情 scope builder。审核、咨询、预约、活动和统计模块各自的列表/详情接口仍由模块服务维护,不能据此宣称六项卡片与详情已使用同一范围查询。
**PARTIAL**。已新增统一详情入口 `GET /api/v1/operator/workbench/details/{type}`,服务端从认证主体推导 `SELF`、`TEAM`、`ALL` 范围,禁止请求参数替代主体或组织,并复用已发布资料条件和工作台状态口径。
## 本次完成
## 已完成
- 已盘点工作台控制器、审核、咨询、预约、活动和 analytics 路由/服务,确认不存在统一详情入口。
- 已确认现有聚合服务内部有 `SELF`/`TEAM`/`ALL` 解析,但该解析为私有实现,未被详情查询复用。
- 支持 `review`、`matchmaker`、`counseling`、`appointment`、`match`、`activity` 六类详情。
- 普通红娘仅本人有效对象,组长仅本组织,管理员为全平台。
- 详情接口只接受类型、页码和页大小,不接受 `userId`、`matchmakerId`、`teamId`、`scope`。
- 未发布资料版本不进入详情统计。
- 无权限返回 `403`,组长无组织返回组织范围拒绝。
- 不返回身份证原始佐证、完整手机号或咨询备注正文。
## 未完成与风险
## 当前限制
- 六类详情接口尚未统一:请求仍可能进入各模块自己的列表/详情查询;跨组织拒绝、分页/时间筛选、聚合与详情一致性尚未形成单一服务端实现。
- 敏感佐证和咨询备注的独立权限仍由原模块控制,本任务未扩大其权限。
- 原有模块详情接口尚未全部迁移到统一入口,旧路由仍需逐步收敛。
- 本轮使用本机 PostgreSQL/Redis 活栈完成 SELF/TEAM/ALL 登录和六类详情路由验收;正式发布仍需在干净环境复验。
- 当前自动化已验证接口可用和范围推导,浏览器卡片点击与详情页面联调仍待执行。
## 建议后续实现
## 验证
- 新增统一 `MatchmakerDashboardMapper` 与服务端 scope builder,详情接口只接收分页和时间筛选。
- 让六项卡片与详情共用状态谓词、已发布资料条件和 `SELF`/`TEAM`/`ALL` 条件。
- 将敏感佐证、咨询备注继续留在各自权限与审计链路,不能由工作台详情隐式放开。
## 盘点依据
- `MatchmakerWorkbenchController`:仅 `GET /operator/workbench/dashboard`。
- `ReviewController`:`/operator/reviews/pending`、`/{taskId}` 独立查询。
- `CounselingController`:`/services/operator/queue`、`/assigned`、`/{id}` 独立查询。
- `ServiceAppointmentController`:`/operator/list` 独立查询。
- `ActivityOperatorController`:活动和报名列表独立查询。
- `AnalyticsController`:统计接口独立查询,未接工作台认证主体范围。
- `mvn -B -f backend/pom.xml -s backend/maven-settings.xml -DskipTests compile`:通过。
- `$env:E2E_OPERATOR_USERNAME='demo_admin'; $env:E2E_ADMIN_PASSWORD='demo123'; $env:E2E_OPERATOR_PASSWORD='demo123'; python -m pytest tests/e2e/v12_permission_scope_dashboard_test.py -q`:7 passed。
@@ -0,0 +1,37 @@
# V1.2 Task 4 红娘团队管理完成报告
日期:2026-10-25
## 结论
- 代码实现:PASS
- 静态契约测试:PASS(2/2)
- 后端运营权限回归:PASS(6/6)
- 管理端类型检查:PASS
- 管理端生产构建:PASS
- Docker 重建与 V075 活栈迁移:PASS
## 已实现
1. 新增 `matchmaker.role.manage` 权限,仅授予 `platform_admin`、`super_admin`。
2. 新增红娘团队专用接口:成员列表、角色模板、新增、编辑、启用、停用、重置密码。
3. 后端白名单限制只能管理 `matchmaker`、`matchmaker_leader`,组长必须绑定组织,禁止通过专用接口分配管理员角色。
4. 新增管理端“红娘团队管理”页面和导航入口,展示 SELF/TEAM 数据范围并支持组织选择。
5. 角色调整复用现有运营账号服务和审计记录,不返回密码哈希。
## 活栈验收证据
- Compose 重建成功,backend/admin 容器已重新创建并健康运行。
- PostgreSQL Flyway 当前版本为 `075`。
- `e2e_admin / demo123` 登录成功,角色模板返回 `matchmaker`、`matchmaker_leader`,成员列表请求成功,当前返回 9 名成员。
- `e2e_matchmaker / demo123` 访问 `/api/v1/operator/matchmaker-team/members` 返回 `403`。
- 管理端 `/admin/matchmaker-team` 返回 `200`。
后续人工验收可使用:
```powershell
docker compose --env-file infrastructure/.env -f infrastructure/docker-compose.yml up -d --build
docker exec danyang-match-postgres psql -U postgres -d danyang_match -Atc "select version from flyway_schema_history order by installed_rank desc limit 1;"
```
并继续验证管理员创建普通红娘/组长、组长组织必填、角色变更后工作台数据范围。
+164
View File
@@ -0,0 +1,164 @@
# V1.2 在途代码修复任务提示词(交给 codex 执行)
> 前置评审依据:`docs/RELEASE_REVIEW_2026-10-24.md`(若存在)与本轮审计证据。
> 本提示词**自包含**:codex 无需访问其它会话即可执行。
---
## 背景(必读)
本仓库处于 V1.1 冻结基线(HEAD `d51c4c6`,tag `demo-v1.1.0-20261019`)之上,工作区有 **107–110 项未提交改动**,全部是 V1.2 在途开发。当前状态:
- 后端 `mvn -B -o test` 实跑 **935 用例,6 失败 / 0 错误 / 17 跳过**。
- E2E `pytest tests/e2e -q` 实跑 **72 用例,27 失败 / 45 通过**。
- UI 契约门禁 `node scripts/run-ui-contracts.mjs` 24 项中 **23 通过 / 1 失败**(`ui-contracts.mjs`)。
- 活栈容器 `danyang-match-backend`(image `infrastructure-backend`,2026-10-24 23:07 构建)编译自当前工作区代码,`/health/ready` = READY,`/health/live` = 200。
- 数据库 `danyang_match` 实测 1010 用户全部 `service_eligibility_status = 'pending'`,0 个 `eligible`。
**本机 Git 注意**:分支名必须用**顶层单段名**(如 `fix-v12-gates`),禁止带 `/` 的嵌套分支名(本机监听进程会 0.2s 内删除 `refs/heads/<a>/<b>`)。
---
## 任务 P0-1:解除 service_eligibility 死锁(**最高优先级**)
### 根因(已验证)
`backend/src/main/java/com/danyang/match/platform/account/service/ServiceConsentService.java:75`:
```java
if (!"eligible".equals(user.getServiceEligibilityStatus())) throw blocked("当前账号不具备服务资格");
```
该门禁被 13 个控制器约 50 处调用(`RecommendationController`、`MatchController`、`LikeController`、`ChatController`/`MessageController`/`ConversationController`、`ActivityInterestController`、`CounselingController`、`AppointmentController`/`ServiceAppointmentController`、`ServiceProgramController`、`DiscoverController`、`MatchmakerController`、`PassController`、`CapabilityService`)。
全仓库扫描证实:**没有任何生产代码路径把 `users.service_eligibility_status` 置为 `"eligible"`**——只有 `UserRegistrationService.java:39` 和 `UserOnboardingService.java:36` 置 `"pending"`;`ReviewService.approveLocalEligibility`(`backend/src/main/java/com/danyang/match/platform/review/service/ReviewService.java:380-419`)只更新 `local_eligibility` 表的 `verification_status`,**不写 `users` 表**。
结果:任何真实用户完成全部注册/认证/授权流程后,仍被该门禁永久挡住,match/chat/counseling/appointment 等全部核心 API 返回 403。
### 修复要求(二选一,推荐方案 A)
**方案 A(推荐,最小改动)**:在 `ReviewService.approveLocalEligibility` 方法内,`localEligibilityMapper.updateById(el)` 之后**同步更新 `users` 表**:
```java
user.setServiceEligibilityStatus("eligible");
userMapper.updateById(user);
```
- 需要注入 `UserMapper`(如尚未注入)。
- 同步修改 `rejectLocalEligibility`(若存在):`user.setServiceEligibilityStatus("ineligible")`。
- 补充或更新 `backend/src/test/java/com/danyang/match/platform/review/` 下的对应单测,断言 `users.service_eligibility_status` 确实被更新。
- 更新 `backend/src/test/java/com/danyang/match/platform/account/ServiceConsentGateTest.java:39` 的注释,说明测试不再需要直接置值(或保留为 fixture 辅助但加注释标注"这是测试快捷方式,生产路径由 ReviewService 负责")。
**方案 B(备选,若方案 A 的产品语义不合适)**:从 `ServiceConsentService.requireActive` 中**临时摘掉**第 4 项校验(`:"eligible"` 断言),让前 3 项(账号状态/手机、资料 published revision、双授权)作为当前门禁,并把 eligibility 写入路径作为独立 P1 任务。
### 验证
- 在活栈 DB 手工把任意一个用户 `service_eligibility_status` 改为 `'eligible'`,重跑 E2E 中该用户相关的 27 个失败用例,确认 403 消失、链路可用。
- 或:修改 `tests/e2e/conftest.py` 里 E2E 用户种子逻辑,使其通过 `ReviewService` 路径获得 eligible 状态,再全量跑 `pytest tests/e2e`。
---
## 任务 P0-2:修复活动互选 E2E 7 用例失败
### 根因(已验证)
`backend/src/main/java/com/danyang/match/platform/activity_mutual/service/ActivityMutualConsentService.java` 中:
`createMutualConsent`(:37-67)按 `userId.compareTo(partnerId)` 排序后统一写 `userAId`/`userBId`,**只生成一行记录**(A→B 和 B→A 都指向同一行)。
`confirmConnection`(:70-104)调用 `consentMapper.confirmSideIfPending(consent.getId(), userId)`(`backend/src/main/java/com/danyang/match/platform/activity_mutual/mapper/ActivityMutualConsentMapper.java:57-69`),该 SQL 的 WHERE 条件是:
```sql
WHERE id = #{id} AND deleted_at IS NULL
AND both_confirmed_at IS NULL
AND (expires_at IS NULL OR expires_at > NOW())
AND CASE WHEN user_a_id = #{userId} THEN user_a_status ELSE user_b_status END = 'pending'
```
SQL 本身是**对称的**(谁 confirm 就改谁那侧),看似没问题。但 E2E `tests/e2e/activity_mutual_consent_test.py` 中 A 先 confirm 后,B 再 confirm 时**拿到的状态仍停在 `pending`**,`bothConfirmed` 永远 `false`。
排查方向(codex 执行前先精读):
- 确认 `ActivityInterestController` / `ActivityMutualConsentController`(在途新增)里 `createMutualConsent` 的调用时机与 `confirmConnection` 之间是否有**状态回滚**(比如 A confirm 后某处把 user_a_status 又写回 pending)。
- 确认 `ActivityMutualConsent` 实体(`??` 未跟踪)的 `both_confirmed_at` 字段与 H2/PG 的 `NOW()` 行为。
- 确认 `expires_at` 写入逻辑(`createMutualConsent` :60-63)是否在测试场景下导致 `(expires_at IS NULL OR expires_at > NOW())` 恒不成立。
### 验证
跑 `pytest tests/e2e/activity_mutual_consent_test.py`,7 个用例全部通过:
- `test_mutual_interest_does_not_create_match`
- `test_one_side_confirm_no_match`
- `test_both_confirm_creates_match_episode`
- `test_existing_active_match_not_duplicated`
- `test_prior_unmatch_creates_new_episode`
- `test_one_side_decline_no_match`
- `test_timeout_handled`
---
## 任务 P0-3:修复 6 个后端测试失败
### P0-3a:`MigrationValidationTest.testMigrationNamingConvention`
`backend/src/test/java/com/danyang/match/platform/deployment/MigrationValidationTest.java:46`:
```java
private static final int LATEST_MIGRATION = 67;
```
但 `backend/src/main/resources/db/migration/` 目录下 V062–V069 共 8 个文件全部 `??` 未跟踪,实际最大版本号是 **69**。
修复:把 `LATEST_MIGRATION` 改为 `69`,并在注释里补一行说明 V068、V069 分别对应 `grant_matchmaker_workbench_read_permissions` 和 `grant_matchmaker_workbench_operation_permissions`。
### P0-3b:`DiscoverBlockInvisibilityIntegrationTest` × 5
`backend/src/main/java/com/danyang/match/platform/discover/service/DiscoverService.java:43-44`(V1.2 在途改动,HEAD 无此两行):
```java
.isNotNull(UserProfile::getPublishedProfileRevision)
.apply("profile_revision = published_profile_revision")
```
导致测试 fixture(`backend/src/test/java/com/danyang/match/platform/discover/DiscoverBlockInvisibilityIntegrationTest.java:195`)只 set `profileStatus=APPROVED`,未 set `publishedProfileRevision`,搜索返回空。
修复(**二选一**):
1. **推荐**:在该测试的 fixture 里补 `p.setPublishedProfileRevision(p.getProfileRevision());`(对每个被搜索的用户 profile 都做),让测试与新契约一致。
2. 备选:若 `published_profile_revision` 语义尚未完全落地,在 `DiscoverService` 里把 `.isNotNull(...)` 改为 `.and(w -> w.isNotNull(UserProfile::getPublishedProfileRevision).or().apply("profile_revision = published_profile_revision"))`(放宽为"有 published revision 或 revision 等于 published 都可见")。**选此方案必须同步补注释说明这是过渡期放宽,并加 TODO 指向任务 P0-1 完成后收紧。**
跑 `mvn -B -o test -Dtest=DiscoverBlockInvisibilityIntegrationTest` 验证 5 个用例全绿。
---
## 任务 P0-4:修复 UI 契约门禁失败
`ui-tests/ui-contracts.mjs`(或未跟踪的 `scripts/run-ui-contracts.mjs` 中)对 `miniapp/app.json` 的 `pages` 数组长度断言为 `=== 67`,但 V1.2 新增了 `pages/register/register` 和 `pages/service-consent/service-consent` 两页,实际为 **69**。
修复:把断言目标值从 67 改为 69。同时检查是否有**其它** UI 契约断言(如 tabBar 数量、特定页面路径白名单)需要同步更新。
验证:`node scripts/run-ui-contracts.mjs` 应输出 24/24 通过。
---
## 任务 P0-5:提交 V1.2 在途改动并推送
完成 P0-1 ~ P0-4 后,把全部 107–110 项未提交改动(含 V062–V069 八个迁移、47 个后端未跟踪文件、`UserCreateView.vue`、`UserImportView.vue`、`miniapp/pages/register/`、`miniapp/pages/service-consent/`、`ServiceConsentController/Service/Controller` 等)一次性提交:
```bash
# 本机 Git:分支名必须是顶层单段名,禁止 feat/xxx 形式
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe checkout -b fix-v12-gates
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe add -A
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe commit -m "fix(v1.2): unblock service consent gate + mutual consent + test regressions + UI contract"
# 本机分支带斜杠会被监听进程 0.2s 删除,单段名 fix-v12-gates 是安全的
C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe push origin fix-v12-gates
```
推送前先验证 `C:\Users\13070\.workbuddy\binaries\PortableGit\versions\1.3.0\cmd\git.exe branch -vv` 显示 `main` 领先 `origin/main` 28 个提交的情况是否已在本次 `fix-v12-gates` 分支基础上解决(即新分支应从最新 main 切出并包含全部在途改动)。
---
## 任务 P1(可选,若时间允许)
- **部署链路**(详见 release-ops 审计):`infrastructure/docker-compose.prod.yml` 补 healthcheck;`deploy.sh` 回滚改为按 digest;`MINIO_PUBLIC_ENDPOINT` 注入真实可达地址;`backend/Dockerfile:7` 去掉 `-DskipTests`(或在 CI 单独跑全量测试);`nginx-production.conf:55-60` 恢复 80→443 跳转;`infrastructure/certs/` 目录补齐证书产物或写入 CI 生成步骤。
- **密钥治理**:`application.yml:88-89` 的 `admin/admin` 兜底、`docker-compose.staging.yml:118` 的硬编码 `ENCRYPTION_KEY`、`application-dev.yml:23,28,32,34` 的默认值全部改为 `${VAR:?}` 强制注入。
- **版本注入**:`backend/src/main/java/com/danyang/match/platform/.../VersionController.java:20` 的 `@Value("${app.version:0.0.1-SNAPSHOT}")` 改为 Maven resource filtering 注入,使 `/api/v1/version` 返回与 `VERSION=1.1.0`(或 V1.2 定版后的值)一致的版本号。
---
## 验收标准(全部满足才算完成)
1. `mvn -B -o test`:**935 用例,0 失败 / 0 错误**(17 跳过允许保留)。
2. `pytest tests/e2e -q`(活栈重建后):**72 用例,0 失败**。
3. `node scripts/run-ui-contracts.mjs`:**24/24 通过**。
4. `node node_modules/typescript/bin/tsc --noEmit`(miniapp):**0 错**。
5. `node node_modules/vue-tsc/bin/vue-tsc.js --noEmit`(admin-web):**0 错**。
6. 活栈重建后 `GET /api/v1/health/ready` = `READY`,`GET /api/v1/version` 返回非 `0.0.1-SNAPSHOT` 的版本号(P1 完成时)。
7. `git status --porcelain` 在 `fix-v12-gates` 分支上为**空**(全部已提交)。
8. 推送后 CI(若有)对 `fix-v12-gates` 分支跑一遍全量绿。
+11
View File
@@ -25,6 +25,17 @@
## 二、配置登记表
### V1.2 状态与策略键
| canonical 配置键 | 分类 | 产品正式确认 | 说明 |
|------------------|------|:---:|------|
| `account.login.identifier` | LOCKED | 是 | 固定为 `verified_phone`,登录主标识为已核验手机号 |
| `account.initial-credential.delivery` | LOCKED | 是 | 固定为 `authorized_staff_manual`;失败进入人工补发队列 |
| `account.service-consent.required-documents` | LOCKED | 是 | 固定为两份独立授权书;文案和版本由发布配置管理 |
| `matchmaker.assignment.mode` | LOCKED | 是 | V1.2 固定为 `manual`;自动分配算法不在本阶段实现 |
| `matchmaker.assignment.no-available-action` | LOCKED | 是 | 固定为 `pending_queue`,不阻塞用户注册 |
| `matchmaker.dashboard.scope` | LOCKED | 是 | 固定为 `principal_data_scope`,由服务端主体权限推导 |
| canonical 配置键 | 实现键(yml / flag 字段) | 分类 | 当前默认值 | 产品正式确认 | 是否可配置 | 代码载体 / 说明 |
|------------------|--------------------------|------|:---------:|:---:|:---:|----------------|
| `identity.liveness.enabled` | **DB 键即 canonical 名**(`feature_flag_overrides.flag_key = 'identity.liveness.enabled'`,V051 写入;Java 字段 `livenessEnabled`,yml `features.liveness-enabled`) | CONFIG / TBD | `false` | 否 | 是 | `FeatureFlagConfig.KEY_ALIASES` 承担「canonical 键 ↔ 实现字段」映射(`identity.liveness.enabled` / 遗留 `livenessVerification.enabled` → `livenessEnabled`);写库归一(Controller PUT/DELETE 落 canonical 键);`IdentityPolicy` + `GET /verification/liveness-status` 真实判定点(FF-LIVENESS-002)。**活体不是 V1 强制规则**;默认关闭不阻断实名主流程;fail-safe:配置缺失默认 false。 |
+77 -68
View File
@@ -1,69 +1,78 @@
# 决策登记表 — decision-register.md
> **版本**:V1.0.1 配置与规则纠偏基线 · **生效**:2026-09-20 · **owner**:产品负责人
> **只登记产品负责人已正式确认的决策**(LOCKED / 已确认默认)。未确认项一律在
> `open-questions.md`,**不得**出现在本表(消解 PENDING「同一事项既待确认又已确认」矛盾)。
> 确认值同步记入 `config-register.md` 的「产品正式确认」列。
---
## 已确认决策
| 类别 | 确认项 | 分类 | 代码载体 / 备注 |
|------|--------|:---:|----------------|
| 品牌 | 善缘丹阳品牌正式启用 | LOCKED | 品牌资源统一走 `admin-web/src/brand.ts` 的 `BASE_URL` 拼接 |
| 首页 | Banner 运营 + 8 个核心入口 + 固定顺序 | LOCKED | INV-017 顺序固定,运营不可重排;Banner 仅内部跳转(INV-016) |
| 数据保留 | **注销后用户数据保留不得低于 36 个月**(2026-09-19 产品确认) | LOCKED | `account.data-retention-months`(默认 36);到期人工审核匿名化清除(V049 `users.closed_at` + `DataRetentionService` + `account.retention.purge`);隐私政策 §3.3 / PIA §8 已修订 |
| 服务站 | 万善公园婚姻登记处为固定阵地 | LOCKED | `service_locations` 种子数据 |
| 服务 | 婚恋咨询 + 婚前辅导进入 V1.1 首发 | LOCKED | counseling / premarital 模块 |
| 内容 | 婚恋课堂 + 婚恋反诈进入 V1.1 首发 | LOCKED | content 模块 |
| 活动 | 多类型公益活动 + 活动双向沟通确认进入 V1.1 首发 | LOCKED | 二阶段同意(INV-014/015) |
| 核心规则 | Required 不可放宽 / Mutual 后才 Chat / Block 优先 | LOCKED | 匹配/聊天不变量 |
| 推荐 | 每日 6 人 / Pass 冷却 180 天 | LOCKED | `match.default-daily-limit=6`、`match.default-cooldown-days=180` |
| 资料 | 兴趣标签上限 8 个 / 自我介绍 500 字符 | LOCKED | 前端常量 + 后端校验 |
| 隐私 | Privacy 默认全 TRUE | LOCKED | `user_privacy_settings` |
| 活动规则 | 需审核 / 男女名额独立配置 / 互选最大 3 人 / 截止后 48 小时 | LOCKED | activity 模块 |
| 关系状态 | searching / getting_to_know / paused / matched_successfully / service_exited | LOCKED | 关系状态枚举 |
| 推荐重置 | 每日 00:00 Asia/Shanghai | LOCKED | 时区统一 Asia/Shanghai |
| 红娘权限 | DEC-MATCHMAKER-001 红娘角色定位与权限边界(受授权、受范围限制的公益服务角色,非管理员/裁决者/风控) | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-002 / A1 服务对象由平台授权人员或红娘组长分配,普通红娘仅访问本人有效分配对象 | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-003 / B1 红娘联系用户默认平台内渠道,不默认开放电话/线下/完整联系方式 | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-004 红娘不得代替用户执行交友决策(Like/Pass/活动意向/开启聊天/修改择偶条件/同意公开资料) | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-005 红娘数据访问最小必要(角色权限∧有效分配∧范围∧字段隐私∧服务目的,五条件同时满足) | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-006 红娘操作全程留痕(分配/受限查看/授权/越权拒绝等审计事件 + 第八节字段要求) | LOCKED | 见下方「红娘角色权限专项」 |
---
### 红娘角色权限专项(ZC-MM-001~007,2026-10-10 登记)
> 决策基准:《善缘丹阳 V1.1 红娘角色权限正式决策与开发规则》V1.1(A1/B1 均标记 LOCKED)。
> 实施全链路:审计先行 → A1 建模 → 数据范围/字段隐私 → B1/代操作护栏 → 审计与权限回收 → 本登记。
| 决策 | 实施摘要 | 落地提交 | 验证 |
|------|---------|---------|------|
| DEC-MATCHMAKER-001~006 差距审计 | 全仓只读走查(角色/分配建模/数据范围/联系方式/代操作/审计/测试/治理 9 项),产出 P0×5 / P1×6 / P2×6 差距清单 | 2d60f78(ZC-MM-001 审计报告) | — |
| DEC-MATCHMAKER-002 / A1 | `matchmaker_assignments` 五态状态机(V053 部分唯一索引防多主责 + 存量回填)、`matchmaker_leader` 角色 + `matchmaker.team.assign`(V054)、`ServiceAssignmentPolicy` 唯一判定器、claim 端点删除、/pending 权限收敛 | 861c78e(ZC-MM-002) | 定向 38/38、UI 24/24、小程序真编译通过 |
| DEC-MATCHMAKER-005 / §5.2 | 读侧纯判定器(删除 assigned_operator_id 回退)、非持有人 404 防探测、字段白名单 7 键 + showOccupation 接入、组长团队脱敏统计 | 3caf5f8(ZC-MM-005) | mvn verify 884/884、UI 契约 24/24、后端契约 198 |
| DEC-MATCHMAKER-003 / B1 | 纯护栏(零生产代码改动):MM-CONTACT-001~004 守卫锁定(无联系端点/无授权机制/零联系方式回显,含紧急联系人 write-only);受控联系机制 DEFERRED(见 OQ-MATCHMAKER-003) | 5e484d0(ZC-MM-003) | MatchmakerContactGuardTest(负向注入) |
| DEC-MATCHMAKER-004 | 代操作核查 + 锁定:红娘可调写端点逐一无代用户意思表示;operator 主体调用户决策端点认证层 403(SecurityConfig 身份类型防线);转介 must-confirm 语义测试锁死 | 5e484d0(ZC-MM-004) | MatchmakerActionGuardTest(负向注入) |
| DEC-MATCHMAKER-005/006 | V055 audit_logs 补 purpose_code/service_assignment_id/actor_role_snapshot/request_trace_id;JWT 权限实时查库回收(零缓存);SensitiveAccessLog 接入服务详情;写接口 404 化防探测统一 | 1be0d98(ZC-MM-006) | mvn verify 884/884、后端契约 198 |
| **DEC-MATCHMAKER-003-B1 例外通道立项**(2026-10-11 LOCKED→已实现) | 受控联系全链路:V056 `matchmaker_contact_authorizations` 六态状态机(申请→审批→**用户单独同意**[PIPL,独立端点/无预同意参数]→限时生效→撤销/过期)+ ContactAuthorizationPolicy/Service + 双控制器 + 前端三端(miniapp 同意页/工作台入口、admin-web 审批 tab);**唯一披露出口** disclose 专用端点(enabled fail-safe + 404 化 deny + user_phone_disclosure 敏感审计 + phone 不落应用日志);守卫翻转五条(ReferralService 响应键白名单与无 @Scheduled 保留,其余从「锁不存在」改「锁新机制契约」);审批岗/有效期/保留期等全部 DEFAULT+CONFIG(config-register `matchmaker.contact.*` 五键,非 LOCKED 待终审) | d3735e3 | mvn verify **895/895**、UI 契约 **24/24**、后端契约 **228**/CURATED 20、小程序真编译 **87 WXML**、守卫负向注入 **5+2** |
**专项验证汇总**:mvn verify 全量 **884/884**、UI 契约 **24/24**、后端契约 **198**、守卫负向注入变异验证 **7 处判红→还原**(守卫有效性自证)。
---
## 明确「非」已确认(防止误登记,OQ 见 open-questions.md)
| 事项 | 正确定位 | 参见 |
|------|----------|------|
| 照片上限 6 | **技术默认,未产品确认**(OQ-PROFILE-001) | open-questions |
| 推荐阈值 60 | **技术默认,未产品确认**(OQ-MATCH-001) | open-questions |
| 活体核验强制 | **V1.1 首发不强制**,Flag 默认关(OQ-IDENTITY-001) | open-questions |
| Banner 最大活跃数量 | **未定 + 代码未实现**(OQ-BANNER-001,真缺口) | open-questions |
| 有效匹配报送口径 | **PROVISIONAL,未产品裁决**(OQ-ANALYTICS-001) | open-questions |
---
*最后更新: 2026-10-11 — 登记 DEC-MATCHMAKER-003-B1 受控联系例外通道立项(V056 / 提交 d3735e3,QA PASS 0 P0/P1);OQ-MATCHMAKER-003 转 IN_PROGRESS,剩余 LEGAL_VERIFY 终审项与审批双岗收敛见 open-questions OQ-MATCHMAKER-003/009,参数 canonical key 见 config-register `matchmaker.contact.*`。此前:2026-10-10 登记红娘角色权限专项 DEC-MATCHMAKER-001~006(ZC-MM-001~007 全链路);2026-09-20 登记 15 项已确认决策。*
# 决策登记表 — decision-register.md
> **版本**:V1.0.1 配置与规则纠偏基线 · **生效**:2026-09-20 · **owner**:产品负责人
> **只登记产品负责人已正式确认的决策**(LOCKED / 已确认默认)。未确认项一律在
> `open-questions.md`,**不得**出现在本表(消解 PENDING「同一事项既待确认又已确认」矛盾)。
> 确认值同步记入 `config-register.md` 的「产品正式确认」列。
---
## 已确认决策
| 类别 | 确认项 | 分类 | 代码载体 / 备注 |
|------|--------|:---:|----------------|
| 品牌 | 善缘丹阳品牌正式启用 | LOCKED | 品牌资源统一走 `admin-web/src/brand.ts` 的 `BASE_URL` 拼接 |
| 首页 | Banner 运营 + 8 个核心入口 + 固定顺序 | LOCKED | INV-017 顺序固定,运营不可重排;Banner 仅内部跳转(INV-016) |
| 数据保留 | **注销后用户数据保留不得低于 36 个月**(2026-09-19 产品确认) | LOCKED | `account.data-retention-months`(默认 36);到期人工审核匿名化清除(V049 `users.closed_at` + `DataRetentionService` + `account.retention.purge`);隐私政策 §3.3 / PIA §8 已修订 |
| 服务站 | 万善公园婚姻登记处为固定阵地 | LOCKED | `service_locations` 种子数据 |
| 服务 | 婚恋咨询 + 婚前辅导进入 V1.1 首发 | LOCKED | counseling / premarital 模块 |
| 内容 | 婚恋课堂 + 婚恋反诈进入 V1.1 首发 | LOCKED | content 模块 |
| 活动 | 多类型公益活动 + 活动双向沟通确认进入 V1.1 首发 | LOCKED | 二阶段同意(INV-014/015) |
| 核心规则 | Required 不可放宽 / Mutual 后才 Chat / Block 优先 | LOCKED | 匹配/聊天不变量 |
| 推荐 | 每日 6 人 / Pass 冷却 180 天 | LOCKED | `match.default-daily-limit=6`、`match.default-cooldown-days=180` |
| 资料 | 兴趣标签上限 8 个 / 自我介绍 500 字符 | LOCKED | 前端常量 + 后端校验 |
| 资料审核 | 实名认证后的个人资料任何变更都必须生成新版本并重新进入审核;新版本审核通过前不得正常展示、推荐或参与匹配 | LOCKED | V1.2 用户确认;实现为 profile revision + published revision,后端门禁优先于前端隐藏 |
| 隐私 | Privacy 默认全 TRUE | LOCKED | `user_privacy_settings` |
| 活动规则 | 需审核 / 男女名额独立配置 / 互选最大 3 人 / 截止后 48 小时 | LOCKED | activity 模块 |
| 关系状态 | searching / getting_to_know / paused / matched_successfully / service_exited | LOCKED | 关系状态枚举 |
| 推荐重置 | 每日 00:00 Asia/Shanghai | LOCKED | 时区统一 Asia/Shanghai |
| 红娘权限 | DEC-MATCHMAKER-001 红娘角色定位与权限边界(受授权、受范围限制的公益服务角色,非管理员/裁决者/风控) | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-002 / A1 服务对象由平台授权人员或红娘组长分配,普通红娘仅访问本人有效分配对象 | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-003 / B1 红娘联系用户默认平台内渠道,不默认开放电话/线下/完整联系方式 | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-004 红娘不得代替用户执行交友决策(Like/Pass/活动意向/开启聊天/修改择偶条件/同意公开资料) | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-005 红娘数据访问最小必要(角色权限∧有效分配∧范围∧字段隐私∧服务目的,五条件同时满足) | LOCKED | 见下方「红娘角色权限专项」 |
| 红娘权限 | DEC-MATCHMAKER-006 红娘操作全程留痕(分配/受限查看/授权/越权拒绝等审计事件 + 第八节字段要求) | LOCKED | 见下方「红娘角色权限专项」 |
| V1.2 账号登录 | 用户使用已核验手机号 + 密码登录 | LOCKED | 新账号和历史微信账号关联均须完成手机号核验;不再新增独立用户名登录主路径 |
| V1.2 初始凭据 | 系统生成一次性初始密码,由后台授权人员线下核验后发放 | LOCKED | 只存密码哈希;首次登录强制改密;发放失败进入人工补发队列 |
| V1.2 服务授权 | 两份授权书分别确认;拒绝任一份即不能进入匹配、展示及相关服务 | LOCKED | 授权书版本、确认人、时间和审计信息独立保存 |
| V1.2 历史账号 | 历史微信用户必须完成手机号核验后关联;重复或歧义账号不得自动合并 | LOCKED | 进入人工关联队列,禁止覆盖已有用户数据 |
| V1.2 重复资料 | 重复手机号或资料不得自动覆盖,进入人工处理队列 | LOCKED | 注册、后台建档和导入均执行同一规则 |
| V1.2 红娘分配 | 首版沿用人工分配;自动分配算法另行确认 | LOCKED | 无可用红娘时进入待分配队列,不阻塞用户注册 |
| V1.2 看板范围 | 看板按当前登录主体的数据权限计算 | LOCKED | 普通红娘=本人;组长=本组织;管理员=平台范围;详情查询不得扩大范围 |
| V1.2 组长权限 | 红娘组长仅管理本组织内红娘及用户 | LOCKED | 不得跨组织建档、导入、分配或调整关系 |
| V1.2 工作台权限冲突裁决(2026-10-25) | 普通红娘与红娘组长均可查看五类工作台业务卡片,并在本人/本组织范围内执行审核、业务待办分配、确认、完成;不得跨组织、查看敏感原始佐证/咨询备注或代替用户作出 Like/Pass 等意思表示 | LOCKED | V068/V069 的授权意图按最新需求保留;V070/V071 仅作为历史收敛迁移,不得覆盖本裁决;后续幂等迁移恢复最小权限 |
---
### 红娘角色权限专项(ZC-MM-001~007,2026-10-10 登记)
> 决策基准:《善缘丹阳 V1.1 红娘角色权限正式决策与开发规则》V1.1(A1/B1 均标记 LOCKED)。
> 实施全链路:审计先行 → A1 建模 → 数据范围/字段隐私 → B1/代操作护栏 → 审计与权限回收 → 本登记。
| 决策 | 实施摘要 | 落地提交 | 验证 |
|------|---------|---------|------|
| DEC-MATCHMAKER-001~006 差距审计 | 全仓只读走查(角色/分配建模/数据范围/联系方式/代操作/审计/测试/治理 9 项),产出 P0×5 / P1×6 / P2×6 差距清单 | 2d60f78(ZC-MM-001 审计报告) | — |
| DEC-MATCHMAKER-002 / A1 | `matchmaker_assignments` 五态状态机(V053 部分唯一索引防多主责 + 存量回填)、`matchmaker_leader` 角色 + `matchmaker.team.assign`(V054)、`ServiceAssignmentPolicy` 唯一判定器、claim 端点删除、/pending 权限收敛 | 861c78e(ZC-MM-002) | 定向 38/38、UI 24/24、小程序真编译通过 |
| DEC-MATCHMAKER-005 / §5.2 | 读侧纯判定器(删除 assigned_operator_id 回退)、非持有人 404 防探测、字段白名单 7 键 + showOccupation 接入、组长团队脱敏统计 | 3caf5f8(ZC-MM-005) | mvn verify 884/884、UI 契约 24/24、后端契约 198 |
| DEC-MATCHMAKER-003 / B1 | 纯护栏(零生产代码改动):MM-CONTACT-001~004 守卫锁定(无联系端点/无授权机制/零联系方式回显,含紧急联系人 write-only);受控联系机制 DEFERRED(见 OQ-MATCHMAKER-003) | 5e484d0(ZC-MM-003) | MatchmakerContactGuardTest(负向注入) |
| DEC-MATCHMAKER-004 | 代操作核查 + 锁定:红娘可调写端点逐一无代用户意思表示;operator 主体调用户决策端点认证层 403(SecurityConfig 身份类型防线);转介 must-confirm 语义测试锁死 | 5e484d0(ZC-MM-004) | MatchmakerActionGuardTest(负向注入) |
| DEC-MATCHMAKER-005/006 | V055 audit_logs 补 purpose_code/service_assignment_id/actor_role_snapshot/request_trace_id;JWT 权限实时查库回收(零缓存);SensitiveAccessLog 接入服务详情;写接口 404 化防探测统一 | 1be0d98(ZC-MM-006) | mvn verify 884/884、后端契约 198 |
| **DEC-MATCHMAKER-003-B1 例外通道立项**(2026-10-11 LOCKED→已实现) | 受控联系全链路:V056 `matchmaker_contact_authorizations` 六态状态机(申请→审批→**用户单独同意**[PIPL,独立端点/无预同意参数]→限时生效→撤销/过期)+ ContactAuthorizationPolicy/Service + 双控制器 + 前端三端(miniapp 同意页/工作台入口、admin-web 审批 tab);**唯一披露出口** disclose 专用端点(enabled fail-safe + 404 化 deny + user_phone_disclosure 敏感审计 + phone 不落应用日志);守卫翻转五条(ReferralService 响应键白名单与无 @Scheduled 保留,其余从「锁不存在」改「锁新机制契约」);审批岗/有效期/保留期等全部 DEFAULT+CONFIG(config-register `matchmaker.contact.*` 五键,非 LOCKED 待终审) | d3735e3 | mvn verify **895/895**、UI 契约 **24/24**、后端契约 **228**/CURATED 20、小程序真编译 **87 WXML**、守卫负向注入 **5+2** |
**专项验证汇总**:mvn verify 全量 **884/884**、UI 契约 **24/24**、后端契约 **198**、守卫负向注入变异验证 **7 处判红→还原**(守卫有效性自证)。
---
## 明确「非」已确认(防止误登记,OQ 见 open-questions.md)
| 事项 | 正确定位 | 参见 |
|------|----------|------|
| 照片上限 6 | **技术默认,未产品确认**(OQ-PROFILE-001) | open-questions |
| 推荐阈值 60 | **技术默认,未产品确认**(OQ-MATCH-001) | open-questions |
| 活体核验强制 | **V1.1 首发不强制**,Flag 默认关(OQ-IDENTITY-001) | open-questions |
| Banner 最大活跃数量 | **未定 + 代码未实现**(OQ-BANNER-001,真缺口) | open-questions |
| 有效匹配报送口径 | **PROVISIONAL,未产品裁决**(OQ-ANALYTICS-001) | open-questions |
---
*最后更新: 2026-10-23 — 登记 V1.2 Gate 0 账号生命周期、授权、资料发布、红娘分配和看板范围决策;此前已登记 V1.1 核心与红娘权限专项决策。*
+14 -1
View File
@@ -145,4 +145,17 @@
---
*最后更新: 2026-10-11(二次)— 产品裁决落账:OQ-MATCHMAKER-003 RESOLVED(5 项 LEGAL_VERIFY 全闭环:①组长主审+管理员兜底 ②用户单独同意已实现 ③有效期 7 天 ④保留期 365 天 ⑤审计面满足+NULL=NULL 维持;建议放量前转法务复核留痕)、OQ-MATCHMAKER-009 RESOLVED(分层审批落地,提交 f73a116)、OQ-MATCHMAKER-010 RESOLVED(disabled 行为基线补齐,顺带修复 request 未查 enabled 真实缺口)。此前:2026-10-11 OQ-003 转 IN_PROGRESS / 新增 009~010;2026-10-10 红娘专项新增 OQ-MATCHMAKER-003~008。*
## V1.2 Gate 0 已解决事项(2026-10-23)
以下事项已按产品确认的推荐方案冻结,具体契约见 `docs/v12/`:
- 用户登录标识:已核验手机号 + 密码。
- 初始凭据:系统生成一次性密码,由授权工作人员线下核验发放;失败进入人工补发队列。
- 服务授权:两份授权书分别确认,任一拒绝即不能进入匹配、展示及相关服务。
- 历史微信账号:完成手机号核验后关联;重复或歧义账号进入人工队列,不自动合并。
- 重复手机号/资料:不得自动覆盖,进入人工处理队列。
- 红娘分配:V1.2 首版沿用人工分配;无可用红娘时进入待分配队列。
- 看板统计:按当前登录主体的数据权限计算。
- 红娘组长:仅管理本组织内红娘及用户。
*最后更新: 2026-10-23 — 登记 V1.2 Gate 0 账号生命周期、授权、资料发布、红娘分配和看板范围决策;既有 V1.1 未相关开放问题继续保持原状态。*
+28
View File
@@ -0,0 +1,28 @@
# 善缘丹阳 V1.2 演示验收冻结记录
- 冻结日期:2026-10-25
- 冻结标签:`demo-v1.2-20261025`
- 性质:本地演示与浏览器验收基线,不代表生产发布验收。
- 用户验收:管理员端“红娘团队”页面浏览器验收通过。
## 范围
- V1.2 账号生命周期、注册与后台建档、服务授权、资料重新审核与发布门禁。
- 红娘工作台权限、团队角色管理、中文唯一用户名及旧用户名登录兼容。
- 机构类型编码修复和红娘成员编辑审计 JSON 修复。
- Flyway 迁移至 V076。
## 冻结前验证
- 后端 `mvn verify -q`:959 项,0 失败,0 错误,17 跳过。
- UI 契约:24/24 通过;小程序和管理端类型检查通过。
- 管理端与 H5 生产构建通过。
- H5 浏览器冒烟通过:登录守卫、演示登录、6 个核心页、Like 和红娘登录。
- 演示数据库 Flyway 当前版本:076。
- 红娘普通角色与组长角色编辑接口经真实 API 验证通过。
## 边界
- 本机局域网 IP 开发配置和 Docker 本地访问地址未纳入冻结提交,仍保留在工作区。
- 未执行全新环境部署、生产域名/微信审核、备份恢复与生产安全验收。
- 管理端构建存在 CSS `url(var(...))` 压缩警告,构建本身成功,需在后续视觉回归中确认相关背景图。
@@ -0,0 +1,363 @@
# V1.2 账号生命周期、红娘分配与工作台实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在保留 V1.1 核心业务和现有红娘分配权限的前提下,补齐 V1.2 的用户注册/建档/审核/账号发放/首次授权闭环,确保认证后的资料变更重新审核后才重新展示和匹配,并让普通红娘、红娘组长和管理员使用一致且不可越权的六项工作台统计。
**Architecture:** 继续使用 Spring Boot 模块化单体、PostgreSQL Source of Truth、Redis 只保存验证码/登录失败状态。用户账号生命周期、授权门禁、导入批次和红娘工作台范围分别建模;业务服务通过现有 `CapabilityService`、`ServiceAssignmentPolicy` 和 `DataScopeGuard` 接入,不重建权限体系。工作台采用一个服务端聚合接口,依据当前登录主体推导 scope,卡片和详情列表复用同一组查询条件。
**Tech Stack:** Java 21 / Spring Boot 3 / MyBatis-Plus / PostgreSQL / Redis / Flyway / Vue 3 / TypeScript / 微信原生小程序 / Maven / pnpm / Python pytest。
**Spec:** `C:\Users\13070\.codex\attachments\155243ba-d394-45a0-b100-8c44baf9094d\已粘贴的文本.txt`;审计基线:`docs/audits/V1.2-现状审计与差距矩阵-2026-10-23.md`(若该审计文件尚未写入,则以本计划中的审计结论为准)。
## Global Constraints
- 用户永久免费;不新增 VIP、会员、金币或收费路径。
- PostgreSQL 是 Source of Truth;Redis 不保存业务状态,不覆盖数据库状态。
- 所有权限范围由后端从认证主体、角色、组织和有效分配关系推导;不信任请求体中的 `userId`、`matchmakerId`、`teamId` 或角色字段。
- 审核状态、账号状态、手机号核验、授权、服务资格、凭据发放必须独立存储,禁止合并为一个完成字段。
- 用户完成实名认证后,任何个人资料变更都生成新版本并进入 `pending_review`;新版本审核通过前,不得在推荐、搜索、活动匹配、红娘推荐或其他用户可见资料中使用。
- 资料重新审核期间,后端按“未通过审核”处理;不能只在前端隐藏,也不能继续使用未审核的新资料计算匹配。
- 未确认的登录标识、密码发放渠道、授权正式文案、重复资料处理、自动分配算法和看板统计口径在 Gate 0 前不得进入业务代码。
- 不重写 V001-V061;V1.2 新迁移从 V062 开始,所有迁移向前兼容已有数据。
- 不增加第三方依赖;批量导入首版使用 UTF-8/UTF-8 BOM CSV,`.xlsx` 作为明确不支持格式返回可读错误。
- Mock 短信/微信只能在显式测试或演示 profile 启用;生产配置不得把 Mock 验证当作真实核验。
- 每个任务先写失败测试,再写最小实现;任务完成后输出阶段 Completion Report,不自动进入下一任务。
- 任何外部短信、微信、生产数据库和真实用户 UAT 都不是本地测试的替代证据。
## Gate 0:产品和数据决策冻结
**Files:**
- Modify: `docs/governance/open-questions.md`
- Modify: `docs/governance/decision-register.md`
- Modify: `docs/governance/config-register.md`
- Create: `docs/v12/V1.2-状态机与接口契约.md`
- Create: `docs/v12/V1.2-字段与统计口径表.md`
**必须确认的决策:**
1. 用户登录标识:手机号 + 密码,或平台发放的独立用户名 + 密码。
2. 初始密码的实际发放渠道;失败时由谁、通过什么双人复核流程人工发放。
3. 两份授权书的正式文本、版本号、拒绝后的服务状态和重新授权规则。
4. 历史微信用户的补授权、手机号核验和账号关联策略。
5. 重复手机号/重复资料的拒绝、合并或人工队列规则。
6. 自动分配算法、跨组规则、无可用红娘时的待分配队列行为。
7. 六项看板的状态、时间范围、去重规则和“有效匹配”正式口径。
8. 红娘组长是否可建档、导入、直接分配及调整组内红娘关系。
**验收:** 所有决策进入 `decision-register.md`;仍未确认的内容进入 `open-questions.md`,并在代码计划中标记为配置或阻断,不写成默认业务规则。
---
### Task 1: 建立 V1.2 状态模型和迁移
**Files:**
- Create: `backend/src/main/resources/db/migration/V062__add_v12_user_account_lifecycle.sql`
- Create: `backend/src/main/resources/db/migration/V063__add_v12_import_batches.sql`
- Create: `backend/src/main/resources/db/migration/V064__add_v12_consent_and_service_state.sql`
- Modify: `backend/src/test/resources/schema-h2.sql`
- Create: `backend/src/test/java/com/danyang/match/platform/deployment/V12MigrationContractTest.java`
- Create: `backend/src/main/java/com/danyang/match/platform/account/entity/UserCredential.java`
- Create: `backend/src/main/java/com/danyang/match/platform/account/entity/UserAccountLifecycle.java`
- Create: `backend/src/main/java/com/danyang/match/platform/account/entity/UserConsentState.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/entity/UserImportBatch.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/entity/UserImportRow.java`
**Interfaces:**
- Produces independent fields for `profile_review_status`, `profile_revision`, `published_profile_revision`, `account_creation_status`, `credential_delivery_status`, `phone_verification_status`, `service_consent_status`, and `service_eligibility_status`.
- Produces password hash, `must_change_password`, password version, credential issued/used timestamps, and delivery channel/status; never stores plaintext passwords.
- Produces import batch and row status/error fields with a unique idempotency key per batch.
- Preserves existing `users`, `operators`, `review_tasks`, `consent_records` rows and existing V1.1 status values.
- [x] **Step 1: Write migration contract tests** for new tables/columns, allowed status values, indexes, uniqueness, and absence of plaintext credential columns.
- [x] **Step 2: Run the contract tests**; initial run failed because V062-V064 did not exist, then passed after implementation.
- [x] **Step 3: Add V062-V064 migrations** with backfill-safe columns, batch row uniqueness, and indexes for status/scope queries.
- [x] **Step 3a: Add profile revision storage**: retain the last approved/published revision and backfill approved users; atomic profile-write transition is implemented in Task 4 at the service boundary.
- [x] **Step 4: Mirror required tables and constraints in `schema-h2.sql`** and keep PostgreSQL-specific DDL covered by migration contract tests.
- [x] **Step 5: Add lifecycle entities** without changing existing V1.1 entity semantics; profile write behavior remains a Task 4 concern.
- [x] **Step 6: Run migration and entity tests**; no modification to V001-V061.
- [x] **Step 7: Write `docs/v12/V1.2-状态机与接口契约.md`** with allowed transitions, terminal states, idempotency behavior, and actor/audit rules.
**验收:** Empty PostgreSQL can migrate V001-V064; existing demo data remains readable; duplicate active credential/phone/batch rows are rejected by database constraints.
### Task 2: 实现用户自主注册、手机号核验和历史账号关联
**Files:**
- Create: `backend/src/main/java/com/danyang/match/platform/user/dto/UserRegistrationRequest.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/dto/PhoneVerificationRequest.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/service/UserRegistrationService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/service/PhoneVerificationService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/auth/controller/AuthController.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/auth/service/AuthService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/auth/provider/SmsVerificationProvider.java`
- Create: `backend/src/main/java/com/danyang/match/platform/auth/provider/MockSmsVerificationProvider.java`
- Create: `backend/src/main/java/com/danyang/match/platform/auth/provider/ConfiguredSmsVerificationProvider.java`
- Create: `backend/src/test/java/com/danyang/match/platform/user/UserRegistrationServiceTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/auth/PhoneVerificationGuardTest.java`
- Modify: `miniapp/services/authService.js`
- Modify: `miniapp/pages/login/login.ts`
- Create: `miniapp/pages/register/register.ts`
- Create: `miniapp/pages/register/register.wxml`
- Create: `miniapp/pages/register/register.wxss`
- Create: `miniapp/pages/register/register.json`
**Interfaces:**
- `POST /api/v1/auth/register` consumes name, organization, organizationPhone, phone, and verification transaction; returns a pending user reference, never an active service token.
- `POST /api/v1/auth/phone/send-code` and `POST /api/v1/auth/phone/verify` use a provider abstraction and one-time Redis transaction; production provider must fail closed when unconfigured.
- `POST /api/v1/auth/wechat/login` remains compatible, but new and historical users receive explicit lifecycle state in the response.
- Duplicate phone returns a stable conflict code and does not overwrite existing records.
- [x] **Step 1: Add failing tests** for phone validation, code expiry/replay, rate limits, duplicate phone, and provider refusal outside test/demo profiles.
- [x] **Step 2: Run targeted tests** and confirm failure before implementation.
- [x] **Step 3: Implement provider and Redis transaction** with hashed code storage, expiry, attempt limit, cooldown, and audit-safe logs; never log the code or full phone.
- [x] **Step 4: Implement registration transaction** that writes the registration application and marks phone verification separately.
- [x] **Step 5: Preserve WeChat login compatibility**; ambiguous historical account association remains blocked until its explicit association flow is implemented in a later task.
- [x] **Step 6: Add miniapp registration and error/loading states** using existing API and UI conventions.
- [x] **Step 7: Run backend tests and miniapp type-check**.
**验收:** Unverified users cannot enter the service flow; duplicate phone cannot overwrite existing data; the same verification transaction cannot be replayed; real-provider configuration is required for production.
### Task 3: 实现后台建档、CSV 导入和审核后账号发放
**Files:**
- Create: `backend/src/main/java/com/danyang/match/platform/user/dto/OperatorCreateUserRequest.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/dto/UserImportPreviewRequest.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/dto/UserImportPreviewResponse.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/service/UserOnboardingService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/service/UserImportService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/user/service/UserCredentialService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/user/controller/UserController.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/review/service/ReviewService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/review/controller/ReviewController.java`
- Create: `backend/src/test/java/com/danyang/match/platform/user/UserImportServiceTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/user/UserAccountLifecycleTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/review/ReviewAccountIssuanceTest.java`
- Create: `admin-web/src/modules/user/UserCreateView.vue`
- Create: `admin-web/src/modules/user/UserImportView.vue`
- Modify: `admin-web/src/modules/user/UserManagementView.vue`
- Modify: `admin-web/src/router/index.ts`
**Interfaces:**
- `POST /api/v1/operator/users` creates a draft profile only; it does not bypass phone verification, authorization, or review.
- `POST /api/v1/operator/users/import/preview` accepts CSV bytes and returns row-level normalized values/errors without writes.
- `POST /api/v1/operator/users/import/commit` consumes a preview batch id plus `Idempotency-Key`, writes valid rows only, and records every row result.
- `POST /api/v1/operator/users/{id}/issue-account` is idempotent and allowed only after approved profile + verified phone + required consent policy.
- Account issuance returns a masked delivery result and one-time credential reference; plaintext initial password is never persisted or written to logs.
- [x] **Step 1: Write failing tests** for permission, CSV encoding/header validation, row errors, duplicate rows within a file, duplicate database phone, preview no-write, commit idempotency, and retry behavior.
- [x] **Step 2: Run targeted tests** and confirm failure.
- [x] **Step 3: Implement manual create and CSV preview** with explicit required columns: name, organization, organizationPhone, phone; unsupported formats are rejected by the CSV-only endpoint.
- [x] **Step 4: Implement commit transaction** with row-level success/failure persistence, Redis idempotency, and no partial overwrite of an existing user.
- [x] **Step 5: Connect review approval to account issuance eligibility and profile publication**; approval publishes the reviewed revision while rejection/need-edit preserves the previous published revision.
- [x] **Step 6: Implement secure random initial password generation and hash storage** with `must_change_password=true`; only a credential reference is returned.
- [x] **Step 7: Add admin UI manual-create and CSV preview/commit workflow** with permission-based visibility.
- [x] **Step 8: Run backend tests and admin type-check**.
**验收:** Preview performs no database write; commit can be safely retried; failed rows are explainable; approval, phone verification, consent, account status and profile publication remain separate; no plaintext credential is stored; a changed authenticated profile is excluded from matching until its new revision is approved.
### Task 4: 实现用户/红娘登录、首次改密和授权门禁
**Files:**
- Create: `backend/src/main/java/com/danyang/match/platform/auth/dto/PasswordLoginRequest.java`
- Create: `backend/src/main/java/com/danyang/match/platform/auth/dto/InitialPasswordChangeRequest.java`
- Create: `backend/src/main/java/com/danyang/match/platform/account/service/UserCredentialService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/account/service/ServiceConsentService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/auth/service/AuthService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/auth/controller/AuthController.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/auth/service/OperatorAuthService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/auth/controller/OperatorAuthController.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/profile/controller/ProfileController.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/profile/service/UserProfileService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/profile/mapper/UserProfileMapper.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/capability/service/CapabilityService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/account/controller/ServiceConsentController.java`
- Create: `backend/src/test/java/com/danyang/match/platform/auth/InitialPasswordGuardTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/account/ServiceConsentGateTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/account/ConsentVersionAuditTest.java`
- Create: `miniapp/pages/initial-password/initial-password.ts`
- Create: `miniapp/pages/initial-password/initial-password.wxml`
- Create: `miniapp/pages/initial-password/initial-password.wxss`
- Create: `miniapp/pages/service-consent/service-consent.ts`
- Create: `miniapp/pages/service-consent/service-consent.wxml`
- Create: `miniapp/pages/service-consent/service-consent.wxss`
- Modify: `miniapp/stores/authStore.js`
- Modify: `miniapp/app.js`
**Interfaces:**
- `POST /api/v1/auth/password/login` authenticates the confirmed login identifier and returns `mustChangePassword`, `consentRequired`, and `serviceActive` state.
- `POST /api/v1/auth/password/change-initial` is the only non-essential endpoint allowed while `must_change_password=true`; success rotates credential version and invalidates old sessions.
- `GET /api/v1/account/service-consent` returns the two required document versions and current user decisions.
- `POST /api/v1/account/service-consent` requires explicit positive confirmation for each required document and records version, actor, timestamp, IP, user agent, and audit event.
- All match, chat, matchmaker, counseling, appointment and activity service entry points call a shared `ServiceConsentService.requireActive(userId)` or equivalent policy check.
- [ ] **Step 1: Write failing tests** for initial-login restriction, wrong password rate limiting, password complexity, old credential invalidation, unchecked consent rejection, version mismatch, withdrawal, and reactivation.
- [ ] **Step 2: Run targeted tests** and confirm failure.
- [ ] **Step 3: Implement password login and initial-change flow** with the existing JWT/Redis conventions; never expose password hashes or plaintext credentials.
- [ ] **Step 4: Implement consent state and audit** using the formal versions from Gate 0; do not treat silence or a pre-checked UI box as consent.
- [ ] **Step 5: Add profile revision gate** to profile update endpoints: after authentication, every personal-profile write creates a new revision and moves the profile to `pending_review`; fields that are not personal-profile data must be explicitly classified rather than silently bypassing review.
- [ ] **Step 6: Add shared backend gate** to each service boundary. The gate must require the latest profile revision to equal the published approved revision, not just trust the front-end capability response.
- [ ] **Step 7: Add miniapp first-login and profile-update routing** for change-password, consent, profile confirmation, pending-review, rejection and resubmission states.
- [ ] **Step 8: Run negative permission, profile-revision and service-block tests** plus miniapp type-check.
**验收:** Refusal sets the confirmed inactive/suspended service state; protected service APIs reject the user server-side; changing the initial password and accepting both documents are separately auditable; an authenticated profile edit immediately leaves the new revision unpublished and excluded from matching until review approval.
### Task 5: 接入红娘自动分配前置条件和账号管理闭环
**Files:**
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/service/MatchmakerEligibilityPolicy.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/matchmaker/service/MatchmakerAssignmentService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/matchmaker/service/ServiceAssignmentPolicy.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/operator/service/OperatorManagementService.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/operator/controller/OperatorController.java`
- Create: `backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerAssignmentEligibilityTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/matchmaker/ProfileRevisionAssignmentGuardTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/operator/OperatorCredentialLifecycleTest.java`
- Modify: `backend/src/main/resources/db/migration/V065__add_v12_assignment_eligibility_audit.sql`
**Interfaces:**
- `MatchmakerEligibilityPolicy.assertEligible(userId)` checks, in order: approved profile, verified phone, required consent, normal account status, confirmed service eligibility.
- Existing `MatchmakerAssignmentService.create/transfer` remains the write boundary and calls the policy before creating an active assignment.
- Operator creation requires an explicit approved role (`matchmaker` or `matchmaker_leader`), organization scope, secure credential issuance state, and audit record.
- Disabling a matchmaker cannot silently drop active assignments; it follows the Gate 0 decision for transfer queue/leader handling.
- [ ] **Step 1: Write failing tests** for each missing eligibility condition, concurrent assignment, cross-organization leader denial, disabled target, and re-enable behavior.
- [ ] **Step 2: Run targeted tests** and confirm failure.
- [ ] **Step 3: Implement `MatchmakerEligibilityPolicy`** by reusing `CapabilityService`, consent policy and the published-profile-revision check; do not duplicate status rules in controllers.
- [ ] **Step 4: Connect policy to create/transfer** within the existing transaction and preserve database unique-index race protection.
- [ ] **Step 5: Harden operator creation** with request validation, role binding, audit, password policy and initial-change flag.
- [ ] **Step 6: Implement disabled-operator assignment handling** exactly as decided at Gate 0, with an audit event and no data loss.
- [ ] **Step 7: Run the existing matchmaker suite plus new eligibility tests**.
**验收:** No ineligible user can receive an active automatic/manual assignment; ordinary matchmakers cannot claim or browse unassigned users; group leaders remain organization-scoped; disabling a matchmaker leaves an auditable, recoverable assignment state.
### Task 6: 重建六项范围看板和详情一致性
**Files:**
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerDashboardScope.java`
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerDashboardMapper.java`
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerWorkbenchService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerWorkbenchController.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/analytics/AnalyticsService.java`
- Create: `backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerWorkbenchScopeTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerDashboardConsistencyTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerDashboardAuthorizationTest.java`
- Modify: `admin-web/src/services/dashboard.ts`
- Modify: `admin-web/src/types/dashboard.ts`
- Modify: `admin-web/src/views/DashboardView.vue`
- Modify: `admin-web/src/router/index.ts`
**Interfaces:**
- `GET /api/v1/operator/matchmaker/workbench/dashboard` returns `{ scope, metrics, availability, generatedAt }` for the six cards and shared query metadata.
- `MatchmakerDashboardScope` is derived server-side: ordinary matchmaker = active assignments owned by principal; group leader = same organization; administrator = platform scope.
- The six metric keys remain `pending_reviews`, `matchmaker_todos`, `counseling_todos`, `station_appointments`, `effective_matches`, and `activity_registrations` to preserve the existing UI contract.
- Detail list endpoints receive only pagination/time filters; they cannot receive an alternate operator/team scope. They call the same scope builder and status predicates as the aggregate query.
- User/profile-backed metrics and drill-down records exclude rows where `profile_revision != published_profile_revision`; an unreviewed profile revision must not leak through the dashboard or matching data.
- [ ] **Step 1: Freeze metric definitions** in `docs/v12/V1.2-字段与统计口径表.md`, including included/excluded statuses, timezone `Asia/Shanghai`, time window, and deduplication.
- [ ] **Step 2: Write failing authorization/consistency tests** for ordinary matchmaker, same-team leader, other-team leader, administrator, empty data, permission denied, and aggregate/detail equality.
- [ ] **Step 3: Run targeted tests** and confirm failure.
- [ ] **Step 4: Implement scope builder and SQL filters** at query level; do not fetch global rows and filter in Vue. Add the published-profile-revision predicate to every user/profile-backed metric and detail query.
- [ ] **Step 5: Implement one aggregate endpoint** while preserving older analytics endpoints for existing analytics pages.
- [ ] **Step 6: Update `dashboard.ts`** to use the aggregate response for six cards; keep loading, empty, forbidden, partial-failure states distinct.
- [ ] **Step 7: Add detail navigation tests** showing a card click cannot broaden the scope.
- [ ] **Step 8: Run backend scope tests, admin type-check, build, and UI contract tests**.
**验收:** Six cards show `0` for real empty data, a distinct no-permission state for forbidden data, and a distinct failure state for query failure; ordinary matchmaker, leader, and administrator counts match their permitted detail lists; changing request parameters cannot change scope.
### Task 7: 活栈 E2E、文档收口和发布门禁
**Files:**
- Create: `tests/e2e/v12_account_lifecycle_test.py`
- Create: `tests/e2e/v12_import_and_review_test.py`
- Create: `tests/e2e/v12_matchmaker_dashboard_test.py`
- Modify: `tests/e2e/conftest.py`
- Modify: `backend/src/test/resources/application-test.yml`
- Modify: `docs/STATUS.md`
- Create: `docs/Phase-V12-Completion-Report.md`
- Modify: `docs/api/API_INVENTORY.md`
- Modify: `docs/database/MIGRATION_GUIDE.md`
- Modify: `README.md`
- Modify: `.github/workflows/ci.yml`
**Interfaces:**
- E2E uses a clean PostgreSQL/Redis stack and explicit test providers; it never treats H2-only results as release evidence.
- CI runs migration validation, backend tests, admin type-check/build, miniapp type-check, and V1.2 E2E; any failed gate blocks completion.
- Completion report records current commit, migration version, test counts, skipped tests/reasons, runtime configuration, and unresolved production evidence.
- [ ] **Step 1: Add E2E fixtures** for a user, duplicate phone, pending/approved review, ordinary matchmaker, same-team leader, other-team leader, and administrator.
- [ ] **Step 2: Add the account lifecycle path**: register or import → preview → commit → review → account issue → password login → forced change → two consents → service capability.
- [ ] **Step 2a: Add the profile revision path**: approved user edits personal profile → new revision becomes pending → old approved revision remains the only published revision → review approval publishes the new revision → matching and display resume.
- [ ] **Step 3: Add negative paths**: duplicate phone, expired code, repeated commit, refused consent, pending/rejected profile excluded from search/recommendation/matchmaker recommendation, cross-team dashboard query, and disabled matchmaker.
- [ ] **Step 4: Add dashboard consistency checks** for all six cards and corresponding detail lists under all three role scopes, including exclusion of unpublished profile revisions.
- [ ] **Step 5: Run the full local gate**:
```powershell
mvn -B -f backend/pom.xml test
pnpm --dir admin-web type-check
pnpm --dir admin-web build
npm --prefix miniapp run type-check
python -m pytest tests/e2e -q
```
- [ ] **Step 6: Fix environment blockers separately** if `spawn EPERM`, locked `target/classes`, missing Redis, or missing production-provider credentials occur; do not weaken tests.
- [ ] **Step 7: Write Completion Report** with `PASS/PARTIAL/FAIL/NOT_IMPLEMENTED/NOT_VERIFIED` evidence and stop for user acceptance.
**验收:** Clean-stack E2E proves the complete V1.2 path and role isolation; CI is green; no historical test count is reused as current evidence; production remains `NO-GO` until real SMS/WeChat, TLS, backup/restore, monitoring, rollback and user UAT are separately proven.
## Dependency Order and Release Gates
```text
Gate 0 product decisions
|
v
Task 1 state model and migrations
|
+--> Task 2 registration and phone verification
| |
| v
+--> Task 3 admin create/import/review/issuance
|
v
Task 4 first login/password/consent gate
|
+--> Task 5 assignment eligibility
|
+--> Task 6 scoped dashboard
|
v
Task 7 E2E and release gate
```
**Phase completion rules:**
- Gate 0: all mandatory product decisions recorded.
- Task 1: V062-V064 migrate on a clean database and existing data remains readable.
- Task 2: phone verification cannot be bypassed or replayed.
- Task 3: import is previewable, idempotent, auditable and cannot bypass review/consent.
- Task 4: initial password, consent and unpublished profile revisions are backend-enforced.
- Task 5: assignment eligibility and role scope are negative-tested.
- Task 6: cards and details share scope and metric definitions.
- Task 7: full current test evidence is recorded and user acceptance is requested.
## Explicitly Deferred
- Direct `.xlsx` parsing; first release accepts UTF-8 CSV and returns a clear unsupported-format error for `.xlsx`.
- A new automatic assignment algorithm before the product decision; the existing explicit assignment path remains available.
- Any payment, VIP, AI代聊, community feed, microservice, Kubernetes, or unrelated V1.2 expansion.
- Production SMS/WeChat provider integration, real credentials, production TLS, backup/restore rehearsal and real-user UAT until their environment and owners are available.
## Plan Self-Check
- Registration fields, phone verification, duplicate handling: Task 2.
- Manual create, CSV preview/commit, row errors, review linkage: Task 3.
- Independent account/credential/consent/service states: Task 1 and Task 4.
- Initial password rules, hash-only storage, forced change: Task 3 and Task 4.
- Authenticated profile update creates a new revision and blocks display/matching until approval: Task 1, Task 3, Task 4, Task 5, Task 6 and Task 7.
- Administrator-only matchmaker account opening and group scope: Task 5.
- Automatic assignment prerequisites and no-available-matchmaker behavior: Gate 0 and Task 5.
- Six dashboard cards, role scopes, empty/error/forbidden states, card/detail consistency: Task 6.
- Tests, migrations, frontend builds, clean-stack E2E, documentation and completion report: Task 7.
## Execution Handoff
Plan complete and saved to `docs/superpowers/plans/2026-10-23-v12-account-lifecycle-matchmaker-dashboard.md`. Execute it task-by-task with review checkpoints; implementation must begin at Gate 0 and stop after each phase Completion Report.
@@ -0,0 +1,105 @@
# 工作台数据范围与运营角色管理实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让普通红娘查看本人工作台数据、红娘组长查看本组数据、管理员查看全平台统计,并在管理员端支持创建/分配红娘角色与组长角色;管理员不展示红娘业务明细页面。
**Architecture:** 后端从当前操作员角色和组织推导 `SELF`、`TEAM`、`ALL` 数据范围;前端只消费统一工作台聚合接口。角色使用现有 `roles`、`permissions`、`operator_roles`、`role_permissions` 表,管理员只能分配预置角色,不能直接给单个账号勾选任意权限。
**Tech Stack:** Java 21 / Spring Boot 3 / PostgreSQL / Flyway / MyBatis-Plus / Vue 3 / TypeScript / Vite。
**Spec:** `docs/superpowers/plans/2026-10-23-v12-account-lifecycle-matchmaker-dashboard.md` Task 5/6;`backend/src/main/resources/db/migration/V054__add_matchmaker_leader_role.sql`。
## Global Constraints
- PostgreSQL 是权限和数据范围 Source of Truth;前端隐藏菜单不是安全边界。
- 普通红娘只能读取本人有效分配/本人关联数据;组长只能读取同组织数据;管理员读取全平台聚合数据。
- 咨询正文、身份证、手机号等敏感数据继续遵守既有最小权限规则。
- 不新增第三方依赖,不重写 V1.1 业务模块,不删除现有数据。
- 普通 `matchmaker` 不拥有 `matchmaker.assign`;组长使用 `matchmaker.team.assign`。
---
### Task 1: 角色管理权限与接口契约
**Files:**
- Create: `backend/src/main/resources/db/migration/V067__add_workbench_view_and_role_management.sql`
- Modify: `backend/src/main/java/com/danyang/match/platform/operator/controller/OperatorController.java`
- Modify: `backend/src/main/java/com/danyang/match/platform/operator/service/OperatorManagementService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/operator/dto/OperatorRoleUpdateRequest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/operator/OperatorRoleManagementTest.java`
**Interfaces:**
- `GET /api/v1/operator/operators/roles` returns caller-allowed role templates and inherited permissions.
- `GET /api/v1/operator/operators/{id}` returns roles, organization and derived scope.
- `POST /api/v1/operator/operators` accepts `roleCodes` and binds roles transactionally.
- `PUT /api/v1/operator/operators/{id}/roles` replaces role bindings transactionally.
- Only `permission.manage` callers may use these endpoints; server validates role assignability, organization scope, last-super-admin protection and self-demotion protection.
- [ ] Write failing tests for role binding, invalid role, missing organization for team leader, and unauthorized role escalation.
- [ ] Run targeted tests and verify expected failures.
- [x] Add `workbench.view`; grant it to `matchmaker`, `matchmaker_leader`, `platform_admin`, `super_admin`.
- [x] Remove `matchmaker.assign` from ordinary `matchmaker` role while preserving administrator assignment capability.
- [x] Implement role catalog, transactional create/update, audit events, and effective permission response.
- [x] Run operator and migration tests.
### Task 2: Scoped workbench aggregation
**Files:**
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerDashboardScope.java`
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerWorkbenchService.java`
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerWorkbenchController.java`
- Create: `backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerWorkbenchMapper.java`
- Create: `backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerWorkbenchScopeTest.java`
- Create: `backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerDashboardAuthorizationTest.java`
**Interfaces:**
- `GET /api/v1/operator/workbench/dashboard` returns `scope`, `scopeLabel`, five card metrics, and each card's detail permission.
- Scope resolver: ordinary matchmaker = `SELF`; leader = same `organization_id` with `NULL=NULL` platform-team semantics; platform/super admin = `ALL`.
- Queries filter review tasks, matchmaker assignments/services, counseling assignments, service appointments, matchmaker-linked matches, and matchmaker-linked activity registrations using server-derived operator/team predicates.
- Aggregates never return counseling正文 or sensitive user fields.
- [ ] Write failing scope-isolation tests for self/team/all and cross-organization denial.
- [x] Implement scope resolver and aggregate queries.
- [ ] Add tests proving request parameters cannot widen scope.
- [ ] Run targeted backend tests.
### Task 3: Admin operator and role management UI
**Files:**
- Modify: `admin-web/src/modules/operator/OperatorManagementView.vue`
- Create: `admin-web/src/components/operator/RolePermissionPreview.vue`
- Modify: `admin-web/src/api/client.ts` only if typed helper is needed
- Create/Modify: `admin-web/src/types/operator.ts`
- [x] Add role, scope, and organization columns.
- [x] Add role selection and inherited-permission preview to create dialog.
- [x] Add role edit dialog with organization validation and confirmation.
- [ ] Show audit-safe error messages; do not expose password hashes or raw permissions from local storage.
- [ ] Run admin type-check and build.
### Task 4: Workbench frontend and administrator route policy
**Files:**
- Modify: `admin-web/src/services/dashboard.ts`
- Modify: `admin-web/src/types/dashboard.ts`
- Modify: `admin-web/src/views/DashboardView.vue`
- Modify: `admin-web/src/layouts/MainLayout.vue`
- Modify: `admin-web/src/router/index.ts`
- Create: `admin-web/tests/workbench-role-scope.cjs`
- [x] Use the unified workbench endpoint for the six workbench metrics while retaining detail requests for secondary panels.
- [x] Render the server-derived scope label and values in the workbench cards.
- [x] Hide `/referral` and `/matchmaker` navigation for administrators.
- [x] Keep those pages for ordinary matchmakers and leaders.
- [x] Redirect administrator direct navigation to those routes back to `/dashboard`.
- [x] Show aggregate cards even without detail permissions; detail buttons remain permission-gated.
- [x] Run UI type-check and build.
### Task 5: Verification and handoff
- [x] Run Flyway migration validation and backend targeted tests.
- [ ] Log in as ordinary matchmaker, group leader, and administrator.
- [ ] Verify self/team/all counts with cross-organization negative cases.
- [ ] Verify administrator has no red娘 detail navigation and can see statistics on dashboard.
- [ ] Update `docs/STATUS.md` and provide a Completion Report with evidence and residual risks.
@@ -0,0 +1,160 @@
# V1.2 权限基线与范围看板收口实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans or superpowers:subagent-driven-development. Steps use checkbox syntax.
**目标:** 按 V1.2 需求、实施计划和决策登记表,修复角色权限、后端数据范围、工作台统计和前端入口不一致问题。
**架构:** 保留模块化单体和 PostgreSQL Source of Truth。权限继续由 role_permissions 与后端门禁控制;数据范围由认证主体、角色、组织和有效分配关系服务端推导。聚合查询与详情查询共用同一 scope builder 和状态谓词。
**技术栈:** Java 21、Spring Boot 3、MyBatis-Plus、PostgreSQL、Flyway、Vue 3、TypeScript、微信原生小程序、Maven、pnpm。
**规范:**
- C:/Users/13070/.codex/attachments/155243ba-d394-45a0-b100-8c44baf9094d/已粘贴的文本.txt
- E:/develop/公益婚恋服务平台/docs/superpowers/plans/2026-10-23-v12-account-lifecycle-matchmaker-dashboard.md
- E:/develop/公益婚恋服务平台/docs/governance/decision-register.md
## 全局约束
- 普通红娘只访问本人有效分配对象;红娘组长只访问本人所属组织;管理员才可查看全平台。
- 后端不信任请求体中的 userId、matchmakerId、teamId 或角色字段。
- 无权限、真实空数据、查询失败必须分别返回和展示。
- 六项卡片必须与对应详情列表使用相同范围和状态口径。
- 未审核的新资料版本不得进入展示、推荐、匹配、红娘服务或统计。
- 不删除历史数据,不重写 V001-V061,不引入新第三方依赖。
---
### Task 1:冻结角色权限矩阵与冲突决策
**文件:**
- 新建 docs/v12/V1.2-角色权限与前端入口矩阵.md
- 修改 docs/governance/decision-register.md(仅在冲突裁决确认后)
- 新建 backend/src/test/java/com/danyang/match/platform/deployment/PermissionBaselineContractTest.java
**步骤:**
- [ ] 查询活跃 PostgreSQL 的 Flyway 版本、角色权限、操作员角色和组织归属。
- [ ] 对照 V070/V071 与 V1.2 要求,记录“红娘是否拥有五类业务操作权限”的正式裁决,不得默默覆盖历史迁移。
- [ ] 为 user、matchmaker、matchmaker_leader、platform_admin、super_admin、reviewer、activity_manager、risk_operator、organization_admin、counselor、counseling_manager、service_station_operator、content_editor、data_viewer 建立权限、范围、菜单和禁止操作矩阵。
- [ ] 添加矩阵契约测试,确保普通红娘不能通过参数修改读取其他人员或组织的数据。
- [ ] 运行后端契约测试并记录基线结果。
**验收:** Task 2-5 均以同一矩阵为准,V070/V071 冲突已被明确记录。
### Task 2:修复工作台权限可用性与组织范围
**文件:**
- 修改 backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerWorkbenchService.java
- 修改 backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerDashboardScope.java
- 新建 backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerWorkbenchAuthorizationTest.java
- 新建 backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerWorkbenchScopeTest.java
- 如需数据约束,经批准后新建 backend/src/main/resources/db/migration/V072__validate_matchmaker_organization_scope.sql
**步骤:**
- [ ] 为普通红娘、同组织组长、跨组织组长、平台管理员和组织为空的组长编写失败测试。
- [ ] 每个指标先检查对应 detailPermission;无权限时不得返回统计值,返回 available=false 和稳定权限标识。
- [ ] 授权但无数据时返回真实 0,不与无权限混淆。
- [ ] 组织为空的组长必须被拒绝或进入明确异常状态,不能把所有 NULL 组织当成一个团队。
- [ ] 对用户资料相关指标增加 published revision 条件。
- [ ] 运行范围和授权测试。
**验收:** 未授权指标不返回数据;授权空数据返回 0;组长不能看到其他组织或所有未分组数据。
### Task 3:统一六项详情接口的数据范围
**文件:**
- 新建或修改 backend/src/main/java/com/danyang/match/platform/matchmaker/dashboard/MatchmakerDashboardMapper.java
- 修改 review、counseling、service_appointment、analytics、activity 相关 Service/Controller
- 新建 backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerDashboardDetailScopeTest.java
**步骤:**
- [ ] 详情接口只接受分页和时间筛选,不接受替代 operator/team scope。
- [ ] 为普通红娘、同组织组长、跨组织组长和管理员添加越权失败测试。
- [ ] 在数据库查询层应用 SELF、TEAM、ALL 条件,不在 Vue 过滤全量数据。
- [ ] 统一各指标纳入和排除状态,并排除未发布资料版本。
- [ ] 验证聚合数字等于允许访问的详情列表数量。
- [ ] 敏感佐证和咨询备注继续独立授权并留痕。
**验收:** 卡片与详情一致;修改请求参数不能扩大范围;跨组织记录不可探测。
### Task 4:修复管理端卡片、导航和角色显示
**文件:**
- 修改 admin-web/src/services/dashboard.ts
- 修改 admin-web/src/types/dashboard.ts
- 修改 admin-web/src/views/DashboardView.vue
- 修改 admin-web/src/router/index.ts
- 修改 admin-web/src/layouts/MainLayout.vue
- 修改 admin-web/src/stores/auth.ts
- 新建 admin-web/tests/dashboard-permission-contract.test.ts
**步骤:**
- [ ] 为普通红娘、组长、管理员、审核员、咨询人员和服务站人员添加前端权限契约测试。
- [ ] 区分 forbidden、真实 0 和 error;无权限卡片不可显示误导性数值,也不可导航到无权限页面。
- [ ] 保留授权角色的六张卡片和真实 0。
- [ ] 角色名称改为使用服务端角色信息,消除“红娘组长被显示为资料审核人员”的权限顺序冲突。
- [ ] 管理员继续隐藏独立“公益红娘/红娘服务”菜单,只在工作台展示统计。
- [ ] 运行 admin type-check、build 和前端契约测试。
**验收:** 前端入口与后端权限矩阵一致,禁止把无权限显示成空数据或可操作入口。
### Task 5:补齐其他运营角色的最小权限
**文件:**
- 新建 backend/src/main/resources/db/migration/V073__align_operator_role_permissions.sql
- 修改 admin-web/src/layouts/MainLayout.vue
- 修改 admin-web/src/stores/auth.ts
- 新建 backend/src/test/java/com/danyang/match/platform/deployment/OperatorRolePermissionContractTest.java
**步骤:**
- [ ] 为 reviewer、activity_manager、risk_operator、organization_admin 编写最小权限契约测试。
- [ ] 仅按已确认矩阵写幂等迁移,禁止给非管理员授予全部权限。
- [ ] 让前端菜单与实际权限一致。
- [ ] 校验操作员创建/编辑只能分配当前管理员可授予的角色;组长必须有组织。
- [ ] 在干净数据库执行迁移验证。
**验收:** 每个正式运营角色都有可用的最小权限集合,且没有无关的全平台数据权限。
### Task 6:活栈端到端验收与文档收口
**文件:**
- 新建 tests/e2e/v12_permission_scope_dashboard_test.py
- 修改 tests/e2e/conftest.py
- 修改 docs/STATUS.md
- 新建 docs/Phase-V12-Permission-Scope-Completion-Report.md
- 修改 docs/api/API_INVENTORY.md
- 修改 docs/v12/V1.2-角色权限与前端入口矩阵.md
**步骤:**
- [ ] 使用干净 PostgreSQL/Redis 栈和真实迁移,不删除现有项目数据。
- [ ] 为普通用户、普通红娘、同组织组长、跨组织组长、平台管理员、审核员、咨询人员、服务站人员、活动管理员建立 fixture。
- [ ] 验证登录权限、菜单、六项卡片、详情列表、禁止状态、空数据和失败状态。
- [ ] 尝试跨组织和替代 operator/team 参数,断言拒绝且不泄露数据。
- [ ] 运行:
- mvn -B -f backend/pom.xml -s backend/maven-settings.xml test
- pnpm --dir admin-web type-check
- pnpm --dir admin-web build
- npm --prefix miniapp run type-check
- python -m pytest tests/e2e/v12_permission_scope_dashboard_test.py -q
- [ ] 生成 Completion Report,使用 PASS、PARTIAL、FAIL、NOT_VERIFIED,并停止等待用户验收。
**验收:** 当前代码、数据库、接口、前端入口和六项看板均通过同一权限矩阵;没有使用历史测试结果替代当前证据。
## 依赖顺序
Task 1 权限矩阵与冲突裁决
→ Task 2 聚合门禁与组织范围
→ Task 3 详情接口范围
→ Task 4 管理端展示
→ Task 5 其他运营角色
→ Task 6 活栈 E2E 和发布门禁
## 明确不做
- 不重新设计自动分配算法。
- 不新增支付、会员、社区、微服务或 Kubernetes。
- 不把身份证原件和咨询备注全文权限隐式授予红娘。
- 不直接修改生产数据库,不以隐藏前端按钮替代后端权限。
## 发布门禁
只有当 Task 1-6 均有当前运行证据,且 SELF、TEAM、ALL 三种范围的聚合数字与详情列表一致、未授权数据不返回、前端入口与后端矩阵一致时,才可判定本阶段完成。否则结论为 CONDITIONAL GO 或 NO-GO。
+26
View File
@@ -0,0 +1,26 @@
# 善缘丹阳 V1.2 字段与统计口径表
## 1. 账号与资料字段
| 字段 | 口径 |
|------|------|
| `phone_verification_status` | 手机号核验状态,独立于实名认证和账号状态 |
| `profile_revision` | 当前最新资料版本 |
| `published_profile_revision` | 当前唯一对外发布版本;为空则不可展示和匹配 |
| `profile_review_status` | `pending_review`、`approved`、`rejected`、`need_edit` |
| `must_change_password` | 初始凭据登录后必须为真,改密成功后置为假 |
| `service_consent_status` | 两份授权分别记录版本、状态、确认时间和审计主体 |
| `service_eligibility_status` | 服务资格独立于审核、账号和授权状态 |
## 2. 六项看板指标
| key | 含义 | 排除条件 |
|-----|------|----------|
| `pending_reviews` | 当前权限范围内待处理审核数 | 非本范围、已结束任务、未发布资料泄露到详情 |
| `matchmaker_todos` | 当前范围内待服务/待跟进事项 | 无有效分配、账号非正常、资料未发布 |
| `counseling_todos` | 当前范围内咨询待办 | 非本人分配或非本组织范围 |
| `station_appointments` | 当前范围内服务站预约待办 | 已取消、已完成或越权范围 |
| `effective_matches` | 按已确认有效匹配定义统计 | 未发布资料、已解除关系、重复计数 |
| `activity_registrations` | 当前范围内有效活动报名 | 已取消、已拒绝、重复报名 |
默认时间基准为 `Asia/Shanghai`。聚合卡片和详情列表必须复用同一范围构建器、状态谓词和去重规则;空数据返回 0,无权限和查询失败使用不同状态。
+42
View File
@@ -0,0 +1,42 @@
# 善缘丹阳 V1.2 状态机与接口契约
## 1. 账号登录与服务准入
主登录标识为已核验手机号,密码只保存哈希。账号发放后必须首次改密;初始密码不落库、不写日志。两份服务授权书分别确认,任一拒绝或撤回时,账号可以登录处理必要事项,但不得进入匹配、搜索、推荐、聊天、活动匹配、红娘推荐、咨询或预约等服务入口。
## 2. 资料版本与审核
认证完成后的个人资料变更必须在同一事务中:
1. 写入新的 `profile_revision`;
2. 将新版本标记为 `pending_review`;
3. 保留旧的 `published_profile_revision`;
4. 记录操作者、时间和变更摘要。
只有审核通过才能把新版本设为 `published_profile_revision`。审核等待、拒绝或要求补充期间,旧的已发布版本继续作为唯一对外资料;新版本不得被搜索、推荐、活动匹配、红娘推荐或其他用户查看。若没有可用的旧发布版本,则用户不进入这些业务流程。
## 3. 红娘分配
- V1.2 首版只支持人工分配。
- 普通红娘只能访问本人有效分配对象。
- 红娘组长只能访问和管理本组织范围。
- 管理员拥有平台范围权限。
- 无可用红娘时写入待分配队列,不阻塞用户注册。
- 分配前必须校验:资料已发布、手机号已核验、授权有效、账号正常、服务资格有效。
## 4. 关键接口约束
所有接口使用 `/api/v1`,统一响应 `{ success, code, message, data }`。请求体中的 `userId`、`matchmakerId`、`teamId`、角色和范围字段不得决定权限;范围由服务端认证主体推导。关键写接口支持 `Idempotency-Key`。
| 场景 | 接口 | 关键约束 |
|------|------|----------|
| 密码登录 | `POST /auth/password/login` | 仅已核验手机号;返回首次改密、授权和服务状态 |
| 首次改密 | `POST /auth/password/change-initial` | 成功后旧凭据和旧会话失效 |
| 服务授权 | `GET/POST /account/service-consent` | 两份文档分别确认并审计版本 |
| 资料修改 | 现有资料更新接口 | 认证后个人资料写入必须创建新版本并进入审核 |
| 审核通过 | 现有审核接口 | 原子推进发布版本,才恢复展示和匹配 |
| 红娘看板 | `GET /operator/matchmaker/workbench/dashboard` | 只返回主体权限范围内的六项指标 |
## 5. 审计与敏感数据
密码、验证码、完整手机号、身份证号和授权正文不得写入普通日志。账号发放、手机号核验、授权确认/撤回、资料版本审核、红娘分配和越权拒绝必须保留审计事件。