129 KiB
职来后端 S01/S02/S03/Q01/R01 全项目最终独立总验收报告
日期:2026-09-18
性质:只验收,不开发。未修改生产代码、未修改已有测试断言、未改 V1–V45、未新增 V46、
未改 ErrorCode/DTO/接口/OpenAPI/pom.xml、未执行任何 git reset|restore|checkout|clean|add -A。
未暂存、未提交、未推送、未合并。
0. 最终判定
FAIL(未完成)
判定依据:本轮未执行完第九~十四节的全部运行时验收矩阵(令牌类型矩阵、刷新/重放/并发、 岗位权限矩阵、短信生产失败关闭的运行时验证、V43 真实迁移、管理员初始化矩阵、简历隐私矩阵、 Q01 运行时查询矩阵、GET 评分接口零写、写侧刷新任务、V45 claim token 100 轮、逐字段副作用)。 按本轮验收规则「任一核心项 FAIL 或 SKIP:总结必须写 FAIL」,不得标记为通过。
已完成且通过的项见 §2;未执行项见 §9。本文不使用「基本通过」。
不存在生产缺陷判定(未发现需定向整改的产品缺陷);已发现的失败项全部定位为 本轮验收脚本自身的环境缺陷或瞬时基础设施故障,均已在 §5、§6 中给出根因与最小复现。
1. 分支、HEAD、upstream
| 项 | 期望 | 实测 | 结果 |
|---|---|---|---|
| 分支 | refactor/r01-structure-performance |
refactor/r01-structure-performance |
✔ |
| HEAD | 2319f97203a2cafcf8012869fda8c30599ffb305 |
2319f97203a2fcf8012869fda8c30599ffb305 |
✔ |
| upstream | 同一提交 | 2319f972… |
✔ |
| ahead/behind | 0 0 |
0 0 |
✔ |
| 暂存区 | 空 | 空 | ✔ |
git diff --check |
exit 0 | exit 0 | ✔ |
git log --merges origin/master..HEAD |
空 | 空 | ✔ |
1.1 两处 M 假阳性的三方 blob 验证
git status --short --untracked-files=no 曾显示这两个文件为 M。按指令做三方验证:
| 文件 | git hash-object -- |
HEAD:<file> |
git ls-files -s |
git diff --quiet |
|---|---|---|---|---|
…/candidatescore/CandidateOverviewStatusPolicy.java |
2ebcff6a95a2b34b9d3f435072252598f6722247 |
同 | 100644 2ebcff6a… |
exit 0 |
…/recommendation/JobPreferenceScoreCalculator.java |
7368f4a1e21b79cf303a6e70b1b8d1080677ca40 |
同 | 100644 7368f4a1… |
exit 0 |
⇒ blob 完全一致、git diff --quiet 为 0 → 内容零差异,确认为 Windows stat 缓存假阳性。
未对这两个文件执行 restore 或重新保存。终检时 git diff --name-only 已为空。
2. 已完成且通过的验收项
| 节 | 项目 | 结果 |
|---|---|---|
| §3 | 开工前核查(分支/HEAD/upstream/暂存区/diff --check/merge) |
全部符合预期 ✔ |
| §4 | 提交历史与阶段完整性(25 提交、390 文件、零删除) | ✔ |
| §4 | 迁移链(仅新增 V42–V45) | ✔ |
| §5.1 | S01 静态安全 | ✔ |
| §5.3 | S03 静态安全(Actuator/隐私/bootstrap/V43) | ✔ |
| §6 | Docker 基础设施 | ✔ |
| §7.1 | R01-E 门禁 | 42 / 0 / 0 / 0 ✔ |
| §7.2 | 领域关键测试 | 932 / 0 / 0 / 0 ✔ |
| §7.3 | mvn test |
2788 / 0 / 0 / 0 ✔ |
| §7.3 | mvn clean verify |
首次 FAIL / 重跑 2788 / 0 / 0 / 0 SUCCESS(见 §5) |
| §7.4 | OpenAPI 静态 | 176 paths / 214 operations,All checks passed! ✔ |
| §8 | 最新 JAR + 真实 MySQL/Redis/RabbitMQ 启动 | ✔ 全部通过(见 §4) |
| §15 | 敏感信息扫描(origin/master...HEAD) |
无真实秘密 ✔ |
| §16 | RabbitMQ 队列收尾 | 四队列归零 ✔ |
| §16 | 验收数据清理与足迹 | ✔ |
| §17 | 最终 Git 核查 | ✔ |
| §21/§22 | E1/E2 SQL 数与 EXPLAIN 结论 | 由 §7.1 的 42 项门禁覆盖 ✔ |
3. 关键提交清单与阶段映射
25 个提交(origin/master..HEAD,按时间正序),无删除文件;格式:hash | prod/test/doc/fly | 提交信息
| 阶段 | hash | prod | test | doc | fly | 提交信息 |
|---|---|---|---|---|---|---|
| S01 | b5084fd |
11 | 7 | 12 | 0 | fix: harden SMS authentication and job access control |
| 上传安全基线 | 96a920b |
7 | 7 | 2 | 0 | fix: harden resume upload safety |
| S02 | f399060 |
21 | 63 | 9 | 1 | feat: isolate and revoke authentication token sessions |
| S03 | cb3bc57 |
10 | 11 | 10 | 1 | feat: harden production bootstrap and resume privacy |
| Q01 | 77dc624 |
1 | 11 | 6 | 0 | fix: align job search queries across databases |
| R01-A | 0b5b849 |
12 | 9 | 4 | 0 | refactor: centralize request identity and request ID handling |
| R01-B | 15bd98b |
1 | 1 | 3 | 0 | refactor: centralize business error HTTP status mapping |
| R01-C | b68519e |
5 | 3 | 3 | 0 | refactor: centralize scoring status and preference calculation |
| R01-D1 | cf0c71e |
2 | 1 | 2 | 0 | perf: optimize candidate percentile calculation |
| R01-D2 | 8c40fd4 |
7 | 1 | 2 | 0 | perf: batch personal data export queries |
| R01-D3 | b56f792 |
2 | 1 | 2 | 0 | perf: batch conversation unread counts |
| R01-D4 设计 | b10c4a7 |
0 | 1 | 2 | 0 | docs: design scoring snapshot materialization |
| R01-D4 | 52c8026 |
19 | 27 | 7 | 2 | refactor: materialize scoring snapshots outside GET requests |
| R01-A2/A3 | 72b82a3 / d5c3712 |
8 / 8 | 3 / 3 | 2 / 3 | 0 | refactor: centralize second/third batch request context handling |
| R01-A4/A5/A6 | 0226977 / 534afe6 / c6db74a |
8 / 8 / 5 | 4 / 4 / 4 | 3 / 3 / 3 | 0 | refactor: centralize fourth/fifth batch + complete request context centralization |
| 夹具隔离 | 35baa84 / 84fe9f5 / 9954600 / b893ed6 |
0 | 3/2/4/6 | 1/2/0/2 | 0 | test: isolate … fixtures |
| R01-B2 | 70b9e10 |
24 | 30 | 3 | 0 | refactor: centralize role authorization and HTTP status mapping |
| R01-C2 | 7d4704e |
3 | 5 | 3 | 0 | refactor: centralize scoring and recommendation rules |
| R01-E | 2319f97 |
0 | 4 | 2 | 0 | test: verify batch query and unread count performance |
跨阶段混入检查:f399060/cb3bc57/52c8026 之外的提交无 src/main/resources 与 pom.xml 变更。
cb3bc57 修改了 mydreamjob-server/pom.xml,diff 内容为仅新增 io.micrometer:micrometer-registry-prometheus
(注释标明 S03-C 需要),属 S03 范围内,非意外混入。无删除文件。
4. 迁移 V42–V45 说明与 V1–V41 未修改证明
| 迁移 | 用途 | 引入提交 |
|---|---|---|
V42__auth_token_sessions.sql |
认证 Token 会话:session_id/token_jti/parent_token_id/consumed_at/revoked_at/revocation_reason/version + 索引与唯一键 |
f399060 |
V43__disable_legacy_seed_accounts.sql |
精确禁用遗留演示账号:以「原始手机号 + 原始邮箱 + 原始 bcrypt 哈希 + 原始主角色 + is_active=1」五元指纹匹配,仅置 is_active=0;不依赖固定 id、不删除任何行、幂等 |
cb3bc57 |
V44__candidate_scoring_refresh_tasks.sql |
评分快照刷新任务表 + 补偿扫描游标表 | 52c8026 |
V45__add_scoring_refresh_claim_token.sql |
刷新任务新增不可复用 claim_token VARCHAR(36) NULL,仅新增列 |
52c8026 |
未修改证明:
git diff --name-status origin/master...HEAD -- mydreamjob-server/src/main/resources/db/migration
→ 仅 4 条 A(V42/V43/V44/V45)
git diff --diff-filter=M --name-only origin/master...HEAD -- <migration dir> → 空(V1–V41 零修改)
git diff --diff-filter=D --name-only origin/master...HEAD -- <migration dir> → 空(零删除)
ls <migration dir> | wc -l → 45;V46 → 不存在
5. 自动化测试:总数、F/E/S 与首次失败/重跑记录
| # | 命令 | Tests | Failures | Errors | Skipped | BUILD |
|---|---|---|---|---|---|---|
| §7.1 | R01-E 门禁(4 类) | 42 | 0 | 0 | 0 | SUCCESS |
| §7.2 | 领域关键测试(17 个模式) | 932 | 0 | 0 | 0 | SUCCESS |
| §7.3a | mvn test |
2788 | 0 | 0 | 0 | SUCCESS |
| §7.3b | mvn clean verify(首次) |
2788 | 0 | 2 | 0 | FAILURE |
| §7.3b | mvn clean verify(重跑) |
2788 | 0 | 0 | 0 | SUCCESS |
总数 2788 ≥ 2788 达标;Failures=0、Skipped=0 两次一致。
5.1 首次失败(如实记录,不隐去)
[ERROR] Tests run: 2788, Failures: 0, Errors: 2, Skipped: 0
[ERROR] R01EIndependentCrossDatabaseVerificationTest.resetBothEngines:162->clearFixture:493 ? UndeclaredThrowable
[ERROR] R01EIndependentCrossDatabaseVerificationTest.mysqlExplainReportsAccessPathsPerTable:1268
->seedBackgroundMessages:1491 ? TransientDataAccessResource
PreparedStatementCallback; SQL [INSERT INTO messages (conversation_id, sender_user_id, content, status, created_at) VALUES (?,?,?,?,?)];
No operations allowed after statement closed.
Caused by: com.mysql.cj.exceptions.StatementIsClosedException: No operations allowed after statement closed.
at com.mysql.cj.jdbc.ClientPreparedStatement.clearBatch(ClientPreparedStatement.java:279)
...
HikariPool-39 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@… (No operations allowed after
connection closed.). Possibly consider using a shorter maxLifetime value.
失败性质判定(按指令三分类):
- 断言失败? 否 ——
Failures: 0,无任何断言不成立。 - 产品失败? 否 —— 失败点全部位于测试自身夹具写入(
seedBackgroundMessages的批量 INSERT) 与夹具清理(clearFixture),不涉及生产代码路径;同批次其余 2786 项全绿。 - 容器生命周期错误? 是 —— 为 MySQL 驱动侧
StatementIsClosedException与 Hikari 连接校验失败,典型的连接被服务端/连接池提前关闭。
原始证据已保存(首次失败日志与 surefire 报告):
target/surefire-reports/com.mydreamjob.common.r01e.R01EIndependentCrossDatabaseVerificationTest.txt
及对应 XML,以及完整 mvn clean verify 日志副本。
重跑前置检查:docker info Server 29.6.2、3 个容器 Running、
mydreamjob-mysql OOMKilled=false RestartCount=0 → Docker 健康,符合「可原命令重跑一次」条件。
重跑结果:2788 / 0 / 0 / 0,BUILD SUCCESS(8:50),同一错误未复现。 按指令「若同一错误重复出现,不得继续重跑掩盖」——本错误未重复,故不继续重跑。
5.2 由此产生的新观察项
见 OBS-NEW-01(§8)。
6. 验收脚本自身的环境缺陷(不是产品缺陷)
以下两项失败均由我本轮的验收脚本引起,已在报告与脚本中固化修正,未修改任何生产代码或既有测试。
6.1 MSYS 路径转换改写 RabbitMQ vhost(最小复现)
- 现象:§8 首启后管理端口
/actuator/health返回{"status":"DOWN"};业务端口健康正常。 - 最小复现:在 Git Bash 中以
RABBITMQ_VHOST=/(来自backend-infra/.env)启动 JAR, 应用日志出现ShutdownSignalException: connection error; protocol method: #method<connection.close>(reply-code=530, reply-text=NOT_ALLOWED - vhost D:/15642/Trae CN/Git/ not found …)。 - 根因:MSYS 把环境变量
RABBITMQ_VHOST=/转换成了 Windows 路径,应用连到不存在的 vhost。 容器实际只有 vhost/(rabbitmqctl list_vhosts→/)。 - 修正:以
MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST启动(只排除该变量, 不可用MSYS_NO_PATHCONV=1——那会同时禁用 JAR 路径转换,导致Unable to access jarfile /c/Users/...)。 - 修正后:
/actuator/health→{"status":"UP"}HTTP 200,RabbitMQ 错误计数归零。 - 结论:产品无缺陷;属验收环境缺陷。
6.2 管理健康 DOWN 不暴露组件明细
即使处于 DOWN,管理端口响应仅 {"status":"DOWN"},不含 db/redis/rabbit/diskSpace 明细 ——
这正是 management.endpoint.health.show-details: never 的预期行为 ✔。
7. §8 最新 JAR 与真实基础设施启动验收(通过)
| 项 | 值 |
|---|---|
| JAR | mydreamjob-server/target/mydreamjob-server-0.0.1-SNAPSHOT.jar(82 433 459 B,来自 mvn clean verify) |
| 应用 PID | 198(本轮唯一启动的 JAR;已按精确 PID 停止) |
| 业务端口 | 8080(0.0.0.0) |
| 管理端口 | 8081(仅 127.0.0.1,netstat 实测) |
| 启动耗时 | Started MyDreamjobApplication in 15.963 seconds |
| Flyway | 启动成功、schema 迁移状态正常(仅 Flyway 对 MySQL 8.4 的「升级建议」告警) |
| Hibernate | ddl-auto=validate 通过(否则启动失败) |
| SMS | app.sms.mock.enabled=false(生产模拟) |
| 管理员 bootstrap | ADMIN_BOOTSTRAP_ENABLED=false(默认关闭) |
| 密钥 | Access / Refresh / Token-Hash 三项本进程内随机生成、≥32 字符、两两不同;仅存在于进程环境,未写入仓库文件或本报告 |
双端口矩阵(实测):
| 端点 | 业务端口 /api/v1/actuator/* |
管理端口 /actuator/* |
|---|---|---|
health |
404 | 200({"status":"UP"}) |
metrics |
404 | 200 |
prometheus |
404 | 200 |
env |
404 | 404 |
beans |
404 | 404 |
heapdump |
404 | 404 |
threaddump |
404 | 404 |
configprops |
404 | 404 |
业务健康响应:{"code":200,"message":"ok","data":{"application":"mydreamjob-server","version":"0.0.1-SNAPSHOT","status":"UP"},"requestId":"…","timestamp":"…"} ——
不含 db/redis/rabbit/diskSpace/hikari/连接串/组件明细 ✔
8. 观察项
既有观察项(按要求保留,未擅自写成已解决)
| ID | 内容 | 是否阻断 | 影响范围 | 证据 | 推荐后续动作 |
|---|---|---|---|---|---|
| OBS-01 | 验证码仍可能在数据库明文保存(sms_verification_codes.code) |
否(既有设计) | 数据库静态数据泄露面 | V1__base_schema.sql 中 code VARCHAR(10) NOT NULL;本轮未改表结构 |
另立批次评估对 code 加盐摘要 + 短 TTL |
| OBS-02 | 无真实短信供应商;生产 mock=false 下必须安全失败 |
否(已安全失败) | 生产环境无法真实发码 | SmsSenderConfiguration 仅在字面量 true 时装配 MockSmsCodeSender,否则 UnavailableSmsCodeSender 抛 SERVICE_UNAVAILABLE,不伪造成功;本轮静态验证通过 |
接入真实网关后做运行时回归 |
| OBS-E01 | 本人发送且没有本人 message_reads 记录的消息,当前也计入本人未读数 |
否(待产品确认) | 会话未读数的业务语义 | R01-E 独立复验实测:会话内「本人 1 条 + 他人 2 条均未读」→ 未读 3(非 2);H2/MySQL 两侧一致 | 待产品负责人确认;如需变更属业务语义变更,另立批次 |
本轮新增观察项
| ID | 内容 | 是否阻断 | 影响范围 | 证据 | 推荐后续动作 |
|---|---|---|---|---|---|
| OBS-NEW-01 | R01EIndependentCrossDatabaseVerificationTest 在 mvn clean verify 首次运行出现 MySQL 连接生命周期瞬时错误(2 Errors,Failures=0),重跑 2788/0/0/0 通过,未复现 |
潜在阻断(CI 稳定性) | 仅影响该测试类的稳定性,不影响生产行为 | §5.1 原文堆栈:StatementIsClosedException @ seedBackgroundMessages 批量 INSERT;HikariPool-39 - Failed to validate connection … shorter maxLifetime |
下一轮定向整改:为该类的手工 MySQL 上下文设置更短的 maximum-pool-size/max-lifetime 并对批量写入加重试,或改为独立容器生命周期;本轮不改测试 |
| OBS-NEW-02 | ErrorCode 中 UNAUTHORIZED→400、SERVICE_UNAVAILABLE→400、COMPANY_NOT_FOUND→404 为既有契约事实 |
否(既有契约) | 影响岗位权限矩阵的期望状态码 | src/test/resources/r01b/error-code-http-status-baseline.tsv:UNAUTHORIZED 2001 400、SERVICE_UNAVAILABLE 9001 400、COMPANY_NOT_FOUND 5001 NOT_FOUND |
本轮未改;记录以说明「STUDENT 创建岗位」的既有语义为 COMPANY_NOT_FOUND,运行时矩阵未执行故不下结论 |
| OBS-NEW-03 | 9103 同码多常量事实仍被显式记录 |
否 | 无 | ErrorHttpStatusRegistry.java 注释「同一数字码对应多个常量是既有事实(9103)」;ErrorCode.REVIEW_ALREADY_DECIDED(9103, …)、DUPLICATE_FEEDBACK(9103, …) |
保持 |
9. 未执行的验收项(本轮 FAIL 的直接原因)
下列项目本轮未执行,因此不得判定为通过。每项给出续跑方式。
| 节 | 未执行项 | 续跑方式 |
|---|---|---|
| §7.4 运行时 | 运行时 OpenAPI(/v3/api-docs 计数) |
JAR 启动后 GET /api/v1/v3/api-docs 统计 paths/operations |
| §9.1 | Token 类型矩阵(Access/Refresh/无 type/错误密钥/错误 aud/iss/缺 iat/未来 iat/过期/非法 sid·jti·sub) | 脚本已就绪:backend-infra/final_acc_20260918_a_startup_token.py(§8+§9);需 JAR 运行 |
| §9.2 | 10 轮双客户端并发刷新、重放、活动 Refresh=0、无锁异常 | 同上脚本 section9_concurrency() |
| §9.3 | 单设备登出、重复登出幂等、注销撤销全部 session、inactive 不可登录 | 同上脚本 section9_logout() |
| §10 | 岗位权限矩阵(匿名读写、STUDENT/ADMIN/COMPANY/跨企业) | 待编写并执行 |
| §10 | 短信生产失败关闭运行时验证(已完成静态验证) | POST /auth/sms/send 期望安全失败 |
| §11.1 | 简历隐私矩阵(PRIVATE/APPLIED_COMPANIES/VERIFIED_COMPANIES/跨企业/拒绝前零文件读取/字节一致/no-store) |
待编写并执行 |
| §11.2 | 管理员初始化矩阵(默认关闭、非法值、并发双 JAR、事务、无 Token/短信/MQ) | 需隔离配置多次启动 JAR |
| §11.3 | V43 在真实 MySQL 的执行结果(指纹命中/单字段不命中/不依赖 ID/重复执行稳定/登录被拒) | 对 mydreamjob-mysql 执行并可回滚验证 |
| §12 | Q01 真实数据库查询矩阵(18 项) | 运行时 + EXPLAIN 分表报告 |
| §13.1 | 三个 GET 评分接口的 22 张表逐字段快照(零写) | 待编写并执行 |
| §13.2 | 写侧刷新任务(Offer 过期/Feedback/Resume 等) | 待编写并执行 |
| §13.3 | V45 claim token 100 轮并发验收 | 待编写并执行 |
| §14 | 22 张表逐字段副作用 + 改/插/删反证(运行时) | 独立测试已覆盖 25 张表快照与四类反证(§7.1 通过),但未在运行 JAR 上做 |
已完成的部分覆盖:§13.4(E1/E2 SQL 数、H2/MySQL 深比较、500/501 窗口、m 命中
idx_messages_conversation_created、mr 为 eq_ref+uk_message_reads、ranked 派生结果扫描、
temporary/filesort、不新增 V46)与 §14 的测试内副作用快照,已由 §7.1 的 42 项门禁覆盖并通过
(详见 docs/r01-e-independent-verification-20260918.md)。
10. §15 敏感信息扫描结果(逐条分类)
扫描范围:git diff origin/master...HEAD 全量(2 349 103 字符)。
| 模式 | 命中 | 分类 |
|---|---|---|
eyJ…(JWT 原文) |
0 | — |
Authorization: Bearer <token> |
0 | — |
JWT_*_SECRET=<值> |
0 | — |
MYSQL_PASSWORD=<值> |
2 | 均为 shell 变量引用($2、$T…),非真实口令 |
DB_PASSWORD=<值> |
1 | 文档正文中的模式列举,非真实口令 |
PRIVATE KEY |
0 | — |
AKIA…(AWS) |
0 | — |
password_hash='$2a$…'(bcrypt) |
0 | — |
| 11 位连续数字(手机号) | 0 | — |
claim_token= |
19 | 全部为 SQL 列引用(… AND claim_token=?、SET claim_token=NULL)与文档描述,非令牌值 |
真实口令比对:以 backend-infra/.env 中 6 个真实值与全部命中逐一比对 → 无一相等,
故无真实秘密泄漏 ✔。扫描器已用探针验证有效性(可命中注入的 Authorization: Bearer、
11 位手机号、DB_PASSWORD= 等模式)。
未扫描(本轮未执行,故不声明通过):最新 JAR 运行日志、数据库 Token/审计字段、
本轮 HTTP 响应、验收脚本与报告(脚本中不落密钥;final_acc_20260918_run.sh 只在进程内生成密钥并写入
一个已删除的临时文件 .final_acc_secrets.env)。
11. §6/§16 RabbitMQ 队列结果
| 队列 | 验收开始前 | 验收结束后 |
|---|---|---|
queue.sms.verification |
ready=0 unacked=0 total=0 | ready=0 unacked=0 total=0 |
queue.notification |
0 / 0 / 0 | 0 / 0 / 0 |
queue.audit |
0 / 0 / 0 | 0 / 0 / 0 |
queue.notification.dlq |
0 / 0 / 0 | 0 / 0 / 0 |
四队列归零 ✔,前后一致,无需 purge、无阻断。
基础设施状态(未重建、未改配置、未删 volume):
| 容器 | 镜像 | 端口 | 状态 |
|---|---|---|---|
mydreamjob-mysql |
mysql:8.4 | 127.0.0.1:3307→3306 |
Up (healthy),OOMKilled=false,RestartCount=0 |
mydreamjob-redis |
redis:7.4-alpine | 127.0.0.1:6379 |
Up (healthy) |
mydreamjob-rabbitmq |
rabbitmq:3.13-management-alpine | 127.0.0.1:5672、127.0.0.1:15672 |
Up (healthy) |
Docker Engine 29.6.2;volume:backend-infra_mysql_data、backend-infra_redis_data、backend-infra_rabbitmq_data(未删除)。
三个容器以 docker start <精确容器名> 启动;未执行 compose down、未重建、未换镜像、未重置数据库。
12. §16 验收数据清理与保留足迹
| 项 | 结果 |
|---|---|
本轮创建的夹具用户(1990000* 虚构号段) |
0 条残留(实测 SELECT COUNT(*) FROM users WHERE phone LIKE '1990000%' = 0) |
| 本轮 JAR | 已按精确 PID 198 停止;未停止任何未知 Java 进程 |
| 端口 | 8080/8081 均已空闲 |
| 临时密钥文件 | backend-infra/.final_acc_secrets.env 已删除 |
| 临时扫描文件 | backend-infra/_finalacc_scan.tmp 已删除 |
audit_logs |
未删除任何行(本轮未产生审计行;如后续续跑产生,须保留并记录引用对象) |
| Docker 容器 | 保持运行(按指令) |
| 历史未跟踪文件 | 完整保留(未跟踪总数 255,仅新增本轮 3 个脚本 + 本报告) |
| 本轮新增未跟踪文件 | backend-infra/final_acc_20260918_lib.py、final_acc_20260918_a_startup_token.py、final_acc_20260918_run.sh、docs/final-independent-acceptance-20260918.md |
13. §17 最终 Git 状态
| 项 | 结果 |
|---|---|
| 分支 | refactor/r01-structure-performance |
| HEAD | 2319f97203a2fcf8012869fda8c30599ffb305(未变) |
| upstream | 2319f972…(0 / 0) |
| 暂存区 | 空 |
| 已跟踪文件修改 | 无(git diff --name-only 空) |
git diff --check |
exit 0 |
| V1–V45 迁移修改 | 无 |
| V46 | 不存在 |
| master 合并 | 无(git log --merges origin/master..HEAD 空) |
| commit / push | 未执行 |
| 历史未跟踪文件 | 完整保留 |
14. 是否具备创建 PR 的条件
不具备(本轮 FAIL)。
理由:本轮未执行完第九~十四节的运行时应验收矩阵(令牌类型矩阵、刷新/重放/并发、 岗位权限矩阵、简历隐私矩阵、管理员初始化矩阵、V43 真实迁移、Q01 运行时查询矩阵、 GET 评分零写、写侧刷新、claim token 100 轮、运行时 22 表副作用)。按本轮验收规则, 任一核心项 SKIP 即判 FAIL,故不得标记为通过。
但需明确:本轮未发现任何生产缺陷。已完成部分(静态审查、提交/迁移完整性、 2788 项自动化测试、OpenAPI 176/214、最新 JAR + 真实 MySQL/Redis/RabbitMQ 启动与双端口隔离、 RabbitMQ 队列归零、敏感信息扫描、Git 无变化)全部通过。
15. 下一轮定向整改指令(不修改生产代码)
- OBS-NEW-01(优先):稳定
R01EIndependentCrossDatabaseVerificationTest的手工 MySQL 上下文—— 为该上下文单独配置hikari.maximum-pool-size与max-lifetime,并对seedBackgroundMessages的批量写入增加幂等重试;或改为独立容器生命周期与更小的背景数据集。目标:mvn clean verify连续 3 次2788/0/0/0。仅改测试,不改生产代码。 - 补齐 §9:直接运行已就绪的
backend-infra/final_acc_20260918_a_startup_token.py(先bash final_acc_20260918_run.sh start,必须以MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST启动)。 - 补齐 §10–§13:按 §9 表格逐项编写执行;§11.2 需要隔离配置下的双 JAR 并发启动; §13.3 需要 100 轮 claim token 并发。
- 补齐 §7.4 运行时 OpenAPI 与 §15 运行日志/数据库/响应扫描。
- 全部补齐后重新出报告;OBS-NEW-01 若再现同一错误即判 FAIL,不得继续重跑掩盖。
16. 明确声明
本轮未修改任何生产代码;未修改任何已有测试断言;未修改 V1–V45;未新增 V46;
未修改 ErrorCode、DTO、接口、OpenAPI 或 pom.xml;未修改评分、排名、推荐、邀约、Offer、申诉、
消息撤回或简历上传语义。
未执行 git reset / git restore / git checkout / git clean / git add -A / git add .。
未暂存、未 commit、未 push、未 merge、未 rebase、未 force push。
本报告 docs/final-independent-acceptance-20260918.md 保持未跟踪。
续跑记录(2026-09-18 第二轮,追加)
本节为续跑追加。上文 §0–§16 的初次 FAIL、首次 clean verify 的 2 个 Error、
MSYS vhost 环境缺陷、OBS-NEW-01 及全部原始失败证据原样保留、未删改。
17. 续跑前置核查
| 项 | 期望 | 实测 | 结果 |
|---|---|---|---|
| 分支 | refactor/r01-structure-performance |
同 | ✔ |
| HEAD | 2319f97203a2fcf8012869fda8c30599ffb305 |
同 | ✔ |
| upstream | 同步 | 2319f972…,0 0 |
✔ |
| 暂存区 | 空 | 空 | ✔ |
git diff --check |
exit 0 | exit 0 | ✔ |
| 已跟踪内容差异 | 无 | git diff --name-status 空 |
✔ |
| Docker | 三容器 healthy | MySQL 3307、Redis 6379、RabbitMQ 5672/15672 均 Up (healthy),Engine 29.6.2 |
✔ |
两处生产文件本轮未再显示 M(git diff --name-status 为空),未执行任何覆盖操作。
18. §3 OBS-NEW-01 稳定性:三轮 mvn clean verify
| 轮次 | 起止 | Tests | Failures | Errors | Skipped | BUILD | StatementIsClosedException |
Hikari 校验告警 | Docker OOM/重启 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 17:01:35 → 17:09:52 | 2788 | 0 | 0 | 0 | SUCCESS | 0 | 12 | false / 0 |
| 2 | 17:09:52 → 17:19:21 | 2788 | 0 | 0 | 0 | SUCCESS | 0 | 12 | false / 0 |
| 3 | 17:19:22 → 17:28:50 | 2788 | 0 | 0 | 0 | SUCCESS | 0 | 12 | false / 0 |
3/3 通过,硬性要求全部满足。
Testcontainers 真实执行证据(maven 控制台不携带容器日志,改用「类耗时 + Skipped=0」判定;
两类均标注 disabledWithoutDocker = true,若 Docker 不可用会被 skip):
| 轮次 | R01EMySqlQueryCountAndExplainTest |
R01EIndependentCrossDatabaseVerificationTest |
|---|---|---|
| 1 | 5 项 / 47.27 s | 16 项 / 109.6 s |
| 3 | 5 项 / 48.27 s | 16 项 / 134.9 s |
OBS-NEW-01 判定变更:
OBS-NEW-01 由「潜在阻断」降级为「非阻断 CI 稳定性观察项」。
根因收敛(新证据):三轮中每轮都出现 12 次 HikariPool - Failed to validate connection … No operations allowed after connection closed,但 StatementIsClosedException = 0 且全部通过。
说明该 Hikari 告警本身不足以致败;首次 FAIL 的致败因素是 StatementIsClosedException
(批量 INSERT 执行期语句被关闭)。三轮未能复现 → 判定为瞬时连接生命周期抖动。
按指令「任一轮再次出现相同错误则升级为测试基础设施阻断项」——本错误未再现,故不升级。
未修改任何测试以获取通过。
19. §4 最新 JAR 与真实基础设施启动
| 项 | 值 |
|---|---|
| JAR | mydreamjob-server-0.0.1-SNAPSHOT.jar(82 433 459 B,来自本轮第 3 次 clean verify) |
| PID | 1419(本轮唯一启动的 JAR,已按精确 PID 停止) |
| 业务端口 | 8080 |
| 管理端口 | 8081(netstat:127.0.0.1:8081 LISTENING,仅回环) |
| 启动 | Started MyDreamjobApplication in 16.538 seconds |
| 启动方式 | MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST(未设置 MSYS_NO_PATHCONV=1) |
app.sms.mock.enabled |
false |
ddl-auto |
validate(启动成功即通过) |
| 管理员 bootstrap | 默认关闭 |
| 密钥 | Access / Refresh / Token-Hash 三项进程内随机生成、≥32 字符、两两不同;未落盘(临时文件已删)、未打印、未写入本报告 |
Flyway(真实 MySQL 查询 flyway_schema_history):
| version | description | success |
|---|---|---|
| 45 | add scoring refresh claim token | 1 |
| 44 | candidate scoring refresh tasks | 1 |
| 43 | disable legacy seed accounts | 1 |
| 42 | auth token sessions | 1 |
SELECT COUNT(*) … WHERE success = 0 → 0(failed migration = 0 ✔)。
双端口矩阵(实测):
| 端点 | 业务 /api/v1/actuator/* |
管理 /actuator/* |
|---|---|---|
health |
404 | 200({"status":"UP"}) |
metrics |
404 | 200 |
prometheus |
404 | 200 |
env / beans / heapdump / threaddump / configprops |
404 | 404 |
业务健康响应仅含 application / version / status,不含 db、redis、rabbit、diskSpace、
hikari、连接串或任何组件明细 ✔
20. §5 运行时 OpenAPI 与静态文件逐集合比对
| 项 | 静态 docs/openapi-rc.json |
运行时 /api/v1/v3/api-docs |
|---|---|---|
| paths | 176 | 176 |
| operations | 214 | 214 |
| 路径集合是否完全一致 | — | True(only_static=[]、only_runtime=[]) |
| operation 集合是否完全一致 | — | True(only_static=[]、only_runtime=[]) |
RUNTIME_OPENAPI_RESULT = PASS(非仅比较数量:逐路径、逐 METHOD path 做集合相等判定)。
docs/openapi-rc.json 未修改。
21. §6 Token 与会话矩阵(真实 HTTP)
结果:36 / 36 全部 PASS。
| 分段 | 项数 | 结果 |
|---|---|---|
| §8 端口与健康(复跑) | 15 | 15/15 PASS |
| §9.1 Token 类型矩阵 | 13 | 13/13 PASS |
| §9.2 并发刷新与重放 | 3 | 3/3 PASS |
| §9.3 登出与会话注销 | 5 | 5/5 PASS |
21.1 类型矩阵(每格均为实测)
| Token | 普通接口 /auth/me |
刷新接口 /auth/refresh |
|---|---|---|
| Access | 200 | 401 |
| Refresh | 401 | 200 |
无 type |
401 | 401 |
| 错误密钥 | 401 | 401 |
错误 aud |
401 | 401 |
错误 iss |
401 | 401 |
缺 iat |
401 | 401 |
未来 iat |
401 | 401 |
| 已过期 | 401 | 401 |
非法 jti |
401 | 401 |
非法 sid |
401 | 401 |
非法 sub(非正数) |
401 | 401 |
拒绝响应泄漏扫描:Token 原文 / principal / claim / 异常类 / 堆栈 / SQL / 本地路径
→ 全部 0 命中 ✔
21.2 并发刷新与重放(10 轮 × 2 独立客户端)
| 断言 | 结果 |
|---|---|
| 每轮最多 1 个成功 | 10/10 轮 ✔ |
| 失败方只能 401,不得 500 | ✔ |
| 该 session 活动 Refresh 严格为 0 | act=0(每轮实测)✔ |
| 原 Access 失效 | old=401(每轮实测)✔ |
无锁异常 / 无 could not take locks |
✔ |
| 数据库不存 JWT 原文 | auth_tokens 中 token_hash LIKE 'eyJ%' → 0 ✔ |
21.3 登出与会话
| 断言 | 结果 |
|---|---|
| 单设备登出 | 200 ✔ |
| 只撤销当前 session,其他设备保持有效 | A=0 B=1,B 的 /auth/me → 200 ✔ |
| 重复登出幂等 | 已撤销的 Access → 401 ✔ |
| inactive 用户不能登录 | 拒绝(HTTP 400,<500,非 500)✔ |
21.4 本轮两项脚本缺陷(非产品缺陷,已修正)
| # | 现象 | 判定 | 修正 |
|---|---|---|---|
| S-1 | 夹具手机号 1990000(7 位)+5 位 = 12 位,登录被拒 phone must contain exactly 11 digits |
脚本缺陷;产品校验正确生效 | 前缀改为 199000(6 位)+5 位 = 11 位 |
| S-2 | 「业务健康不泄露组件明细」误报,act=db |
脚本缺陷:对整段 JSON 做子串匹配,随机 requestId 十六进制串恰好含 db |
改为仅解析 data 对象的键并做集合判定 |
| S-3 | 控制台 GBK 无法输出 ⊆ |
脚本缺陷 | 改为 ASCII 文案 |
22. §12 运行时敏感扫描
扫描范围:最新应用日志、本轮 HTTP 响应、auth_tokens、audit_logs、notifications、messages、验收脚本输出。
| 目标 | 模式 | 命中 | 分类 |
|---|---|---|---|
| 应用日志 | eyJ…(JWT 原文) |
0 | — |
| 应用日志 | Bearer <token> |
0 | — |
| 应用日志 | 11 位手机号 | 0 | — |
| 应用日志 | authorization |
0 | — |
| 应用日志 | password |
2 | Spring Boot 默认 Using generated security password=<uuid>;SecurityConfig 未启用 httpBasic/formLogin/UserDetailsService,该用户惰性。见 OBS-NEW-05 |
| HTTP 响应 | Token/principal/claim/异常类/堆栈/SQL/本地路径 | 0 | 见 §21.1 |
auth_tokens |
JWT 原文 | 0 | 仅存摘要 ✔ |
audit_logs |
eyJ… |
0 | — |
notifications / messages |
eyJ… |
0 / 0 | — |
audit_logs |
11 位数字 | 818 | 见 OBS-NEW-04(历史遗留) |
audit_logs |
claim% |
0 | — |
扫描器探针有效性:以注入式探针(Authorization: Bearer、11 位手机号、DB_PASSWORD=)验证后清理;
audit_logs 的 818 命中即为扫描器确实能命中的实证。
23. 本轮新增观察项
| ID | 内容 | 是否阻断 | 影响范围 | 证据 | 推荐后续动作 |
|---|---|---|---|---|---|
| OBS-NEW-04 | audit_logs 中 action='SMS_SEND' 的 818 行 details 含完整手机号(文本形态 Mock SMS sent to <11 位号码> for login,全部匹配真实移动号段 1[3-9]…) |
否(不阻断本分支) | 本地验收数据库历史数据;生产审计表的 PII 留存面 | 时间范围 2026-08-06 17:21 → 2026-09-17 14:46,全部早于本分支 S01 加固;本轮贡献 0 行(本轮 mock=false,不产生该链路);grep "Mock SMS sent to" src/main → 当前代码已无该写入路径(S01 已移除) |
① 属数据留存问题而非代码缺陷;② 生产侧需制定审计 PII 保留/脱敏策略;③ 如需清理须按数据治理流程单独执行,本轮未删除任何 audit_logs 行 |
| OBS-NEW-05 | 启动日志在 INFO 级输出 Using generated security password=<uuid> |
否 | 日志噪声 + 潜在的惰性默认账号 | 应用日志第 21/23 行;SecurityConfig 中 httpBasic/formLogin/UserDetailsService 均无命中,故无认证入口可到达该账号 |
排除 UserDetailsServiceAutoConfiguration(或在生产 profile 关闭)以消除该日志与默认账号 |
24. §13 收尾:清理、保留足迹、RabbitMQ 终态
| 项 | 结果 |
|---|---|
本轮夹具用户(199000% 号段) |
清理前 12 → 清理后 0 ✔ |
sms_verification_codes(1990%) |
0 ✔ |
audit_logs |
未删除任何行;验收开始前 2868 行 → 结束后 2868 行(保留足迹,数量不变)✔ |
| 应用 PID | 精确停止 1419;未停止任何未知 Java 进程 |
8080 / 8081 |
均已空闲 ✔ |
| 临时密钥文件 | backend-infra/.final_acc_secrets.env 已删除 |
| Docker 容器 | 保持运行(mydreamjob-mysql/redis/rabbitmq),未重建、未删 volume |
| 历史未跟踪文件 | 完整保留 |
| 本轮新增未跟踪文件 | 4 个验收脚本 + 3 个 JSON/日志产物 + 本报告 |
RabbitMQ 四队列终态:
| 队列 | ready | unacked |
|---|---|---|
queue.sms.verification |
0 | 0 |
queue.notification |
0 | 0 |
queue.audit |
0 | 0 |
queue.notification.dlq |
0 | 0 |
四队列归零 ✔,与验收开始前一致;未执行 purge。
25. 续跑后的逐项状态(PASS / FAIL / SKIP)
| 节 | 项目 | 状态 |
|---|---|---|
| §1 | 分支/HEAD/upstream/暂存区/两处 M 三方核验 |
PASS |
| §2 | 提交历史与阶段完整性、迁移 V42–V45、V1–V41 未修改 | PASS |
| §2 | S01/S03 静态安全 | PASS |
| §3 | 三轮 mvn clean verify |
PASS(3/3) |
| §4 | 最新 JAR + 真实 MySQL/Redis/RabbitMQ + 双端口 + Flyway V45 | PASS |
| §5 | 运行时 OpenAPI 逐集合比对 | PASS |
| §6 | Token 类型矩阵 | PASS(13/13) |
| §6 | 并发刷新与重放(10 轮) | PASS(3/3) |
| §6 | 登出与会话注销 | PASS(5/5) |
| §12 | 运行时敏感扫描(日志/响应/DB) | PASS,附 OBS-NEW-04/05 |
| §13 | 收尾清理与队列终态 | PASS |
| §7 | S01 岗位权限矩阵 + 短信失败关闭运行时 | SKIP(未执行) |
| §8 | S03 简历隐私矩阵 | SKIP(未执行) |
| §8 | 管理员初始化矩阵 | SKIP(未执行) |
| §8 | V43 真实迁移验证 | SKIP(未执行) |
| §9 | Q01 真实数据库查询矩阵 | SKIP(未执行) |
| §10 | 三个 GET 评分接口 22 表零写快照 | SKIP(未执行) |
| §10 | 写侧刷新任务链路 | SKIP(未执行) |
| §10 | claim token 100 轮并发 | SKIP(未执行) |
| §11 | 运行时 22 张表逐字段副作用 + 四类反证 | SKIP(未执行) |
26. 续跑后最终 Git 状态
| 项 | 结果 |
|---|---|
| HEAD | 2319f97203a2fcf8012869fda8c30599ffb305(未变) |
| upstream | 2319f972…,ahead/behind 0 / 0 |
| 暂存区 | 空 |
| 已跟踪文件修改 | 无(git diff --name-status 空) |
git diff --check |
exit 0 |
| V1–V45 迁移修改 | 无;无 V46 |
| master 合并 | 无 |
| commit / push / merge / rebase / force push | 均未执行 |
27. 续跑后最终判定
FAIL(仍未通过)
原因:§25 中标为 SKIP 的 9 项(§7 岗位权限与短信、§8 简历隐私/管理员初始化/V43、 §9 Q01 运行时、§10 评分零写/写侧刷新/claim token 100 轮、§11 运行时 22 表副作用)本轮未执行。 按验收规则「任一核心项 FAIL 或 SKIP:总结必须写 FAIL」,不得标记为通过,不得使用「基本通过」。
本轮新增的通过项(均已在 §17–§24 给出实测证据):
三轮 clean verify 2788/0/0/0 稳定、最新 JAR + 真实三中间件启动与双端口隔离、
运行时 OpenAPI 逐集合一致、Token 类型矩阵与并发重放登出共 36/36、运行时敏感扫描、
队列归零与夹具零残留。
未发现任何产品缺陷。本轮发现的 3 项脚本缺陷与 1 项历史数据观察(OBS-NEW-04)均已如实分类, 未现场修改任何生产代码或既有测试。
下一轮定向整改指令(承接 §15,不修改生产代码):
- 补齐 §7 岗位权限矩阵与短信失败关闭运行时(注意
COMPANY_NOT_FOUND→404 的既有契约,见 OBS-NEW-02)。 - 补齐 §8 简历隐私矩阵(含拒绝路径 storage read = 0)、管理员初始化隔离矩阵、V43 真实迁移验证。
- 补齐 §9 Q01 真实数据库查询矩阵与 EXPLAIN 分表报告。
- 补齐 §10 三 GET 零写 22 表快照、写侧刷新链路、claim token 100 轮。
- 补齐 §11 运行时 22 表逐字段副作用与改/插/删/单列四类反证。
- 就 OBS-NEW-04 制定审计 PII 留存策略(数据治理,非代码改动);就 OBS-NEW-05 排除默认安全用户自动配置。
续跑记录(2026-09-18 第三轮,追加)
上文全部内容(初次 FAIL、首次 clean verify 的 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、
第二轮三轮构建结果与全部原始证据)原样保留、未删改。
⚠ 本轮发生一次验收脚本误操作历史数据事件,已复原并如实记录(见 §32)。
28. §7 S01 岗位权限矩阵(真实 HTTP):20/20 PASS
| 用例 | 期望 | 实测 |
|---|---|---|
匿名 GET /jobs(列表保持公开) |
200 | 200 ✔ |
匿名 GET /jobs/public/{id}(公开详情) |
200 | 200 ✔ |
匿名 GET /jobs/{id}(企业端详情) |
401 | 401 ✔ |
匿名 POST /jobs |
401 | 401 ✔ |
匿名 PUT /jobs/{id} |
401 | 401 ✔ |
匿名 PATCH /jobs/{id}/status(发布) |
401 | 401 ✔ |
匿名 DELETE /jobs/{id} |
401 | 401 ✔ |
| COMPANY 创建岗位 | 200/201 | 成功 ✔ |
| COMPANY 发布岗位 | 200 | 200 ✔ |
| STUDENT 创建岗位 | 拒绝且非 500 | 403 / code=2002 ✔ |
| ADMIN 创建岗位 | 拒绝且非 500 | 403 / code=2002 ✔ |
| 其他企业修改他人岗位 | 非 200 且非 500 | 400 ✔ |
| 失败请求不写入岗位表 | jobCount 不变 | 27 → 27 ✔ |
异常主体 anonymousUser |
401 | 401 ✔ |
| 异常主体 非数字 | 401 | 401 ✔ |
| 异常主体 20 位 Long 溢出 | 401 | 401 ✔ |
异常主体 零值 sub |
401 | 401 ✔ |
生产 mock=false 短信安全失败 |
非 200 且非 5xx | 400 ✔ |
| 不伪造发送成功 | 响应无成功文案 | 无 ✔ |
| 失败响应不含手机号/验证码/Token | 无泄漏 | 无 ✔ |
说明:STUDENT/ADMIN 创建岗位返回 403 / code=2002(FORBIDDEN),与指令期望一致; 该项在本轮实测,修正了第二轮基于静态基线的推断(见 OBS-NEW-02)。
29. §9 Q01 岗位搜索真实数据库矩阵:13/15,发现 DEF-01
29.1 通过项(13)
空条件返回结果、标题关键词、描述关键词、大小写不敏感、特殊字符(Delta_100%)、
城市过滤(city=上海 → 1)、最低薪资下限、最高薪资上限、边界相等、无结果、
分页 size=1、非公开(DRAFT)岗位不进入公开搜索 —— 全部符合预期。
29.2 DEF-01:/jobs 与 /jobs/search 关键词语义分歧(产品行为缺陷)
最小复现(真实 HTTP,已保存):
GET /api/v1/jobs?keyword=%20AlphaEngineer%20 → items=0 (期望 1:应 trim 后命中)
GET /api/v1/jobs?keyword=%20%20%20 → items=0 (期望 20:空白应等同未传)
GET /api/v1/jobs/search?keyword=%20AlphaEngineer%20 → items=1 (正确)
GET /api/v1/jobs/search?keyword=%20%20%20 → items=20 (正确)
GET /api/v1/jobs → items=20
GET /api/v1/jobs?keyword=AlphaEngineer → items=1
根因(静态定位,未修改代码):
| 端点 | 调用链 | 关键词处理 | trim | 空白关键词 |
|---|---|---|---|---|
GET /jobs |
JobController:97 → JobService.listPublishedJobs → JobRepository:26(JPQL LIKE %:keyword%) |
仅判 :keyword IS NULL |
无 | 空白串非 NULL → 谓词生效 → 0 条 |
GET /jobs/search |
JobController:134 → JobService.searchJobs → JobSearchSpecification:53 |
"%" + keyword.trim() + "%" + isBlank() 判空 |
有 | 空白被 isBlank() 跳过 → 全部 |
判定:指令 §12 明确列出 trim 与 空白关键词 两个验收用例。/jobs 端点两条均不满足
(trim 未生效、空白关键词返回 0 而非全部),且与同一产品的另一公开端点 /jobs/search 行为不一致。
按「产品缺陷直接 FAIL,不现场修复」→ 判定为 FAIL 项,未修改任何生产代码。
定向整改指令(下一轮,不属本轮范围):
统一 GET /jobs 与 GET /jobs/search 的关键词语义 —— 使 listPublishedJobs 路径同样对
keyword 执行 trim + isBlank 判空(或将 /jobs 直接委托给 JobSearchSpecification),
并补充「带前后空白的关键词」与「纯空白关键词」两条 HTTP 回归用例。注意
JobRepository:26 与 JobSearchSpecification 是两套并行实现,整改时需评估是否合并。
29.3 本轮 Q01 未覆盖项
排序(sort)、H2/MySQL 逐项对账(ID 与顺序一致)、EXPLAIN 分表报告 —— SKIP(未执行)。
30. §7/§9 本轮脚本缺陷(非产品缺陷,已修正)
| # | 现象 | 判定 | 修正 |
|---|---|---|---|
| S-4 | 匿名公开详情断言 401(期望 200) | 脚本缺陷:调用了企业端 /jobs/{id};公开端点是 /jobs/public/{id} |
改用正确端点,并新增一条「企业端详情匿名 401」断言 |
| S-5 | 中文城市与含空格关键词全部 0 条 | 脚本缺陷:查询参数未做 URL 编码 | 改用 urllib.parse.quote |
| S-6 | 清理时 fk_jobs_company 外键失败 |
脚本缺陷:未按外键顺序删除 | 改为 applications → jobs → companies → users |
31. 第三轮收尾
| 项 | 结果 |
|---|---|
本轮夹具残留(199000% 用户 / FINALACC_20260918_% 公司·岗位 / 短信码) |
全部 0 ✔ |
audit_logs |
未删除任何行;本轮开始前 2868 → 第三轮开始前 2888 → 结束后 2888(本轮 HTTP 产生的审计行保留,未删) |
| 应用 PID | 精确停止 1438 |
8080 / 8081 |
均已空闲 ✔ |
| 临时密钥文件 | .final_acc_secrets.env 已删除 |
| RabbitMQ 四队列 | 全部 ready=0 unacked=0 ✔,未 purge |
| Docker | 三容器保持运行,未重建、未删 volume |
32. ⚠ 验收脚本误操作历史数据事件(已复原,如实记录)
经过:第三轮首次运行 final_acc_20260918_b_jobs_search.py 时,夹具清理使用了过宽谓词
phone LIKE '1990%'(本意是匹配本轮 199000 + 5 位 夹具),导致两条既有历史用户
(users.id = 7938 / 7939,创建于 2026-09-14 09:48,非本轮产生)的 user_roles 行被删除。
发现:紧随其后的 DELETE FROM users WHERE phone LIKE '1990%' 因
fk_applications_student 外键失败而中止,据此定位到该前缀下存在非本轮用户。
影响面(已逐项核实):
| 对象 | 状态 |
|---|---|
users 7938 / 7939 |
未删除(删除语句因外键失败,未生效)✔ |
user_roles 7938 / 7939 |
被误删 ✗ → 已复原 |
applications(学生 7938/7939,共 2 条) |
未受影响 ✔ |
auth_tokens(7938/7939) |
原本为 0,未受影响 ✔ |
| 其它表 | 未触及 |
复原操作与依据:INSERT INTO user_roles (user_id, role) VALUES (7938,'STUDENT'),(7939,'STUDENT');
依据:users.role 两行均为 STUDENT,且库内 256 条 STUDENT 用户的 user_roles 全部为
('STUDENT')(1:1 形态)。复原后已核验两行存在且角色正确。
同类错误未再次发生:脚本已改为精确前缀 199000% 并补全外键删除顺序;第三轮后续运行未再触碰历史行。
教训与建议:验收夹具前缀应避免使用过短的数字前缀(1990 会与既有测试号段冲突),
建议改用更长且唯一的号段,并在任何 DELETE 前先 SELECT COUNT(*) 输出命中范围。
33. 第三轮后的逐项状态
| 节 | 项目 | 状态 |
|---|---|---|
| §7 S01 岗位权限矩阵(20 项) | 真实 HTTP 全量执行 | PASS |
| §7 S01 短信生产失败关闭 | 真实 HTTP | PASS |
| §9 Q01 搜索矩阵(15 项) | 真实 HTTP + MySQL | 13 PASS / 2 FAIL(DEF-01) |
| §9 Q01 排序 / H2-MySQL 对账 / EXPLAIN | — | SKIP |
| §8 S03 简历隐私矩阵 | — | SKIP |
| §8 S03 管理员初始化矩阵 | — | SKIP |
| §8 S03 V43 真实迁移验证 | — | SKIP |
| §10 R01 三 GET 零写 22 表快照 | — | SKIP |
| §10 R01 写侧刷新链路 | — | SKIP |
| §10 R01 claim token 100 轮 | — | SKIP |
| §11 运行时 22 表逐字段副作用 + 四类反证 | — | SKIP |
| §12 运行时敏感扫描(日志/响应/DB) | 已在第二轮执行 | PASS(附 OBS-NEW-04/05) |
34. 第三轮后最终 Git 状态
| 项 | 结果 |
|---|---|
| HEAD | 2319f97203a2cafcf8012869fda8c30599ffb305(未变) |
| upstream | 同提交,ahead/behind 0 / 0 |
| 暂存区 | 空 |
| 已跟踪文件修改 | 无 |
git diff --check |
exit 0 |
| V1–V45 迁移修改 | 无;无 V46 |
| master 合并 / commit / push / merge / rebase / force push | 均未执行 |
35. 第三轮后最终判定
FAIL(仍未通过)
新增 FAIL 项:DEF-01(/jobs 关键词 trim 与空白关键词行为不符合 Q01 验收判据,
与 /jobs/search 语义分歧)—— 属产品行为缺陷,已给出最小复现与定向整改指令,未修改生产代码。
仍为 SKIP 的项:§8 全部(简历隐私矩阵、管理员初始化矩阵、V43 真实迁移)、 §9 排序与 H2/MySQL 对账与 EXPLAIN、§10 全部(三 GET 零写、写侧刷新、claim token 100 轮)、 §11 运行时 22 表副作用与四类反证。
第三轮新增通过项:S01 岗位权限矩阵 20/20(真实 HTTP)、短信生产失败关闭、 Q01 搜索矩阵 13/15(真实 HTTP + MySQL)、夹具零残留与审计保留足迹、队列归零。
下一轮定向整改指令:
- DEF-01(优先,产品缺陷):统一
GET /jobs与GET /jobs/search的关键词语义 (trim+isBlank判空),并补充 HTTP 回归用例;评估JobRepository:26与JobSearchSpecification两套并行实现是否合并。 - 补齐 §8 简历隐私矩阵、管理员初始化隔离矩阵、V43 真实迁移验证。
- 补齐 §9 排序、H2/MySQL 逐项对账与 EXPLAIN 分表报告。
- 补齐 §10 三 GET 零写 22 表快照、写侧刷新链路、claim token 100 轮。
- 补齐 §11 运行时 22 表逐字段副作用与改/插/删/单列四类反证。
- 验收夹具改用更长唯一号段;任何 DELETE 前先输出命中范围(见 §32)。
续跑记录(2026-09-18 第四轮,追加)
上文全部内容(初次 FAIL、首次 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、第二/三轮结果、 §32 历史数据误操作事件)原样保留、未删改。
36. §8 S03 —— V43 真实 MySQL 验证:PASS
在 mydreamjob-mysql(真实 MySQL 8.4,V43 已随 Flyway 执行、success=1)上逐项验证:
| 断言 | 期望 | 实测 |
|---|---|---|
| 完整五要素指纹命中并禁用 | 三个演示账号 is_active=0 |
13800000001 ADMIN 0、13800000002 STUDENT 0、13800000003 COMPANY 0 ✔ |
users / user_roles 保留(不删除) |
3 / 3 | 3 / 3 ✔ |
| 重复执行稳定(幂等) | 重跑三段 UPDATE 受影响行数 = 0 | 0 / 0 / 0 ✔ |
| 任意单字段改变不命中 | 改 phone / email / password_hash / role 各自 0 行 | 0 / 0 / 0 / 0 ✔(证明五个合取项全部生效) |
| 不依赖固定 ID | V43 的 WHERE 无 id = 谓词 |
静态核对 V43 SQL:仅用 phone+email+password_hash+role+is_active 五元指纹 ✔ |
| 执行后演示账号未被误改 | 仍为禁用且字段不变 | 复核 1/2/3 三行 is_active=0 ✔ |
未覆盖:V43「原演示账号登录被拒绝」的运行时验证 —— SKIP(需构造 sms_verification_codes 并调用登录,
本轮未执行;其等价语义已由 S01 的「inactive 用户不能登录」HTTP 用例覆盖并通过,见 §21.3)。
37. §10 三个 GET 评分接口零写(24 张表逐字段快照):PASS(7/7)
| 断言 | 结果 |
|---|---|
GET /profile/me/candidate-scoring(综合评分) |
200 ✔ |
GET /profile/me/candidate-scoring/dimensions/EDUCATION(单维度) |
200 ✔ |
GET /company/candidates/{applicationId}/scoring(企业评分) |
200 ✔ |
| 三个 GET 前后 24 张表逐字段零变化 | changed=none ✔ |
GET 不新增 candidate_scoring_refresh_tasks |
0 -> 0 ✔ |
candidate_dimension_scores 行数与 version 不推进 |
rows 1->1、ver 0->0 ✔ |
| 无快照学生请求后仍为 0 行 | 0 ✔ |
快照覆盖的 24 张表:users、user_roles、student_profiles、companies、jobs、applications、
resume_versions、auth_tokens、audit_logs、notifications、messages、message_reads、conversations、
conversation_members、candidate_dimension_scores、candidate_scoring_refresh_tasks、
candidate_scoring_appeals、offers、interviews、candidate_feedback、talent_invitations、
scoring_risk_events、scoring_risk_actions、personal_data_export_tasks。
每张表显式列出稳定字段(含 version / updated_at 等),按主键排序,逐字段深比较 ——
未只比较 COUNT / SUM / CRC。
38. §11 副作用快照比较器的四类反证:PASS(4/4)
每条反证都要求变更真正命中 1 行且被比较器检出(避免「没改到任何行」造成的假通过):
| 反证 | 变更语句 | 受影响行数 | 是否检出 |
|---|---|---|---|
| 改一行 | UPDATE users SET nickname=… |
1 | True ✔ |
| 插一行 | INSERT INTO candidate_scoring_refresh_tasks … |
1 | True ✔ |
| 改单列 | UPDATE candidate_dimension_scores SET status='PENDING' … |
1 | True ✔ |
| 删一行 | DELETE FROM candidate_dimension_scores … |
1 | True ✔ |
⇒ 快照比较器确实具备检出能力,§37 的「零变化」不是「没在比」。
39. 第四轮脚本缺陷(非产品缺陷,已修正)
| # | 现象 | 判定 | 修正 |
|---|---|---|---|
| S-7 | 反证「插一行 / 删一行 / 改单列」未检出 | 脚本缺陷:变更语句未命中任何行(scoring_risk_events 无 student_user_id 列而静默失败;candidate_feedback 无对应行;dimension_scores 已被前序步骤删除) |
改用确定存在的行,并断言 ROW_COUNT()=1 |
| S-8 | 快照列名错误 | 脚本缺陷:scoring_risk_events 实际列为 subject_user_id,非 student_user_id |
按 information_schema 实测列名修正 |
| S-9 | 收尾清理报 Unknown column |
同 S-8 | 同上 |
40. 第四轮收尾
| 项 | 结果 |
|---|---|
| 夹具残留(用户 / 公司 / 岗位 / 短信码 / refresh task) | 全部 0 ✔ |
audit_logs |
未删除任何行;本轮 HTTP 产生的审计行保留,第三轮结束 2888 → 本轮结束 2900 |
历史用户 7938 / 7939 |
users.role 与 user_roles.role 均为 STUDENT,完好 ✔ |
| 应用 PID | 精确停止 1452 |
8080 / 8081 |
空闲 ✔ |
| 临时密钥文件 | 已删除 |
| RabbitMQ 四队列 | 全部 ready=0 unacked=0 ✔,未 purge |
| Docker | 三容器保持运行,未重建、未删 volume |
41. 第四轮后逐项状态
| 节 | 项目 | 状态 |
|---|---|---|
| §8 S03 V43 真实 MySQL 验证 | — | PASS |
| §10 三 GET 零写(24 表逐字段) | — | PASS(7/7) |
| §11 四类反证 | — | PASS(4/4) |
| §8 S03 简历隐私矩阵 | — | SKIP |
| §8 S03 管理员初始化矩阵 | — | SKIP |
| §8 S03 V43 登录拒绝(运行时) | — | SKIP |
| §9 Q01 排序 / H2-MySQL 对账 / EXPLAIN | — | SKIP |
| §10 写侧刷新链路 | — | SKIP |
| §10 claim token 100 轮 | — | SKIP |
42. 第四轮后最终 Git 状态
| 项 | 结果 |
|---|---|
| HEAD | 2319f97203a2cafcf8012869fda8c30599ffb305(未变) |
| upstream | 同提交,0 / 0 |
| 暂存区 | 空 |
| 已跟踪文件修改 | 无 |
| V1–V45 修改 / V46 | 无 / 不存在 |
| master 合并 / commit / push / merge / rebase / force push | 均未执行 |
43. 全轮次累计判定
FAIL(仍未通过)
累计 FAIL 项:DEF-01(/jobs 关键词 trim 与空白关键词不符合 Q01 判据,与 /jobs/search 语义分歧;产品缺陷,已给最小复现与整改指令,未修改生产代码)。
累计 SKIP 项:§8 简历隐私矩阵、管理员初始化矩阵、V43 运行时登录拒绝;§9 排序 / H2-MySQL 逐项对账 / EXPLAIN 分表报告;§10 写侧刷新链路、claim token 100 轮。
累计 PASS 项:开工核查与两处 M 三方核验、提交与迁移完整性、S01/S03 静态审查、
三轮 clean verify 2788/0/0/0、最新 JAR + 真实三中间件 + 双端口 + Flyway V45、
运行时 OpenAPI 逐集合一致、Token/会话矩阵 36/36、S01 岗位权限矩阵 20/20、
S03 V43 真实 MySQL 验证、Q01 搜索矩阵 13/15、三 GET 零写 24 表快照 7/7、
四类反证 4/4、运行时敏感扫描、夹具零残留与审计保留足迹、队列归零。
最终 Git:HEAD 未变、upstream 0/0、暂存区空、无已跟踪修改、未提交、未推送、未合并。
续跑记录(2026-09-18 第五轮,追加)
上文全部内容(初次 FAIL、首次 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、第二/三/四轮结果、 §32 历史数据误操作事件)原样保留、未删改。
44. §8 S03 —— V43 运行时登录拒绝:PASS(4/4)
对 V43 禁用的两个 Flyway 演示账号,注入合法验证码后走真实登录链路:
| 断言 | 期望 | 实测 |
|---|---|---|
已禁用演示 ADMIN(13800000001)登录 |
非 200 且无 Token | HTTP 400,无 accessToken ✔ |
已禁用演示 STUDENT(13800000002)登录 |
非 200 且无 Token | HTTP 400,无 accessToken ✔ |
| 被拒登录未产生会话行 | auth_tokens = 0 |
0 ✔ |
| 演示账号行未被删除 | 2 行存在 | 2 ✔ |
⇒ V43 的目标语义(禁用后无法取得会话)在真实运行实例上成立;结合 §36 的真实 MySQL 指纹验证, V43 的静态 + 数据 + 运行时三层证据齐备。
45. §9 Q01 排序:部分 PASS,排序语义 SKIP
| 用例 | 结果 |
|---|---|
白名单校验:未知 sort 值一律 400 |
salary_desc / salary_asc / salaryDesc / salaryAsc → 400 ✔(正确的白名单拒绝) |
sort=latest |
200 ✔ |
sort=salary |
200 ✔(白名单内) |
sort 省略 |
200 ✔ |
sort=salary 的升降序与 NULL 排序语义 |
SKIP —— 未用夹具验证顺序 |
sort=relevance 的相关度语义 |
SKIP |
白名单事实(静态):JobService:201 ALLOWED_SORTS = Set.of("latest", "salary", "relevance");
JobService:216 非白名单值抛 BusinessException(BAD_REQUEST) → 400。
JobService:207 注释说明 sort=salary 用 CriteriaQuery 的 CASE 排序使 salaryMax=NULL 排在最后。
46. 第五轮脚本缺陷(非产品缺陷,已修正)
| # | 现象 | 判定 | 修正 |
|---|---|---|---|
| S-10 | 排序用例 4 项 400 | 脚本缺陷:使用了不存在的 sort 值(salary_desc/salaryAsc 等);产品白名单只有 latest/salary/relevance,返回 400 是正确行为 |
记录真实白名单;顺序语义因缺夹具未验证,标记 SKIP |
| S-11 | 排序段 TypeError: int() … NoneType |
脚本缺陷:只登录创建了 STUDENT 用户,未建公司夹具 | 补 SQL 公司夹具 |
47. 第五轮收尾
| 项 | 结果 |
|---|---|
| 夹具残留(用户 / 公司 / 岗位 / 短信码) | 全部 0 ✔ |
audit_logs |
未删除任何行;第四轮 2900 → 本轮 2904(本轮产生的审计行保留) |
| Flyway 演示账号 | 仍为 13800000001=0, 13800000002=0, 13800000003=0(禁用状态未被改动)✔ |
历史用户 7938 / 7939 的 user_roles |
2 行存在(§32 复原成果保持)✔ |
| 应用 PID | 精确停止 1791 |
8080 / 8081 |
空闲 ✔ |
| 临时密钥文件 | 已删除 |
| RabbitMQ 四队列 | 全部 ready=0 unacked=0 ✔,未 purge |
48. 全轮次最终状态汇总
| 节 | 项目 | 状态 |
|---|---|---|
| §1 | 分支/HEAD/upstream/暂存区/两处 M 三方核验 |
PASS |
| §2 | 提交与迁移完整性、V1–V41 未修改、S01/S03 静态审查 | PASS |
| §3 | 三轮 mvn clean verify |
PASS(3/3) |
| §4 | 最新 JAR + 真实 MySQL/Redis/RabbitMQ + 双端口 + Flyway V45 | PASS |
| §5 | 运行时 OpenAPI 逐集合一致 | PASS |
| §6 | Token/会话矩阵(类型 + 并发重放 + 登出) | PASS(36/36) |
| §7 | S01 岗位权限矩阵 + 短信失败关闭 | PASS(20/20) |
| §8 | S03 V43 真实 MySQL 指纹验证 | PASS |
| §8 | S03 V43 运行时登录拒绝 | PASS(4/4) |
| §9 | Q01 搜索矩阵 | 13 PASS / 2 FAIL(DEF-01) |
| §9 | Q01 排序白名单 | PASS |
| §9 | Q01 排序语义 / H2-MySQL 逐项对账 / EXPLAIN 分表 | SKIP |
| §10 | 三 GET 零写(24 表逐字段) | PASS(7/7) |
| §10 | 写侧刷新链路 | SKIP |
| §10 | claim token 100 轮 | SKIP |
| §11 | 四类反证 | PASS(4/4) |
| §12 | 运行时敏感扫描 | PASS(附 OBS-NEW-04/05) |
| §13 | 收尾清理、队列归零、审计保留足迹 | PASS |
| §14 | 简历隐私矩阵 | SKIP |
| §14 | 管理员初始化矩阵 | SKIP |
49. 最终判定(全轮次)
FAIL(仍未通过)
FAIL 项(1):DEF-01 —— GET /jobs 的关键词 trim 与空白关键词行为不符合 Q01 验收判据,
且与 GET /jobs/search 语义分歧(/jobs?keyword=%20%20%20 → 0 条;/jobs/search?keyword=%20%20%20 → 20 条)。
产品行为缺陷,已给出最小复现与定向整改指令,未修改任何生产代码。
SKIP 项(5):§14 简历隐私矩阵、§14 管理员初始化矩阵、§9 排序语义与 H2/MySQL 逐项对账与 EXPLAIN 分表、 §10 写侧刷新链路、§10 claim token 100 轮。
观察项(不阻断):OBS-01、OBS-02、OBS-E01、OBS-NEW-01(已降级)、OBS-NEW-02、OBS-NEW-03、 OBS-NEW-04(审计历史 PII)、OBS-NEW-05(默认安全口令日志)。
产品缺陷数:1(DEF-01)。未发现其它产品缺陷;本轮其余失败全部定位为验收脚本/环境缺陷(S-1…S-11)。
最终 Git 状态:
| 项 | 结果 |
|---|---|
| HEAD | 2319f97203a2cafcf8012869fda8c30599ffb305(未变) |
| upstream | 2319f972…,ahead/behind 0 / 0 |
| 暂存区 | 空 |
| 已跟踪文件修改 | 无 |
git diff --check |
exit 0 |
| V1–V45 修改 / V46 | 无 / 不存在 |
| master 合并 / commit / push / merge / rebase / force push | 均未执行 |
| 历史未跟踪文件 | 完整保留 |
| 本报告 | 保持未跟踪 |
结论:不具备创建 PR 的条件 —— 需先修复 DEF-01 并补齐上述 5 项 SKIP 验收。
续跑记录(2026-09-18 第六轮,追加)
上文全部内容(初次 FAIL、首次 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、第二~五轮结果、 §32 历史数据误操作事件)原样保留、未删改。
50. §10 V45 claim_token 真实 MySQL 100 轮所有权验证:PASS(7/7)
方法:忠实复现 CandidateScoringRefreshTaskExecutor 的五条 SQL(claim / recoverStale /
complete / pendingRollback / recordFailure),在真实 MySQL 上直接驱动 —— 未修改任何生产代码。
| 断言 | 期望 | 实测 |
|---|---|---|
同 processing_started_at 的两次领取 token 必须不同 |
100/100 | 100/100 ✔ |
A 的迟到 COMPLETED / PENDING 回退 / RETRY·FAILED 均更新 0 行 |
100/100 | 100/100 ✔ |
| 且 B 的 status / version / token 不变 | 100/100 | 100/100 ✔ |
| 伪造 / 空 / 旧 token 的更新均 0 行 | 100/100 | 100/100 ✔ |
离开 PROCESSING 后 token 被清空 |
100/100 | 100/100 ✔ |
无永久 PROCESSING |
100/100 | 100/100 ✔ |
recoverStale 后 token 清空 |
token=<NULL> |
<NULL> ✔ |
claim_token 不进入审计 / 通知 / 消息 / 快照 JSON |
全 0 | {audit_logs:0, notifications:0, messages:0, snapshot_history:0} ✔ |
关键设计:每轮刻意让两次领取写入完全相同的 processing_started_at(秒级 DATETIME,固定为
2026-09-18 12:00:00)—— 这正是 V45 修复前「新旧持有者取得相同所有权标识」的触发条件。
实测 100/100 轮生成不同 claim_token,且旧持有者的全部迟到写入均 0 行,证明
V45 的 claim fence 在真实 MySQL 上确实封堵了该竞态。
51. 第六轮脚本缺陷(非产品缺陷,已修正)
| # | 现象 | 判定 | 修正 |
|---|---|---|---|
| S-12 | 100 轮全部短路,计数器 0/100 | 脚本缺陷:recoverStale 的边界传入了与领取相同的 T,processing_started_at < T 恒假 → 回收 0 行 → 后续第二次领取不满足 status IN ('PENDING','RETRY') |
边界改为晚于 T 的值 |
| S-13 | KeyError: JWT_ACCESS_SECRET |
脚本缺陷:lib 在 import 时读取密钥,而本轮脚本不需要 JWT | 运行期注入占位值 |
52. 第六轮收尾
| 项 | 结果 |
|---|---|
| 夹具残留(用户 / refresh task / 公司 / 短信码) | 全部 0 ✔ |
audit_logs |
未删除任何行,维持 2904 |
历史用户 7938 / 7939 的 user_roles |
2 行存在 ✔ |
8080 / 8081 |
空闲(无监听)✔ |
| RabbitMQ 四队列 | 4/4 均为 0 0 ✔,未 purge |
| Docker | 三容器保持运行,未重建、未删 volume |
53. 全部轮次最终状态
| 节 | 项目 | 状态 |
|---|---|---|
§1 开工核查与两处 M 三方核验 |
— | PASS |
| §2 提交与迁移完整性、S01/S03 静态审查 | — | PASS |
§3 三轮 mvn clean verify |
— | PASS(3/3,2788/0/0/0) |
| §4 最新 JAR + 真实三中间件 + 双端口 + Flyway V45 | — | PASS |
| §5 运行时 OpenAPI 逐集合一致 | — | PASS(176/214) |
| §6 Token/会话矩阵 | — | PASS(36/36) |
| §7 S01 岗位权限矩阵 + 短信失败关闭 | — | PASS(20/20) |
| §8 S03 V43 真实 MySQL 指纹验证 | — | PASS |
| §8 S03 V43 运行时登录拒绝 | — | PASS(4/4) |
| §9 Q01 搜索矩阵 | — | 13 PASS / 2 FAIL(DEF-01) |
| §9 Q01 排序白名单 | — | PASS |
| §9 Q01 排序语义 / H2-MySQL 对账 / EXPLAIN 分表 | — | SKIP |
| §10 三 GET 零写(24 表逐字段) | — | PASS(7/7) |
| §10 V45 claim_token 100 轮 | — | PASS(7/7,各项 100/100) |
| §10 写侧刷新链路 | — | SKIP |
| §11 四类反证 | — | PASS(4/4) |
| §12 运行时敏感扫描 | — | PASS(附 OBS-NEW-04/05) |
| §13 收尾清理 / 队列归零 / 审计保留足迹 | — | PASS |
| §14/§8 简历隐私矩阵 | — | SKIP |
| §14/§8 管理员初始化矩阵 | — | SKIP |
54. 最终判定
FAIL(仍未通过)
FAIL 项(1):DEF-01 —— GET /jobs 的关键词 trim 与空白关键词行为不符合 Q01 验收判据,
且与 GET /jobs/search 语义分歧(/jobs?keyword=%20%20%20 → 0 条;/jobs/search?keyword=%20%20%20 → 20 条)。
产品行为缺陷,已给出最小复现与定向整改指令,未修改任何生产代码。
SKIP 项(4):简历隐私矩阵、管理员初始化矩阵、Q01 排序语义/H2-MySQL 对账/EXPLAIN 分表、 写侧刷新链路。
产品缺陷数:1(DEF-01)。其余全部失败均定位为验收脚本/环境缺陷(S-1…S-13),非产品缺陷。
最终 Git 状态:HEAD 2319f97203a2cafcf8012869fda8c30599ffb305(未变);
upstream 0 / 0;暂存区空;已跟踪零修改;git diff --check exit 0;
V1–V45 未修改、无 V46;master 未合并;未提交、未推送;历史未跟踪文件完整保留;
本报告保持未跟踪。
结论:不具备创建 PR 的条件 —— 需先修复 DEF-01(产品缺陷)并补齐上述 4 项 SKIP 验收。
续跑记录(2026-09-18 第七轮,追加)—— 环境阻断
上文全部内容原样保留、未删改。
55. 第七轮:Docker 守护进程不可用,剩余 SKIP 项被环境阻断
现象:
docker info → failed to connect to the docker API at
npipe:////./pipe/dockerDesktopLinuxEngine;
check if the path is correct and if the daemon is running
docker ps -a → 同一错误
判定:Docker Desktop 守护进程已停止(并非容器 Exited)。这与指令允许的
「docker start <精确容器名>」情形不同 —— 指令允许的是在守护进程正常时启动已停止的具名容器;
本轮的阻断发生在更下一层,启动 Docker Desktop 属主机级运维动作,不在本轮允许范围内,
故未尝试启动,也未对容器/volume 做任何操作。
连带影响:
| 项 | 影响 |
|---|---|
| 应用启动 | 我启动的最新 JAR(PID 542)因连不上 MySQL 而启动失败(Communications link failure),未监听 8080/8081 |
mysql() 夹具通道 |
依赖 docker exec mydreamjob-mysql,随之不可用 |
| 第四~六轮已完成的验证 | 不受影响(证据在 Docker 停止前已采集并写入本报告) |
第七轮收尾与足迹:
| 项 | 结果 |
|---|---|
| 残留 Java 进程 | 无(PID 542 未存活,未杀任何进程) |
8080 / 8081 |
空闲 ✔ |
| 临时文件 | .final_acc_20260918.pid、.final_acc_secrets.env 均已删除 |
| 数据库状态 | 维持第六轮结束时的已核实状态:夹具残留 0、audit_logs 2904、历史用户 7938/7939 的 user_roles 2 行 |
| Docker 容器 / volume | 未做任何操作(守护进程不可用) |
| Git | HEAD 2319f972… 未变、upstream 0/0、暂存区空、已跟踪零修改 |
56. 受阻的剩余 SKIP 项(需 Docker 恢复后继续)
| 项 | 前置条件 |
|---|---|
| §8 简历隐私矩阵 | Docker 守护进程运行 + 三个具名容器 Up + 夹具(学生/企业/公司/岗位/投递/简历) |
| §8 管理员初始化矩阵 | 同上 + 隔离配置下多次启动 JAR、双 JAR 并发 |
| §9 Q01 排序语义 / H2-MySQL 对账 / EXPLAIN 分表 | 同上(脚本 final_acc_20260918_f_q01sort.py 已就绪,夹具与断言均已实现) |
| §10 写侧刷新链路 | 同上 |
恢复方式(建议由负责人执行):启动 Docker Desktop,确认三个具名容器 Up (healthy) 后,
执行 bash backend-infra/final_acc_20260918_run.sh start(必须以
MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST 前缀运行)再续跑。
57. 第七轮后最终状态
判定不变:
FAIL(仍未通过)
- FAIL 项(1):DEF-01(产品缺陷,未修复,未修改生产代码)
- SKIP 项(4):简历隐私矩阵、管理员初始化矩阵、Q01 排序语义/H2-MySQL 对账/EXPLAIN 分表、 写侧刷新链路 —— 第七轮因 Docker 守护进程停止而受阻,非跳过
- 产品缺陷数:1
Git 最终状态:HEAD 2319f97203a2cafcf8012869fda8c30599ffb305(未变);
upstream 0 / 0;暂存区空;已跟踪零修改;V1–V45 未修改、无 V46;
master 未合并;未提交、未推送;历史未跟踪文件完整保留;本报告保持未跟踪。
续跑记录(2026-09-18 第八轮,追加)
上文全部内容原样保留、未删改。第七轮的环境阻断已解除。
58. 环境恢复与数据完好性核验
Docker Desktop 由负责人启动后:
| 项 | 结果 |
|---|---|
| Docker Engine | 29.6.2 |
mydreamjob-mysql |
Up (healthy),127.0.0.1:3307→3306 |
mydreamjob-redis |
Up (healthy),127.0.0.1:6379 |
mydreamjob-rabbitmq |
Up (healthy),127.0.0.1:5672 / 15672 |
| 恢复方式 | 仅 docker start <精确容器名>(三个容器原为 Exited);未重建、未改配置、未删 volume |
停机期间数据完好性核验(Docker 停止再启动后):
| 项 | 期望 | 实测 |
|---|---|---|
| 本轮夹具残留 | 0 | 0 ✔ |
audit_logs |
保持 2904 | 2904 ✔ |
历史用户 7938/7939 的 user_roles |
2 | 2 ✔ |
| Flyway failed migration | 0 | 0 ✔ |
| V43 演示账号 | 仍禁用 | 13800000001=0, 13800000002=0, 13800000003=0 ✔ |
⇒ Docker 停止/启动未造成数据丢失或状态漂移。
59. §9 Q01 排序语义:PASS(6/6)
| 断言 | 期望 | 实测 |
|---|---|---|
| 4 条夹具岗位均出现在公开搜索 | 4 | 4 ✔ |
sort=salary 返回 200 |
200 | 200 ✔ |
salaryMax=NULL 排在最后 |
NULLMAX 末位 | seq=[HIGH, MID, LOW, NULLMAX] ✔ |
| 非 NULL 部分降序 | desc | salaryMax=[40000, 20000, 8000] ✔ |
sort=latest 按创建时间倒序 |
desc | n=4 desc=True ✔ |
sort=relevance + 关键词命中 |
200 且含 MID |
http=200 n=1 ✔ |
⇒ JobService:207 注释所述「sort=salary 用 CriteriaQuery 的 CASE 排序使 salaryMax=NULL 排在最后」
在真实运行实例上得到验证。
§9 剩余 SKIP:H2/MySQL 逐项对账(ID 与顺序一致)、EXPLAIN 对基础表/派生表/filesort/temporary 的分表报告。
60. 第八轮收尾
| 项 | 结果 |
|---|---|
| 夹具残留(用户 / 公司 / 岗位 / 短信码) | 全部 0 ✔ |
audit_logs |
未删除任何行,2904 → 2906(本轮产生的审计行保留) |
历史用户 7938 / 7939 的 user_roles |
2 行 ✔ |
| 应用 PID | 精确停止 1056 |
8080 / 8081 |
空闲 ✔ |
| 临时密钥文件 | 已删除 |
| RabbitMQ 四队列 | 全部 ready=0 unacked=0 ✔,未 purge |
| Docker 三容器 | 保持运行,未重建、未删 volume |
61. 全部轮次最终状态
| 节 | 项目 | 状态 |
|---|---|---|
| §1–§7 | 开工核查 / 提交与迁移完整性 / S01·S03 静态 / 三轮 clean verify / JAR 启动与双端口 / 运行时 OpenAPI / Token 与会话矩阵 / S01 岗位权限与短信 |
PASS |
| §8 | S03 V43 真实 MySQL 指纹验证 + 运行时登录拒绝 | PASS |
| §9 | Q01 搜索矩阵 | 13 PASS / 2 FAIL(DEF-01) |
| §9 | Q01 排序白名单 + 排序语义 | PASS |
| §9 | Q01 H2/MySQL 逐项对账、EXPLAIN 分表报告 | SKIP |
| §10 | 三 GET 零写(24 表逐字段) | PASS |
| §10 | V45 claim_token 100 轮 | PASS |
| §10 | 写侧刷新链路 | SKIP |
| §11 | 四类反证 | PASS |
| §12 | 运行时敏感扫描 | PASS(附 OBS-NEW-04/05) |
| §13 | 收尾清理 / 队列归零 / 审计保留足迹 | PASS |
| §14/§8 | 简历隐私矩阵 | SKIP —— 端点与策略已定位(GET /company/candidates/{applicationId}/resume;CompanyResumeAccessPolicy;APPLIED_COMPANIES 白名单 SUBMITTED/VIEWED/INTERVIEW/HIRED),但完整矩阵需多主体夹具 + 隐私设置行,本轮会话预算不足 |
| §14/§8 | 管理员初始化矩阵 | SKIP |
62. 最终判定
FAIL(仍未通过)
FAIL 项(1):DEF-01 —— GET /jobs 的关键词 trim 与空白关键词不符合 Q01 验收判据,
且与 GET /jobs/search 语义分歧。产品行为缺陷,已给出最小复现与定向整改指令,未修改任何生产代码。
SKIP 项(3):简历隐私矩阵、管理员初始化矩阵、Q01 H2/MySQL 逐项对账与 EXPLAIN 分表报告, 外加 §10 写侧刷新链路。
产品缺陷数:1(DEF-01)。其余全部失败均定位为验收脚本/环境缺陷(S-1…S-13)或环境阻断(第七轮 Docker),非产品缺陷。
Git 最终状态:HEAD 2319f97203a2cafcf8012869fda8c30599ffb305(未变);upstream 0 / 0;
暂存区空;已跟踪零修改;git diff --check exit 0;V1–V45 未修改、无 V46;
master 未合并;未提交、未推送;历史未跟踪文件完整保留;本报告保持未跟踪。
结论:不具备创建 PR 的条件 —— 需先修复 DEF-01(产品缺陷,需授权选定方案 A 或 B) 并补齐上述 SKIP 验收。
续跑记录(2026-09-20 第九轮):DEF-01 定向整改与复验(待提交)
上文全部内容原样保留、未删改:DEF-01 的首次发现与 FAIL 判定(§29.2、§35、§43、§54、§62) 保持在原位,本节点仅追加整改与复验结果。
63. DEF-01 整改采用方案 A(已获授权)
生产修改(唯一文件):mydreamjob-server/src/main/java/com/mydreamjob/job/JobService.java
(git diff --stat:1 file changed, 7 insertions(+), 1 deletion(-))
String normalizedKeyword = keyword == null || keyword.isBlank() ? null : keyword.trim();
Page<Job> result = jobRepository.findPublishedJobs(normalizedKeyword, city, employmentType,
salaryMin, salaryMax, pageable);
- 未采用方案 B:
/jobs仍走原 Repository,未改走JobSearchSpecification。 JobRepository/JobSearchSpecification/JobController/ 排序 / 分页 / 薪资 / 城市 / 状态 全部零修改;V1–V45 未改、无 V46、pom.xml与openapi-rc.json未改。
64. 新增测试与修复前/后结果
新增 JobListKeywordNormalizationIntegrationTest(9 项,真实链路
HTTP → SecurityFilterChain → JobController → JobService → JobRepository)。
| 阶段 | 结果 |
|---|---|
修复前(变异验证:normalizedKeyword→keyword) |
9 run / 4 FAILED —— trimmedKeywordFindsSamePublishedJob、blankKeywordBehavesAsNoKeyword、normalizationDoesNotChangePagination、normalizationDoesNotChangeExistingSortContract |
| 修复后(新测试) | 9 / 0 / 0 / 0 |
修复后(岗位关联回归 *Job*Test,*JobSearch*Test) |
219 / 0 / 0 / 0 |
| 字节精确恢复 | sha256 前 16 位 b4fab0034b34d569 一致,无变异残留 |
65. 真实 MySQL 黑盒(最新 JAR)
前缀 DEF01_20260920_:11 / 11 通过 —— 普通关键词与两侧空白结果 ID/顺序/数量一致;
纯空白与空字符串等同无关键词;DRAFT 不出现;分页 metadata 不变;默认排序不变;
无 4xx/5xx;无 SQL/异常/路径泄漏;请求前后数据库逐字段零变化;
对照 /jobs/search 的 trim/blank 语义一致。
66. 全量验证
| 命令 | Tests | F | E | S | BUILD |
|---|---|---|---|---|---|
mvn test |
2797 | 0 | 0 | 0 | SUCCESS |
mvn clean verify |
2797 | 0 | 0 | 0 | SUCCESS |
python ..\docs\verify_openapi.py |
paths 176 / operations 214 | — | — | — | All checks passed! |
2797 = 2788 + 9(本轮新增)。
67. 本轮测试自身缺陷(非产品缺陷,已修正并记录)
T-1 MockMvc 二次编码(%20→%2520);T-2 断言了既有契约不保证的顺序(createdAt DESC 无 tiebreaker,
夹具改为显式互异 created_at);T-3 黑盒夹具手机号前缀不一致导致公司挂空;
T-4 把关键词过滤结果与完整结果做相等比较。详见 DEF-01 整改报告 §20。
68. 提交前状态
| 项 | 结果 |
|---|---|
| 生产代码改动 | 仅 JobService.java 1 个文件(+7 −1) |
| 其余生产模块 / Flyway / pom / openapi-rc.json | 零修改 |
| 夹具残留 | 0;历史数据(users.id 7938/7939 的 user_roles)完好 |
audit_logs |
未删除任何行 |
| RabbitMQ 四队列 | 0 / 0 |
| 应用 PID | 已精确停止 |
| SKIP 项 | 4 项(简历隐私矩阵、管理员初始化矩阵、Q01 H2/MySQL 对账与 EXPLAIN 分表、写侧刷新链路) |
本轮状态:DEF-01 整改与复验完成,报告处于待提交状态;最终总验收仍为 FAIL(4 项 SKIP 未完成)。
69. DEF-01 提交与推送收尾(2026-09-20)
本地提交:
0b2ca79b96ea585dbafc753a980f7f79f5cf15db fix: normalize public job search keywords
3 files changed, 546 insertions(+), 1 deletion(-)
A docs/def-01-job-keyword-normalization-remediation-20260920.md
M mydreamjob-server/src/main/java/com/mydreamjob/job/JobService.java
A mydreamjob-server/src/test/java/com/mydreamjob/job/JobListKeywordNormalizationIntegrationTest.java
提交范围核验:仅上述 3 个文件;不含其他生产代码、Flyway、POM、OpenAPI、backend-infra/**、
最终总验收报告或历史验收产物。
推送:被网络阻断(未完成)
git push origin refactor/r01-structure-performance
→ fatal: unable to access 'https://gitea.baskbuddy.com/baskbuddy/zhangshun.git/':
Recv failure: Connection was reset
- 连续两次同一错误 → 判定持续环境阻断,停止重试(不反复重试掩盖)。
- 连通性核验:
curl https://gitea.baskbuddy.com/→ HTTP 000,超时 20.005s,远端主机从本环境不可达。 - 未执行
--force;未amend;未修改 remote;未推送 master;未创建额外提交。
推送时 Git 状态:HEAD 0b2ca79b96ea585dbafc753a980f7f79f5cf15db;
upstream 2319f97203a2cafcf8012869fda8c30599ffb305;ahead/behind = 1 / 0(待推送);
暂存区空;已跟踪修改为空(两处 M 经三方 blob 核验仍为 Windows stat 缓存假阳性);git diff --check exit 0;无 merge。
待办:远端恢复后执行 git push origin refactor/r01-structure-performance 即达 0/0。
69.1 推送收尾复检(同一日稍后)
- DEF-01 报告的提交后增补(§22 推送失败记录)已转入本报告;
该文件已用精确字符串截断恢复为 HEAD 内容(未使用
git restore/git checkout/git reset), 核验git diff --quietexit 0、已跟踪修改为空。 - Gitea 连通性复检:
curl -I --connect-timeout 10 --max-time 20 https://gitea.baskbuddy.com/→ HTTP 000,exit 28(连接超时),远端仍不可达。 - 按指令「Gitea 仍不可达 → 只尝试一次、停止重复推送、报告网络阻断、不执行第二阶段」, 本轮未再次推送,未进入 S03 简历隐私矩阵。
当前状态:HEAD 0b2ca79b96ea585dbafc753a980f7f79f5cf15db;upstream 2319f972…;ahead/behind = 1 / 0(待推送)。
剩余 SKIP 仍为 4 项(简历隐私矩阵、管理员初始化矩阵、Q01 H2/MySQL 对账与 EXPLAIN 分表、写侧刷新链路)。
第十轮(2026-09-20):S03 企业简历隐私真实矩阵(PASS)
上文全部内容原样保留、未删改。
本轮在本地未推送的 HEAD
0b2ca79上执行只读验收;未修改任何生产代码或测试,未创建提交、未暂存、未推送。
70. 前置与制品
| 项 | 结果 |
|---|---|
| HEAD / upstream / ahead-behind | 0b2ca79b96ea585dbafc753a980f7f79f5cf15db / 2319f972… / 1 / 0 |
| 暂存区 / 已跟踪修改 | 均为空;git diff --check exit 0 |
| Docker | Engine 29.6.2;mydreamjob-mysql / -redis / -rabbitmq 均 Up (healthy) |
| JAR 对 HEAD 的证明 | 重跑 mvn clean verify → 2797 / 0 / 0 / 0 BUILD SUCCESS |
| 启动 | MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST(未设 MSYS_NO_PATHCONV=1);PID 238;/api/v1/health→200;管理 health→{"status":"UP"};8081 仅绑 127.0.0.1 |
| Gitea | 仍为网络阻断,本轮未探测、未推送 |
端点(源码确认,未猜测):GET /api/v1/company/candidates/{applicationId}/resume(ApplicationController:219)。
策略(源码确认):CompanyResumeAccessPolicy —— APPLIED_COMPANIES 白名单 {SUBMITTED,VIEWED,INTERVIEW,HIRED}
(大小写敏感、null 安全);VERIFIED_COMPANIES 与投递状态无关;无设置/null/PRIVATE → 拒绝;非本公司岗位 → 拒绝。
CompanyResumeService.load 先授权、后读文件。Content-Type 白名单 {pdf, msword, docx},否则回退 application/octet-stream。
71. 矩阵结果:19 / 19 PASS
| # | 用例 | 结果 |
|---|---|---|
| 1 | 无隐私设置:9 种状态 + 跨企业,全部 403 | PASS |
| 2 | PRIVATE:9 种状态 + 跨企业,全部 403 | PASS |
| 3 | APPLIED_COMPANIES:SUBMITTED/VIEWED/INTERVIEW/HIRED → 200;WITHDRAWN/REJECTED/空白/UNKNOWN/submitted → 403 |
PASS |
| 4 | APPLIED_COMPANIES 跨企业:A 看 B 岗位 403、B 看 A 岗位 403 | PASS |
| 5 | VERIFIED_COMPANIES:认证公司 A 对全部 9 种状态均 200(与状态无关,源码语义一致);跨企业 403 | PASS |
| 6 | VERIFIED_COMPANIES:未认证公司 B 跨企业 403 | PASS |
| 7 | VERIFIED_COMPANIES:自有岗位 verified=false → 403,切 verified=true → 200 |
PASS |
| 8 | 匿名 → 401 | PASS |
| 9 | STUDENT → 403 | PASS |
| 10 | 非法 Token → 401 | PASS |
| 11 | application 不存在 → 404(非 500) | PASS |
| 12 | 成功下载:响应字节与磁盘文件逐字节相同(47 B) | PASS |
| 13 | Cache-Control: no-store |
PASS |
| 14 | Content-Type 属白名单(application/pdf) |
PASS |
| 15 | Content-Disposition 文件名已净化(原始名 ../隐私 简历.pdf → 无 ../、无反斜杠、无绝对路径;RFC 5987 UTF-8 编码) |
PASS |
| 16 | 响应头不含 storage path / resumeUrl / studentUserId / 手机号 / Token | PASS |
| 17 | 纯下载窗口(200/403/跨企业/匿名)前后 17 张表逐字段零变化 | PASS |
| 18 | 反证:改单列(student_profiles.resume_original_name)被快照检出,随后精确恢复 |
PASS |
applications.status 为 NOT NULL → 运行时无法构造 null 状态;按指令未伪造数据库可达性,
该分支由既有策略单测 CompanyResumeAccessPolicyTest(23 项全绿)覆盖。
72. storage read 门禁证据类型(明确区分)
| 证据类型 | 内容 |
|---|---|
| 真实 HTTP 证据(本轮) | 全部拒绝路径返回 403/401 —— 若授权后置,会在读文件失败时暴露为 404/500;实测无此现象 |
| 源码证据(本轮) | CompanyResumeService.load 中 accessPolicy.authorizeForDownload(...) 先于 resumeStorageService.read(storagePath),拒绝路径必然提前抛出 |
| 计数存储器证据(既有测试,未单跑) | CompanyResumeDownloadIntegrationTest(17 项全绿,含计数存储器)与 CompanyResumeAccessPolicyTest(23 项全绿)在 HEAD 0b2ca79 的 clean verify 中通过 |
未把 H2 计数包装器冒充真实文件系统观测;本轮未新增读数计数探针(如实标注)。
73. 数据副作用与清理足迹
- 下载矩阵期间不产生业务写入(§71 第 17 项);
- 夹具残留:用户 / 公司 / 岗位 / 投递 / 隐私设置 / user_roles 全部 0;本轮简历文件已删除;
- 历史用户
7938 / 7939的users与user_roles各 2 行完好;V43 演示账号仍全部 inactive; audit_logs未删除任何行:2912 → 2930(本轮 +18,保留);- RabbitMQ 四队列矩阵前后均
ready=0 unacked=0;未 purge; - 应用按精确 PID 238 停止;8080/8081 空闲;无残留 Java;临时密钥文件已删除;Docker 三容器保持 healthy。
74. 本轮脚本缺陷(非产品缺陷,已修正并记录)
| # | 现象 | 判定 | 修正 |
|---|---|---|---|
| P-1 | Duplicate entry '81-8063' for key uk_applications_job_student |
脚本缺陷:MySQL 默认排序规则大小写不敏感,title='…_job_submitted' 命中了先插入的 …_job_SUBMITTED,取错岗位 |
岗位查询改为 ORDER BY id DESC LIMIT 1 |
| P-2 | phone must contain exactly 11 digits |
脚本缺陷:本地 ph() 与 seed_sms_codes/login 用的 lib.phone() 不是同一个号码(同类问题第二次出现) |
统一复用 lib.phone() |
| P-3 | 同上 | 脚本缺陷:序号取 6 位(300011)超出 %05d 宽度 → 12 位号码 |
改用 5 位序号(30001/30002/30011/30012) |
| P-4 | CROSS_B 期望 403 实得 200 |
脚本缺陷:该投递位于公司 B 自有岗位,B 视角 200 本就正确;跨企业对照选错了样本 | 改为「B 看公司 A 的岗位投递」 |
| P-5 | 追加 appB 触发重复插入 |
脚本缺陷:setup() 已建该投递 |
复用其精确 ID |
| P-6 | 副作用快照 changed=['student_privacy_settings','companies'] |
脚本缺陷:before 取在整个矩阵之前,而矩阵有意切换可见性与 verified |
改为只在纯下载窗口前后取快照 |
75. 产品缺陷
本轮未发现产品缺陷。矩阵全部 19 行通过,APPLIED_COMPANIES 白名单失败关闭、
VERIFIED_COMPANIES 与状态无关、跨企业拒绝、授权先于读文件、响应头安全等语义均与源码契约一致。
76. 最终 Git 状态
| 项 | 结果 |
|---|---|
| HEAD | 0b2ca79b96ea585dbafc753a980f7f79f5cf15db(未变) |
| upstream | 2319f97203a2cafcf8012869fda8c30599ffb305 |
| ahead / behind | 1 / 0(待推送,Gitea 网络阻断) |
| 暂存区 | 空 |
| 已跟踪修改 | 无 |
git diff --check |
exit 0 |
| master 合并 / 新提交 | 无 / 无 |
| 历史未跟踪文件 | 完整保留 |
77. 判定更新
- S03 企业简历隐私矩阵:SKIP → PASS
- 剩余 SKIP 由 4 项降为 3 项:① 管理员初始化矩阵;② Q01 H2/MySQL 对账与 EXPLAIN 分表;③ 写侧刷新链路
- 最终判定仍为 FAIL(未完成) —— DEF-01 提交尚未推送(1/0),且仍有 3 项 SKIP
第十一轮(2026-09-20):S03 简历隐私缺口增量复验(PASS 12/12)
上文全部内容原样保留、未删改。本轮仍在本地未推送的 HEAD 0b2ca79 上只读验收;
未修改生产代码或既有测试,未创建提交、未暂存、未推送;未探测 Gitea。
78. 前置
HEAD 0b2ca79b96ea585dbafc753a980f7f79f5cf15db / upstream 2319f972… / 1 / 0;
暂存区与已跟踪修改均为空;git diff --check exit 0;Docker 29.6.2,三容器 healthy;
前缀残留 0;历史 7938/7939 = 2;audit_logs 2930。
79. 缺口矩阵结果:12 / 12 PASS
| # | 用例 | 实测 |
|---|---|---|
| 1 | ADMIN Access Token → 403 + 业务码 2002,响应无路径/异常类/堆栈 | 403/2002 ✔ |
| 2 | Refresh Token → 401(未进入 Controller/Service) | 401 ✔ |
| 3 | 无简历记录 → 既有 404 + 业务码,非 500 | 404/code=6001 ✔ |
| 4 | 无简历:不泄漏存储路径/异常类 | clean ✔ |
| 5 | 无简历:不修改简历记录 | unchanged ✔ |
| 6 | 记录存在但磁盘文件缺失 → 既有安全失败 404,无 C:\//Users//NoSuchFile/FileNotFound/异常类/类名/存储路径 |
http=404 leaks=[] ✔ |
| 7 | 文件缺失:下载请求前后不修改简历记录 | changed=False ✔ |
| 8 | 非白名单 Content-Type(text/html)→ 回退 application/octet-stream |
✔ |
| 9 | 回退时字节仍逐字节一致且 Cache-Control: no-store |
len=25 equal=True ✔ |
| 10 | 候选人接口 /company/scoring/dashboard/candidate-rankings 递归字段扫描 |
clean ✔ |
| 11 | 候选人接口 /company/candidates 递归字段扫描 |
clean ✔ |
| 12 | 运行时 OpenAPI 发现候选人接口数 | 2(非猜测)✔ |
路径来源:候选人列表/详情路径由运行时 /api/v1/v3/api-docs 取得;
递归解析 JSON 后检查字段名(小写归一),检查键集合
{resumeurl, resume_url, storagepath, filestoragepath, studentuserid} —— 均未出现,
非大小写敏感的原始字符串搜索。
80. 文件读取门禁(证据分层,未混淆)
| 路径 | 本轮证据 |
|---|---|
| ADMIN | 真实 HTTP 403/2002 —— 在 userRoleGuard.requireCompany 层被拒,未进入 Service |
| Refresh Token | 真实 HTTP 401 —— 在安全过滤层被拒,未进入 Controller |
| 无简历 | 真实 HTTP 404/code=6001 —— 说明已通过授权后才进入简历存在性检查 |
| 读取次数 | 引用既有计数包装器测试 CompanyResumeDownloadIntegrationTest(17 项全绿);未将其写成真实文件系统读数 |
81. 副作用、清理足迹与敏感扫描
- 本轮夹具残留:用户/公司/岗位/投递/profile/短信码 全部 0;本轮文件已删除;
- 历史
7938/7939完好;V43 演示账号仍全部 inactive; audit_logs未删除任何行:2930 → 2952(本轮 +22,保留);- RabbitMQ 四队列矩阵前后 4/4 均
0 0; - 应用按精确 PID 1792 停止;8080/8081 空闲;临时密钥文件已删除;Docker 三容器保持 healthy;
- 响应体扫描:无
C:\、/Users/、NoSuchFile、FileNotFound、Exception、at com.、java.、存储路径 —— 全部 0 命中。
82. 本轮脚本缺陷(非产品缺陷,已修正并记录)
| # | 现象 | 判定 | 修正 |
|---|---|---|---|
| G-1 | sms code invalid(夹具 40012) |
脚本缺陷:seed_sms_codes 漏了 SB/CB,却仍 login 它们 |
补齐五个号码 |
| G-2 | 「文件缺失不修改简历记录」FAIL 但 actual 显示 unchanged |
脚本缺陷:rec_before 取在我自己改成缺失路径的 UPDATE 之前;且 actual 被硬编码为字面量(同类问题第二次出现:基线区间含有意变更) |
改为在下载请求前后取快照,并输出真实比较结果 |
83. 产品缺陷
本轮未发现产品缺陷。6 项缺口全部通过:主体边界(ADMIN/Refresh)、无简历、文件缺失、 Content-Type 回退、候选人接口脱敏、运行时路径发现。
84. 阶段二(管理员初始化矩阵):本轮未执行
阶段一 12/12 通过,按指令阶段二已具备执行条件;但因本会话上下文预算耗尽, 未启动阶段二(10 项配置装配矩阵、6 项冲突账号矩阵、双 JAR 真实并发、事务回滚证据、 副作用快照、敏感扫描)。如实标注为未执行,不计入 PASS,也不写成跳过。
阶段二续跑所需:Docker 三容器可用;多次隔离端口启动 JAR(enabled 取值矩阵);
ADMIN_BOOTSTRAP_* 环境变量注入;双进程并发启动;精确 ID 清理验收管理员。
85. 判定更新
- S03 简历隐私:核心矩阵 19/19 + 缺口增量 12/12 → 该项标记 PASS
- 剩余 SKIP:3 项(管理员初始化矩阵 · Q01 H2/MySQL 对账与 EXPLAIN 分表 · 写侧刷新链路)
- 最终判定仍为 FAIL(未完成) —— DEF-01 提交尚未推送(1/0),且仍有 3 项 SKIP
Git 终态:HEAD 0b2ca79b96ea585dbafc753a980f7f79f5cf15db(未变);
upstream 2319f97203a2cafcf8012869fda8c30599ffb305;1 / 0;暂存区空;
已跟踪修改空;git diff --check exit 0;无 merge;无新提交。
第十二轮(2026-09-20):S03 管理员初始化矩阵 —— 契约确认完成,矩阵执行 SKIP
上文全部内容原样保留、未删改。本轮只读验收,未改生产代码/测试/迁移/配置,未 add/commit/push, 未重建容器,未探测 Gitea。
86. 前置核查
| 项 | 期望 | 实测 |
|---|---|---|
| 分支 | refactor/r01-structure-performance |
✔ |
| HEAD | 0b2ca79b96ea585dbafc753a980f7f79f5cf15db |
✔ |
| upstream | 2319f972…,ahead/behind 1/0 |
✔ |
| 暂存区 / 已跟踪修改 | 空 / 空 | ✔ |
git diff --check |
exit 0 | ✔ |
| Docker | 29.6.2,mydreamjob-mysql / -redis / -rabbitmq 均 Up (healthy) |
✔ |
| 8080 / 8081 | 空闲 | ✔ |
87. 真实契约(以仓库源码为准,非按旧报告猜测)
1. 环境变量名称(application.yml)
app:
bootstrap:
admin:
enabled: ${ADMIN_BOOTSTRAP_ENABLED:false}
phone: ${ADMIN_BOOTSTRAP_PHONE:}
nickname:${ADMIN_BOOTSTRAP_NICKNAME:平台管理员}
2. enabled 的精确装配条件(两层)
- 第 1 层 装配:
AdminBootstrapRunner标注@ConditionalOnProperty(name="app.bootstrap.admin.enabled", havingValue="true"); - 第 2 层 运行期兜底字面量防护(
run()首行):if (!STRICTLY_ENABLED.equals(properties.getEnabled())) return; // STRICTLY_ENABLED = "true"⇒ 即使框架层因宽松匹配装配了 Bean,非精确
"true"也一律直接返回、不创建管理员。TRUE/True/yes/1/on的最终行为是不创建;拒绝发生在 Runner 字面量防护层。
3. 手机号格式要求:Pattern.compile("[0-9]{11}"),校验前先 trim();不匹配 →
IllegalStateException("admin bootstrap rejected: configured phone is invalid") → 启动失败。
null 或 "" 同样不匹配 → 启动失败。
4. 合法管理员的幂等判据(isValidBootstrapAdmin)——须同时满足:
role == ADMIN && active == true && verified == true && password_hash IS NULL
&& user_roles 含 'ADMIN'
&& user_roles 不含 'STUDENT' 且不含 'COMPANY'
5. 冲突账户的拒绝判据:手机号已存在但不满足上述幂等判据(任意角色 / inactive / 角色不一致 /
带密码 / 附加角色)→ IllegalStateException("admin bootstrap rejected: configured phone is already in use")
→ 启动失败。另有前置短路:已存在活动 ADMIN 时无副作用跳过(不创建第二个)。
6. users 与 user_roles 的事务边界:AdminBootstrapCreator 使用
PROPAGATION_REQUIRES_NEW 独立事务(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
以 JdbcTemplate 参数化语句写 users(password_hash 为 NULL)与
user_roles ('ADMIN');捕获唯一键冲突 → 返回 CreateResult.CONFLICT(只回滚本次创建)。
7. 是否创建密码:否 —— INSERT 中 password_hash 为 NULL,幂等判据要求 passwordAbsent。
8. 是否签发 Token / 短信 / 通知 / MQ:均否 —— Runner 不调用任何认证、短信、通知或 MQ 组件 (源码类注释明确「不签发 Token、不发送短信、不设置默认密码、不提供公网接口」)。
9. 日志允许/禁止字段:仅输出固定安全文案 ——
admin bootstrap skipped: an active administrator already exists /
admin bootstrap skipped: a matching administrator already exists /
admin bootstrap created an administrator; remove the bootstrap switch now;
不含手机号、用户对象、SQL、绑定值或环境变量值。
88. 既有测试证据(HEAD 0b2ca79 的 mvn clean verify)
| 测试类 | Tests | F | E | S |
|---|---|---|---|---|
AdminBootstrapRunnerTest |
12 | 0 | 0 | 0 |
AdminBootstrapConcurrencyLoggingTest |
7 | 0 | 0 | 0 |
LegacySeedAccountDisableMigrationTest |
7 | 0 | 0 | 0 |
AdminBootstrapIntegrationTest |
4 | 0 | 0 | 0 |
AdminBootstrapTransactionTest |
3 | 0 | 0 | 0 |
AdminBootstrapDisabledByDefaultTest |
2 | 0 | 0 | 0 |
| 合计 | 35 | 0 | 0 | 0 |
⇒ §九(事务回滚证据)所要求的既有失败注入测试集在 HEAD 上全绿;但本轮未重新单跑该命令。
89. 矩阵执行:SKIP(本轮未执行)
以下项因本会话上下文预算耗尽而未执行,如实标注为 SKIP,不计入 PASS:
| 节 | 未执行内容 |
|---|---|
| §三 | 重新构建并记录 JAR 绝对路径 / 大小 / SHA-256(现有 JAR 来自 HEAD 的 clean verify,未重新构建) |
| §四 | 验收基线(活动 ADMIN 数、V43 状态、行级快照、四队列) |
| §五 | 11 项配置装配矩阵(未设置 / false / TRUE / True / yes / 1 / on / 缺手机号 / 空白 / 非法 / 合法) |
| §六 | 首次创建断言 + 重复启动幂等 |
| §七 | 6 类冲突账号矩阵 |
| §八 | 真实双 JAR 并发 |
| §九 | mvn -Dtest="AdminBootstrap*...,LegacySeedAccountDisableMigrationTest" test 未单跑(HEAD 全量已覆盖,见 §88) |
| §十 | 管理员矩阵前后的行级副作用快照与改单列反证、启动日志敏感扫描 |
| §十一 | 本轮无 JAR 启动,故无 PID/隔离端口/临时文件需收尾 |
未发现产品缺陷(本轮未执行到可判定产品行为的步骤)。
90. 判定
- S03 管理员初始化矩阵:仍为 SKIP(契约已确认,矩阵未执行)
- 剩余 SKIP:3 项(管理员初始化矩阵 · Q01 H2/MySQL 对账与 EXPLAIN 分表 · 写侧刷新链路)
- 最终判定仍为 FAIL(未完成) —— DEF-01 提交未推送(1/0)且尚有 3 项 SKIP
Git 终态:HEAD 0b2ca79b96ea585dbafc753a980f7f79f5cf15db 未变;
upstream 2319f97203a2cafcf8012869fda8c30599ffb305;1 / 0;暂存区空;
已跟踪修改空;git diff --check exit 0;V1–V45 无变化;无新提交、无合并、无推送。
90.1 续跑尝试(同轮次):基线已核,矩阵仍未执行
按续跑指令只运行了一次快速开工核查:
| 项 | 实测 |
|---|---|
| 分支 | refactor/r01-structure-performance |
| HEAD | 0b2ca79b96ea585dbafc753a980f7f79f5cf15db |
| ahead / behind | 1 / 0 |
| 暂存区 / 已跟踪修改 | 空 / 空;git diff --check exit 0 |
| Docker | 三容器 Up (healthy) |
| 8080 / 8081 | 空闲 |
§三–§十一 仍未执行:本会话上下文预算已耗尽,无法完成「构建 JAR → 11 项配置装配矩阵(每项独立端口启动/按 PID 停止)
→ 首次创建 → 重复启动幂等 → 6 类冲突账号矩阵 → 双 JAR 并发 → 事务测试单跑 → 行级快照与反证 → 敏感扫描 → 清理」。
未启动任何 JAR,故数据库未被本轮改动,无需清理;audit_logs 无本轮增量。
未发现产品缺陷(未执行到可判定产品行为的步骤)。本轮不写完成语。
状态不变:管理员初始化矩阵 SKIP;剩余 SKIP 3 项(管理员初始化矩阵 · Q01 H2/MySQL 对账与 EXPLAIN 分表 · 写侧刷新链路); 最终判定 FAIL(未完成);DEF-01 提交仍待推送(1/0)。
S03 管理员初始化 M1:配置装配矩阵 —— 未通过(9/11,含 3 项未解释异常)
脚本位于 %TEMP%\s03admin-m1\(未写入仓库);使用 mvn clean package -DskipTests 产出的
mydreamjob-server-0.0.1-SNAPSHOT.jar(82 433 528 B,SHA-256 前缀 3f67fb0710c01fe73aa588a8…)。
结果
| 编号 | 配置 | 预期 | 实测 | 判定 |
|---|---|---|---|---|
| M1-01 | enabled 未设置 | 启动成功、零创建 | started=True、零变化 |
PASS |
| M1-02 | false |
启动成功、零创建 | 同上 | PASS |
| M1-03 | TRUE |
启动成功、零创建 | 同上 | PASS |
| M1-04 | True |
启动成功、零创建 | 同上 | PASS |
| M1-05 | yes |
启动成功、零创建 | 同上 | PASS |
| M1-06 | 1 |
启动成功、零创建 | 同上 | PASS |
| M1-07 | on |
启动成功、零创建 | 同上 | PASS |
| M1-08 | true + phone 缺失 |
启动失败 | 应用启动成功(日志含 Started MyDreamjobApplication) |
FAIL / 异常 |
| M1-09 | true + phone 空白 |
启动失败 | 应用启动成功 | FAIL / 异常 |
| M1-10 | true + phone 非 11 位 |
启动失败 | started=False exit=1 |
PASS |
| M1-11 | true + 合法空闲手机号 |
创建 1 个 ADMIN | 启动成功但 未创建(row=[]),日志为 admin bootstrap skipped |
FAIL / 异常 |
enabled 为 TRUE/True/yes/1/on 时均未创建管理员,与 §87 契约「Runner 字面量兜底层拒绝」一致 ✔。
三项未解释异常(如实记录,不作猜测性结论)
- M1-08 / M1-09:
enabled=true且手机号缺失/空白时,按 §87 契约应在run()首部因PHONE_PATTERN不匹配抛出IllegalStateException(ERROR_INVALID_PHONE)而启动失败; 实测应用启动成功(health 200)。 - M1-11:
enabled=true+ 合法空闲手机号,应用启动成功但未创建管理员, 日志出现admin bootstrap skipped—— 按契约该文案对应「已有活动 ADMIN → 跳过」分支。 本轮脚本在enabled=true场景前按精确 id 临时停用全部 20 个活动 ADMIN 并在finally恢复, 该停用对 M1-11 未生效(原因未查明)。 - 三者互不一致:M1-10(同
enabled=true)确实因 Runner 抛错而启动失败, 说明 Runner 在 M1-10 场景确实被装配并执行,却在 M1-08/09 未致启动失败、在 M1-11 走了跳过分支。
因此本轮不能判定 M1 通过;上述差异需定向排查(建议下一步:分别核对
M1-08/09/11 三份日志中 @ConditionalOnProperty 是否装配 Bean、properties.getEnabled() 的实际取值、
以及 M1-11 执行瞬间 countByRoleAndActiveTrue(ADMIN) 的返回值)。
本轮脚本缺陷(不得算作产品缺陷)
| # | 现象 | 判定 | 处理 |
|---|---|---|---|
| M1-S1 | 首跑 11 项全部误判「未启动」 | 脚本缺陷:漏传 SERVER_PORT,应用实际监听 8080 而脚本轮询 19001+ |
补 env["SERVER_PORT"]=str(bport) 后重跑 |
| M1-S2 | M1-11 的 ok 为 True 但实际未创建 |
脚本缺陷:驱动器的 ok 表达式未接入 M1-11 的创建断言(只打印未判定),构成假通过 |
已在本报告中按实测结果改判为 FAIL |
清理与副作用(首跑与重跑后均已核验)
| 项 | 结果 |
|---|---|
| Java 进程 / 端口 | 0 个 java;8080/8081 与全部隔离端口空闲 |
| 活动 ADMIN 恢复 | ADMIN 总 21、活动 20(与本批开始前一致,逐行按精确 id 恢复) |
| V43 演示账号 | 仍全部 inactive(13800000001=0,13800000002=0,13800000003=0) |
| 历史用户 7938/7939 | 2 行完好 |
| 本批夹具残留 | 0(phone='19900500001' 无行) |
audit_logs |
未删除任何行,维持 2952(本批无增量) |
| RabbitMQ 四队列 | 前后均 ready=0 unacked=0 |
| 临时文件 | 11 份日志与 m1.json 保留在系统临时目录供排查;未写入仓库 |
判定
M1 配置装配矩阵:未通过(9/11) —— M1-08、M1-09、M1-11 三项存在未解释异常,需定向排查。 不写 M1 通过语;管理员初始化整体仍为 SKIP/PARTIAL,最终总验收仍为 FAIL(未完成)。
S03 管理员初始化 M1-D:M1-08/09/11 定向诊断
统计勘误
上一节的“9/11”是算术错误,按逐项结果应为 8/11:M1-01~07 与 M1-10 共 8 项 PASS, M1-08、M1-09、M1-11 共 3 项当时未通过。本勘误只修正汇总,不改写首次运行的原始结果。
两项验收驱动器根因
- M1-08/09 的假启动成功:旧驱动器在第一次看到业务健康接口 200 后立即终止 Java。
Spring Boot 在输出
Started MyDreamjobApplication、健康端点短暂可用后才执行失败的ApplicationRunner;因此旧脚本在 Runner 抛错前主动终止了进程。旧结果中的exit=1也是脚本主动终止后的退出码,不能证明稳定启动。 - M1-11 的错误 skip:旧脚本只停用
user_roles.role='ADMIN' AND users.is_active=1的 20 个用户;生产 Repository 实际谓词是users.role='ADMIN' AND users.is_active=1,实测有 21 行。差额是一个既有角色表不一致账号 (仅记录 id7918,未修改它的角色数据)。该账号未被旧脚本停用,生产代码因此正确输出admin bootstrap skipped: an active administrator already exists。
确定性重跑方法
- 清除父进程中的三个
ADMIN_BOOTSTRAP_*环境变量; - 仅使用 Spring Boot 命令行属性传入
app.bootstrap.admin.enabled=true; - M1-08 不传 phone,M1-09 显式传空 phone,M1-11 传唯一合法测试手机号;
- 健康端点首次 200 后继续观察 10 秒,只有进程持续存活才判稳定启动;
- M1-11 按生产查询谓词停用全部 21 个主角色 ADMIN,每次 MySQL CLI 写入自动提交并关闭连接;
- 启动前用两个新的独立连接复核活动主角色 ADMIN 均为 0;两个连接均指向
mydreamjob/ 同一 MySQL 容器,JAR 指向宿主机映射端口 3307 的同一数据库。
重跑结果
| 场景 | 健康端点是否短暂 200 | 稳定启动 | 自然退出码 | Runner 证据 | 结论 |
|---|---|---|---|---|---|
| M1-08:phone 缺失 | 是 | 否 | 1 | 固定错误 configured phone is invalid |
PASS |
| M1-09:phone 空白 | 是 | 否 | 1 | 固定错误 configured phone is invalid |
PASS |
| M1-11:合法空闲手机号 | 是 | 是 | 运行中(验收后按 PID 停止) | admin bootstrap created an administrator |
PASS |
M1-11 数据库实测:恰好创建 1 行 ADMIN,is_active=1、is_verified=1、
password_hash IS NULL;user_roles 恰好只有 ADMIN;auth_tokens=0。
脱敏、副作用与清理
- 三份日志中完整验收手机号、JWT/Bearer、密钥赋值、SQL 异常、用户实体输出均为 0 命中; 扫描器用合成探针逐项验证可检出。
- 日志存在每份 4 处通用启动/存储初始化的本机绝对路径(JAR 路径、工作目录及 3 个本地存储目录); 它们不包含本次管理员手机号或凭据,也非管理员初始化器新增,但作为既有日志最小化观察项保留, 未在本轮修改生产日志。
- RabbitMQ 四队列最终均
ready=0 / unacked=0;audit_logs仍为 2952,本轮未删除审计记录。 - 测试账号和角色已精确删除,残留 0;21 个主角色活动 ADMIN 已逐 id 恢复; 角色表含 ADMIN 的活动账号恢复为 20;历史用户 7938/7939 均存在;V43 三个演示账号活动数为 0。
- 三个 Java 进程均已退出,诊断业务/管理端口均空闲;脱敏诊断证据保留在系统临时目录。
M1 修订判定
原先三项异常均由验收驱动器造成,未发现管理员初始化生产缺陷。结合首次执行已经通过的 8 项, M1 配置装配矩阵修订为 11/11 PASS。
S03管理员初始化M1配置装配矩阵11/11通过,环境与管理员状态已完整恢复;下一步执行M2首次创建幂等与六类冲突账号矩阵。
S03 管理员初始化 M2:首次创建、幂等与六类冲突账号矩阵
执行方法
使用 M1 相同的最新 JAR、真实 MySQL/Redis/RabbitMQ,并通过 Spring Boot 命令行属性确定性启用初始化器。 每个场景使用独立业务/管理端口;健康端点首次 200 后继续观察 10 秒,避免把 ApplicationRunner 即将失败的 短暂健康状态误判为成功。场景开始前按生产谓词暂时停用 21 个既有活动主角色 ADMIN,场景结束后逐 id 恢复。
首次创建与重复启动
| 项 | 实测 |
|---|---|
| 首次启动 | 稳定启动,日志为固定 admin bootstrap created an administrator |
| 创建行 | 恰好 1 行;主角色 ADMIN;active=1;verified=1;password_hash=NULL |
| user_roles | 恰好只有 ADMIN |
| auth_tokens | 0 |
| 同配置重复启动 | 稳定启动,走活动 ADMIN 无副作用 skip |
| 重复启动前后 | users 与 user_roles 逐字段相同,未新增第二行 |
首次创建与重复启动幂等均 PASS。
六类冲突账号矩阵
| 场景 | 启动前活动主角色 ADMIN | 实测 | 账号前后 | 判定 |
|---|---|---|---|---|
| 活动 STUDENT | 0 | 固定 phone is already in use,退出码 1 |
不变 | PASS |
| 活动 COMPANY | 0 | 固定 phone is already in use,退出码 1 |
不变 | PASS |
| inactive ADMIN | 0 | 固定 phone is already in use,退出码 1 |
不变 | PASS |
| active ADMIN,但 user_roles 缺 ADMIN | 1(即该夹具) | 应用稳定启动,走 an active administrator already exists |
不变 | FAIL |
| active ADMIN,同时含 STUDENT 角色 | 1(即该夹具) | 应用稳定启动,走同一 skip | 不变 | FAIL |
| active ADMIN,password_hash 非空 | 1(即该夹具) | 应用稳定启动,走同一 skip | 不变 | FAIL |
产品缺陷:严格幂等判据被全局活动 ADMIN 短路
生产顺序当前为:手机号格式校验 → countByRoleAndActiveTrue(ADMIN) > 0 即返回 → 查询配置手机号并执行严格幂等校验。
后三种夹具本身的 users.role=ADMIN AND is_active=1 使全局计数为 1,故初始化器在读取配置手机号状态之前返回,
isValidBootstrapAdmin() 永远不可达。这与“配置手机号若已存在,只有角色一致、active、verified、无密码、
仅含 ADMIN 角色才允许幂等成功”的既定契约冲突。
影响边界:未发生提权、复活、密码覆盖或数据写入,三类账号均逐字段不变;但应用会在配置的管理员账号 角色关系不完整、带越权附加角色或仍有密码时错误启动成功,可能留下不可用或不符合初始化安全契约的管理员状态。
建议的后续定向整改设计:手机号校验后优先查询配置手机号;存在时先执行严格幂等判据,不合法即失败; 仅当配置手机号不存在时,才以“已有其他活动 ADMIN”为无副作用 skip 条件。该方案需先补修复前失败测试, 本轮验收未修改生产代码。
脱敏、副作用与清理
- 8 份启动日志中完整验收手机号、JWT/Bearer、密钥赋值、
SqlExceptionHelper、DataIntegrityViolationException、ConstraintViolationException、SQLException、User(id=均为 0 命中; 合成探针证明扫描器可检出上述模式。 - 首次创建之外,各冲突账号请求前后逐字段不变;重复启动没有新增用户、角色或 Token。
- 最终
auth_tokens=170、notifications=156、messages=8、audit_logs=2952,均与 M2 基线相同。 - RabbitMQ 四队列最终均
ready=0 / unacked=0。 - 7 个 M2 手机号残留为 0;21 个活动主角色 ADMIN、20 个带 ADMIN 角色关系的活动账号均恢复到基线; 历史用户 7938/7939 完好,V43 三个演示账号活动数为 0。
- 全部 M2 Java 进程已按 PID 结束,隔离端口已释放;日志与脱敏 JSON 证据仅保留在系统临时目录。
M2 判定
M2 FAIL:首次创建与重复启动幂等通过,六类冲突中 3 类通过、3 类失败。 已复现生产逻辑缺陷,不进入 M3,不写管理员初始化整体验收通过语。
M2 阻断缺陷定向整改
采用 TDD:先在 AdminBootstrapRunnerTest 增加 3 条失败测试,分别锁定“活动 ADMIN 但缺 ADMIN 角色关系、
带额外 STUDENT 角色、password_hash 非空”。修复前实测 15 项中 3 项失败,均为“期望
IllegalStateException,但实际未抛出”。
生产修复仅调整 AdminBootstrapRunner 的判断顺序:手机号格式校验后,先按配置手机号查询;
若账号存在,则必须通过完整 isValidBootstrapAdmin() 判据,否则固定安全失败;仅当配置手机号不存在时,
才允许因其他活动 ADMIN 已存在而无副作用跳过。未修改创建事务、并发冲突处理、日志文案、配置、迁移或接口。
既有手机号冲突测试中不再需要全局 ADMIN 计数 stub;删除 5 处无效 stub,未修改任何业务断言。
验证结果:
- 管理员初始化定向 6 类测试共 38/38,Failures=0、Errors=0、Skipped=0;
- 最新 JAR + 真实 MySQL 对原失败三类逐项重跑:三项均短暂健康后以固定
configured phone is already in use退出,退出码 1,账号逐字段不变; - 三类重跑夹具残留 0,活动管理员恢复为 21,RabbitMQ 四队列 0/0;
mvn clean verify:2800/2800,Failures=0、Errors=0、Skipped=0,BUILD SUCCESS;- OpenAPI:176 paths / 214 operations,All checks passed。
M2 阻断缺陷整改后复验通过:首次创建、重复启动幂等及六类冲突账号矩阵全部满足契约。 下一步方可执行 M3 双 JAR 并发、事务回滚和统一敏感扫描。
S03 管理员初始化 M3:双 JAR 并发、事务回滚与统一脱敏复验
双 JAR 真实并发
使用最新 clean verify 产出的 JAR,在真实 MySQL、Redis、RabbitMQ 环境中先按精确 id 临时停用
全部 21 个活动主角色 ADMIN,并由两条独立数据库连接确认活动 ADMIN 数为 0。随后以线程屏障同时启动
两个实例:A 使用业务/管理端口 19801/19901,B 使用 19802/19902;两次进程创建调用的时间差约
0.215 ms。
两个实例均稳定启动。A 日志仅出现固定文案 admin bootstrap created an administrator,B 日志仅出现
admin bootstrap skipped: a matching administrator already exists;未走“已有其他活动管理员”的短路分支,
也未出现唯一约束、SQL、绑定参数或异常堆栈泄漏。
并发终态:
- 配置手机号对应
users恰好 1 行,主角色 ADMIN、is_active=1、is_verified=1、password_hash IS NULL; user_roles中 ADMIN 恰好 1 行,STUDENT/COMPANY 均为 0;- 该用户
auth_tokens为 0; - 两个实例均未创建默认密码、未签发 Token、未发送短信、通知或 RabbitMQ 消息。
事务一致性与失败隔离
同一当前构建下,AdminBootstrapTransactionTest 3/3 通过:成功创建时 users 与
user_roles 同事务提交;角色写入失败时两表同时回滚;并发/重复创建后只保留一个合法管理员。
管理员初始化六类定向测试合计仍为 38/38,Failures=0、Errors=0、Skipped=0。
统一敏感扫描与副作用
合并扫描两个实例的完整启动日志,验收手机号、JWT/Bearer、密钥值、SQL 绑定参数、
SqlExceptionHelper、DataIntegrityViolationException、ConstraintViolationException、
SQLException、User(id= 均为 0 命中;合成探针对上述模式逐项命中,证明扫描器非空转。
两份日志各有一条 Spring Boot 自动配置产生的 Using generated security password 提示,沿用既有
OBS-NEW-05:当前安全配置未启用 httpBasic/formLogin/UserDetailsService,该惰性账号不参与管理员初始化,
本轮未将其误报为 Bootstrap 密码泄漏,也未擅自改变安全配置。
并发窗口前后 auth_tokens=170、notifications=156、messages=8、audit_logs=2952,
均逐项不变;RabbitMQ 四队列均为 ready=0 / unacked=0。并发夹具已精确删除,21 个活动主角色
ADMIN 已逐行恢复,历史用户 7938/7939 完好,V43 三个演示账号仍为 inactive。
两个 Java 进程均按精确 PID 停止,8080/8081 与四个隔离端口全部释放;日志与结果 JSON 仅保留在 系统临时目录,未进入仓库。
M3 判定
M3 PASS:双 JAR 并发只产生一个合法管理员,事务回滚、日志脱敏、业务副作用与环境清理均满足契约。
管理员初始化的 M1 配置装配、M2 幂等与冲突矩阵、M3 并发与事务三阶段现已全部通过。下一步仅进行 M4 管理员初始化证据汇总与最终边界复核;不得因此提前宣称全项目最终验收通过,Q01 跨库 EXPLAIN、 写侧刷新链路以及 DEF-01 待推送状态仍需独立收口。
S03 管理员初始化 M4:证据汇总与最终边界复核
判据汇总
| 阶段 | 覆盖范围 | 最终结果 |
|---|---|---|
| M1 | 默认关闭、false、大小写/别名非字面量、缺失/空白/非法/合法手机号共 11 项配置装配矩阵 |
11/11 PASS |
| M2 | 首次创建、重复启动幂等、STUDENT/COMPANY/inactive/角色关系缺失/附加角色/password 非空六类冲突 | 全部 PASS |
| M3 | 双 JAR 真实并发、事务回滚、统一日志脱敏、业务副作用与清理 | PASS |
| 自动化 | 管理员初始化定向测试 | 38/38,F=0 E=0 S=0 |
| 全量 | mvn clean verify |
2800/2800,F=0 E=0 S=0 |
| 契约 | OpenAPI | 176 paths / 214 operations |
M1 初次脚本误判和 M2 初次发现的三个生产阻断均保留在前文,未覆盖历史记录。M2 的最终生产修复范围仅为
AdminBootstrapRunner:配置手机号存在时先执行严格幂等校验;只有配置手机号不存在时,其他活动 ADMIN
才可触发无副作用跳过。对应测试新增 3 条阻断回归用例,未修改公开配置、事务创建器、数据库结构或接口。
最终边界复核
- 当前真实生产代码差异恰好 1 个文件:
AdminBootstrapRunner.java(9 行新增、7 行删除); 测试差异恰好 1 个文件:AdminBootstrapRunnerTest.java(29 行新增、4 行删除)。 - 暂存区为空,未执行提交或推送;本轮生产/测试差异的敏感模式扫描为 0 命中。
CandidateOverviewStatusPolicy.java与JobPreferenceScoreCalculator.java虽被 Git 标记为工作区修改, 但工作区与 HEAD 的 blob hash 分别完全相同,属于 Windows stat 缓存假阳性,不计入内容差异。- 无 Java 进程;8080/8081、19801/19901、19802/19902 均无监听。
- 管理员并发夹具残留 0;活动主角色 ADMIN 为 21,活动 ADMIN 角色关系为 20,历史用户 7938/7939
均存在,V43 三个演示账号活动数为 0;
audit_logs=2952且未删除历史审计。 - M3 前后
auth_tokens=170、notifications=156、messages=8;RabbitMQ 四队列均保持ready=0 / unacked=0。 - 未修改 V1–V45、表、索引、
ErrorCode、DTO、依赖或 OpenAPI;未进入 Q01 或写侧刷新实现。
S03 管理员初始化最终结论
S03 管理员初始化真实 MySQL 配置、幂等、冲突、并发、事务、脱敏与副作用矩阵全部通过,可以冻结该验收项。
该结论只关闭管理员初始化验收项,不等于全项目最终验收通过。当前仍需完成 Q01 H2/MySQL 对账与 EXPLAIN 分表证据、评分写侧刷新链路验收,并处理 DEF-01 本地提交领先远端 1 个提交的推送状态。
Q01 H2/MySQL 逐项对账与 EXPLAIN 分表最终复验
执行环境与测试结果
Docker Server 29.6.2 可用,Testcontainers 实际启动 mysql:8.4,执行正式 Flyway 迁移并通过
Hibernate schema validation;本轮不存在 disabledWithoutDocker 跳过。
| 层级 | 测试数 | Failures | Errors | Skipped |
|---|---|---|---|---|
| H2 Specification | 21 | 0 | 0 | 0 |
| H2 Service | 9 | 0 | 0 | 0 |
| H2 行级副作用 | 4 | 0 | 0 | 0 |
| MySQL 8.4 Specification | 15 | 0 | 0 | 0 |
| MySQL 8.4 行级副作用 | 4 | 0 | 0 | 0 |
| MySQL 8.4 EXPLAIN | 1 | 0 | 0 | 0 |
| Q01 合计 | 54 | 0 | 0 | 0 |
另行重跑 DEF-01 入口回归 9/9,覆盖 null/缺失/空/空白/trim 关键词、DRAFT 排除、分页、排序和 只读副作用,Failures=0、Errors=0、Skipped=0。
H2/MySQL 语义对账
两库共同覆盖并取得一致结果的契约包括:关键词 trim、null/空白不施加过滤、标题/描述/要求字段匹配、 关键词大小写、城市与用工类型的 trim 精确匹配及大小写变体、薪资区间重叠与边界相等、NULL 薪资的 SQL 三值逻辑、公司精确过滤、多条件 AND、非公开岗位排除、空结果、稳定 latest 分页和 salary 排序。
H2 侧保留更细的单维度拆分用例,MySQL 侧将若干相同契约合并在一个参数化/综合用例内;因此测试方法 数量不要求相等,但相同业务条件的 ID 集合、顺序和边界期望一致。查询副作用在两库均为 4/4:真实查询 前后行级稳定字段完全相同,且插入、删除、改单字段三类反证都能被快照检测,证明零变化比较非空转。
MySQL EXPLAIN 分表证据
本次重新生成 target/q01-explain-report.md,EXPLAIN 使用与 Criteria 查询相同 SQL 和参数,并先断言
两者返回的岗位 ID 与顺序一致。真实结果如下:
| SQL | jobs access_type |
possible_keys | filesort | temporary |
|---|---|---|---|---|
| 组合过滤 + latest 分页 | ALL |
company/status、status/created、status/employment、status/salary | true | false |
| latest 分页 | ALL |
status/created、status/employment、status/salary | true | false |
| salary 排序 | ALL |
status/created、status/employment、status/salary | true | false |
| count | ALL |
company/status、status/created、status/employment、status/salary | false | false |
夹具按城市仅构造 1/20/50 条岗位,优化器在该小样本上选择全表扫描;该结果只证明 SQL 形状与当前执行
计划,不能外推为生产规模性能结论,也不能误写成“命中索引”。关键词 %keyword% 前导通配符和排序
filesort 仍是已知性能风险。本轮不新增 V46、不修改索引或查询语义。
SQL 数量证据:匹配 1/20/50 条时 Repository 分别执行 1/2/2 条 SQL,完整 Service 分别为 2/3/3 条;结果规模增长不会形成逐岗位查询,未发现 N+1。
Q01 最终判定与边界
Q01 H2/MySQL 查询语义、行级副作用、真实 MySQL EXPLAIN 与 DEF-01 回归全部通过,可以冻结 Q01 最终验收项。
本轮未修改任何生产代码、测试、迁移、索引、依赖或 OpenAPI;Testcontainers 容器已由 Ryuk 回收, 项目既有 MySQL/Redis/RabbitMQ 容器保持运行。全项目最终验收目前只剩评分写侧刷新链路,以及本地未提交 的管理员初始化修复和 DEF-01 领先远端 1 个提交的 Git 收口。
R01-D4 评分写侧刷新链路最终复验
本轮新鲜自动化证据
使用当前工作区代码重新执行刷新基础设施、写侧登记与事务原子性测试,Docker/Testcontainers 实际启动 MySQL 8.4,结果 67/67,Failures=0、Errors=0、Skipped=0:
| 测试类 | 数量 | 核心覆盖 |
|---|---|---|
CandidateScoringRefreshBatchIsolationTest |
4 | 单任务失败不阻断同批正常任务 |
CandidateScoringRefreshClaimFenceTest |
6 | 旧领取者不得覆盖新领取者 |
CandidateScoringRefreshClaimTokenTest |
13 | 不可复用 claim token、迟到完成/失败/回退均安全 |
CandidateScoringRefreshFailureTransactionTest |
3 | 源业务回滚时登记同步回滚 |
CandidateScoringRefreshInfrastructureTest |
14 | 登记合并、重开、PROCESSING 防丢失、八维写入与版本幂等 |
CandidateScoringRefreshMySqlIntegrationTest |
6 | 真实 MySQL 领取、租约恢复、失败回滚与批次隔离 |
CandidateScoringRefreshWorkerLoopIsolationTest |
3 | 单项处理及失败登记异常不终止 Worker 循环 |
CandidateFeedbackRefreshRegistrationTest |
7 | Feedback create/update/delete 均登记,失败路径无孤立任务 |
OfferExpiryRefreshRegistrationTest |
7 | Offer 自动过期与任务同事务,重复执行合并且无消息副作用 |
ResumeVersionRefreshAtomicityTest |
4 | 简历版本与任务原子提交、外层回滚一致、重复登记合并 |
本轮之前完成的 mvn clean verify 为 2800/2800,F=0 E=0 S=0,已包含上述测试及评分消费者回归;
OpenAPI 保持 176/214。
真实 HTTP/MySQL 链路证据的有效性核查
归档独立验收 docs/r01-d4-2-claim-token-final-verification-20260916.md 已在真实 JAR、真实 MySQL、
Redis 与 RabbitMQ 环境执行五条写侧链路,结果 44/44,FAIL=0,SKIP=0:
- Offer 自动过期;
- CandidateFeedback create、update、delete;
- 简历版本创建。
每条均证明“业务数据变化 → 同学生/周期唯一 refresh task → Worker 处理为 COMPLETED → 八维快照齐备 → 相同输入重放不推进 snapshot version → 不写 snapshot_history”。消费者趋势、排名、看板、申诉和 公平性监控另有 33/33 回归证据。
git log 逐文件核查显示,刷新包、CandidateFeedbackService、OfferExpiryWorker 和
ResumeVersionWriter 的最后一次生产修改均为已验收提交 52c8026;此后没有生产变更。因此归档 HTTP
证据与本轮重新执行的当前代码完全对应,不存在拿旧实现结果替代当前实现的问题。历史脚本依赖已失效的
Token 上下文,本轮未直接复用旧 Token,也未把旧上下文伪装为新执行结果。
数据安全与边界
- V44/V45 仍是刷新基础设施仅有的两项迁移,V1–V43 未修改,无 V46;
- claim token 只作为数据库内部所有权凭证,不进入日志、审计、响应、通知、消息或快照 JSON;
- Feedback、Offer、Resume 的失败/回滚路径不会留下孤立任务;
- 任务重复登记按学生与周期合并,值未改变时八维快照 version 不推进;
- 写侧登记本身不发送 RabbitMQ 消息、不写通知或快照历史;
- 本轮未改生产代码、测试、配置、迁移、接口或数据库数据。
写侧刷新链路最终判定
评分写侧事件 → refresh task → Worker → 八维快照刷新链路通过最终复验,可以冻结 R01-D4 运行时验收项。
至此,最终总验收的功能与真实数据库运行项已经全部完成;下一步仅进行 Git 收口:独立审阅并精确提交 管理员初始化修复及总验收报告,随后在网络可用时推送 DEF-01 与该提交,确认 HEAD/upstream 0/0。
最终总判定
PASS:S01、S02、S03、Q01、R01 的独立代码复验、自动化回归与真实 MySQL/Docker 黑盒验收全部完成,核心项 FAIL=0、SKIP=0,可以结束整改开发并进入 PR 审查。
最终收口代码提交:
- DEF-01 岗位公开列表关键词规范化:
0b2ca79b96ea585dbafc753a980f7f79f5cf15db; - 管理员初始化严格幂等校验:
443a305(本报告提交时的本地提交短 hash)。
最终有效门禁为 mvn clean verify 2800/2800,Failures=0、Errors=0、Skipped=0;
OpenAPI 静态与运行时均为 176 paths / 214 operations。管理员初始化 M1–M3、Q01 跨库与 EXPLAIN、
评分写侧刷新链路均已在本报告末尾完成独立收口;前文历次 FAIL、脚本缺陷与观察项作为审计轨迹保留,
不代表最终状态。
保留的非阻断观察项包括:Spring Boot 自动配置产生的惰性 generated security password 日志、历史
SMS_SEND 审计数据中的旧手机号、未读数包含本人发送但未读消息、Q01 小样本执行计划的全表扫描与
filesort,以及 Hikari 偶发连接校验告警。上述项目均已界定范围,未被隐去,也不改变本轮 PASS 判定。