Files
prop-ai-hr/docs/BRD_PRODUCTION_MIGRATION_RUNBOOK.md
T

359 lines
45 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AIHR BRD 生产迁移 Runbook
本文件只覆盖 AIHR 阶段一 BRD 已落地代码所需的 MySQL 8 结构和内置内容迁移。它不执行正式组织同步、试点训练、人工校准、SOP 评审、案例导入或满意度补录;这些属于业务数据和正式验收,必须单独审批。
## 当前生产基线
2026-07-18 已在生产发布功能包提交 `064816a7`:
- 22 个版本化迁移按本文件顺序执行完成,目标结构核验为 `35/35`;
- 最后一条排序规则迁移把 19 张发布表对齐到 `aihr_knowledge_info.tenant_id` 的实际排序规则,核验为 `19/19`;
- 管理端、H5、后端产物与本地发布产物哈希一致,`wygj-aihr.service` 为 `active`;
- 根站、H5、租户接口、员工首页 API 和关键管理页通过生产回归;
- 该次发布备份位于 `/opt/wygj/backups/ask-memory-20260718-2130`,内含完整数据库、JAR、H5 和 SHA-256 清单。
上述事实只证明本批版本和 schema 已发布。正式 OTP、真机能力、角色权限正反例和严格试点窗口仍需按发布后回归及 `DEMO_ACCEPTANCE.md` 验收。
2026-07-25 已完成生产资料处理 multipart 限额修正:
- 已备份线上外置 `application-prod.yml`,并将 Spring/Undertow `max-file-size` 设为 `500MB`、`max-request-size` 设为 `520MB` 后重启 `wygj-aihr.service`;
- 服务重启后为 `active`,101MB 的无认证回环 multipart 请求已通过解析层并止于鉴权,未写入业务表或 OSS;
- 此配置在当时只让既有异步队列的视频和 ZIP 兑现其 `≤500MB` 契约;普通文档、同步 SOP 上传和公司文件仍按各自的 `100MB` 业务限制执行。
资料处理中心当前发布约束:
- 2026-07-29 已将异步批量上传的文档、图片、视频和 ZIP 统一为单文件 `≤500MB`,管理端校验、后端 `AihrUploadQueueService` 和 Tika `500MiB` 输入流上限已同批发布并用 101.6MB XLSX、147.9MB PPTX 完成生产解析与向量入库验证;
- 生产 Spring/Undertow 继续保持单文件 `500MB`、单请求 `520MB`,无须进一步放大传输层;
- 同步 SOP 上传和公司文件不属于本次范围,继续保持 `100MB`。
2026-07-25 已完成最新完整包发布,发布源为 `230dfb4d`:
- 完整备份位于 `/opt/wygj/backups/broadcast-insights-20260725152349`;管理端、H5、后端 JAR/AIHR 模块与发布源一致,`wygj-aihr.service` 为 `active`;
- 远端 schema 只读预检为 `64/64`,包括 `aihr_practice_help_event`、数字师傅 Agent、主管下一训练层级确认和公司文件多岗位解读所需结构;
- 公司文件多岗位解读已完成生产 H5 认证态验证,包含四视角切换、原文依据、下载哈希一致及撤回后拒绝访问;
- 主管下一训练层级确认和数字师傅 Agent 仅确认结构/产物已部署,尚未完成各自的生产认证态业务回归;不得由 `64/64` 推断业务验收完成。
2026-07-26 已完成后端 ASR 单项热修复:
- 生产 `category=asr` 已切换为 `dashscope/qwen3-asr-flash`,旧 `siliconflow/TeleAI/TeleSpeechASR` 已停用;供应商密钥仍只保存在受控运行配置和模型表,不进入仓库、命令记录或验收材料;
- 生产接口已获得正确转写和受控 `ossId`,Android HBuilderX 标准基座“按住说话”请求成功,服务日志无此前的 403/ASR 异常;
- 回滚目录为 `/opt/wygj/backups/asr-hotfix-20260726210122`,包含原 JAR 与模型配置 SQL。本次未发布管理端/H5/App 静态资源、未变更 schema,不替代完整包预检、自定义基座权限验收、弱网/方言样本或 Agent 专项业务回归。
2026-07-29 已完成资料处理媒体恢复的 schema、管理端与后端定向发布:
- 先备份 `aihr_knowledge_upload_item`、生产后端和完整 `/opt/wygj/www`,执行 `aihr_20260729_media_reprocess_mysql8.sql`,并通过 `source_attach_id` 列及 `(tenant_id, source_attach_id, id)` 索引契约;
- 生产后端 JAR SHA-256 为 `5376b67b33b171b5c85e22cd6a2f20bbb0bc527fc50a223603d0aec3f6587bac`,服务为 `active`;定向发布脚本等待根站、租户和移动首页真实就绪后再跑后端 + schema 预检,三个公开入口均返回 HTTP 200;
- 管理端资料处理页已发布,移动 H5 未重发。原卡住的 4 个图片任务已从 OSS 原件重试,最新队列条目全部完成、各有一个片段且向量存在,页面“待重试”为 0;
- 该次只证明资料恢复链路、管理端和后端定向发布成功。移动 H5 与当前工作树的完整包匹配、Android 真机和严格试点仍须分别验收。
2026-07-30 已完成 8 月 1 日策略边界后端定向发布:
- 发布源提交为 `6a29f990`,生产后端 JAR SHA-256 为 `b090a7752e1ce0bd83bff4808c0111c0035f7142e33402c0a9526e988929d314`,AIHR 模块与本地构建一致;
- 回滚备份为 `/opt/wygj/backups/backend-20260730232827-18c7d0954080`;服务激活、三个公开入口和只读 schema `64/64` 均通过,运行时 schema bootstrap 保持关闭;
- 认证态生产冒烟通过,30 题状态/冲突/无依据/越权/引用结构回归为 `30/30`;vivo Android 15 上的 `0.1.17 (117)` 又完成首页、学习中心和问师傅真实发问/引用复验;
- 来源权威性审计仍发现经验萃取/访谈材料命中。该发布证明后端边界和功能链路,不证明 30 道题已形成正式制度答案,严格批准清单继续失败关闭。
2026-07-31 已完成正式来源治理与受控内部公测收口:
- 最新后端 JAR SHA-256 为 `d5145194024ba3526a869e3270df6d71468931b96ba9aebb93121592e54012eb`,回滚备份为 `/opt/wygj/backups/backend-20260731012751-73a6caf69aa3`;服务、三个公开入口、AIHR 模块匹配和 schema `64/64` 均通过;
- 政策题只允许当前有效、已批准、版本和原文件哈希完整且与问题领域相关的正式来源进入回答;无相关制度时统一失败关闭,不再回退经验/访谈材料;
- 使用生产现有 APP 账号重新执行 30 题,得到 11 个正式有据回答和 19 个冲突/无依据/越权安全边界,合计 `30/30`,且不在证据中保存手机号、验证码、令牌、问题或答案正文;
- 场景 `5/5`、问题 `30/30`、五通道 `5/5`、负责人 `5/5` 和 DCloud 测试签名受控分发均已签认;`verify-aug1-formal-content.mjs --strict` 与 `verify-aug1-release-readiness.sh --execute` 通过。该结论仅为 2026-08-01 受控内部公测 Go,不替代严格试点窗口或对外正式发布。
2026-07-20 增量把“项目服务记忆最小切片”扩展为工作助手确认式统一采集:显式“记一下”生成候选,用户确认后按 `PRIVATE/COMPANY` 写入 `aihr_assistant_capture`;个人记录仅本人可见,公司记录先保持 `PENDING`,正式接收端和回执尚未实现。旧 `aihr_service_memory/version` 继续兼容项目记录;同批会话式“工作上报”继续使用既有 `aihr_work_report`,并补 `request_key/request_hash` 和唯一索引。
2026-07-21 已在生产发布项目化工作助手与今日工作成果提交 `4536d607`,并发布确认卡首屏修正 `11250811`:
- 执行 `aihr_20260721_org_multi_project_membership_mysql8.sql` 与 `aihr_20260721_work_assistant_results_mysql8.sql`,目标新表、字段和组织多项目唯一约束核验通过;
- 后端 JAR、H5 入口资源与本地发布产物哈希一致,`wygj-aihr.service` 为 `active`;
- 正式员工账号完成项目读取、伪造项目拒绝、文字巡检确认卡、候选撤销和按员工 + 项目 + 日期幂等生成成果回归;
- 当前正式员工账号只返回一个授权项目“银河湾星苑”,故生产仅证明单项目自动选择,多项目名称下拉继续以本地双项目授权数据为证;
- 本次完整备份位于 `/opt/wygj/backups/work-assistant-20260721-044923`,包含数据库、旧 JAR、完整 H5 和 SHA-256 清单。
上述事实仍不代表生产真机语音/照片/视频、主管正式账号、正式组织多项目 dry-run 或外部线索/工单/考勤回执已验收。个人文件、网页收藏、独立个人向量库、自动派单和未经确认的自动外发仍不在本轮范围。
2026-07-23 已完成“大喇叭 + 问模块纠偏”相关的一轮全量迁移与重新发布,发布源为本地 `f023417a`(含业务提交 `4b31848e`):
- 本批按当时清单执行 `aihr_20260722_knowledge_category_mysql8.sql`、`aihr_20260722_broadcast_mysql8.sql`、`aihr_20260722_broadcast_question_context_mysql8.sql` 与 `aihr_personal_knowledge_mysql8.sql`;后者建立 10 张 `aihr_personal_*` 表,并配置私有 `personal-minio` 存储与 `ruoyi-personal` bucket;
- 当时 `release-preflight` 的远端只读核验为 schema `57/57`,且 H5、管理端资源、后端 AIHR 模块均与本地构建一致,`wygj-aihr.service` 为 `active`。后续只读复核已确认 M1 的 `aihr_broadcast_target_rule`、`aihr_broadcast_target_recipient` 及必读字段存在;这只证明 schema,不能替代 M1 认证态定向/必读闭环验收;
- 本次完整备份位于 `/opt/wygj/backups/aihr-full-20260723-094600-f023417a`,包含数据库、旧静态资源和旧 JAR;
- 发布后仅完成公开接口和未登录 H5 页面的基础检查。正式认证账号的“管理发布—员工阅读—消息追问”闭环、真机媒体及完整个人资料能力仍未作为生产验收完成。
2026-07-24 已完成五岗位训练内容运营的完整发布,发布源为 `faa2a77e`:
- 在既有完整迁移序列上执行训练内容运营、M2 求助事件和租户组织治理迁移;当前远端 `release-preflight` 只读核验为 schema `62/62`,且管理端/H5 静态资源、后端 AIHR 模块均与本地构建一致,`wygj-aihr.service` 为 `active`;
- 训练内置内容为五岗位共 `100` 个场景,`86` 个启用、`14` 个高风险场景保持禁用,另有 `100` 条启用 Rubric 和 `500` 条评分维度;这只是结构与内置内容事实,不替代业务内容签字;
- 认证态员工 H5 已到达训练预检页;未在本次发布验证完整训练写入、主管复盘、真机麦克风/弱网或严格试点;
- 本次完整备份位于 `/opt/wygj/backups/practice-ops-20260724003337`,包含数据库、旧静态资源和旧 JAR。
数字师傅 Agent、`aihr_agent_run` 和对应移动端改造已随 2026-07-25 完整包部署,2026-07-30 又完成专项认证态生产回归和真机问师傅复验;下文迁移顺序仍是重建/回滚的执行依据。功能回归通过不代表引用来源已获得正式政策批准。
## 发布前置条件
1. 确认发布窗口、DB 备份和回滚负责人;迁移脚本包含 `ALTER TABLE`,不能在无备份状态执行。
2. 发布前先执行 `scripts/release-preflight.sh` 的本地只读检查。定向后端发布先运行 `scripts/release-backend.sh plan`;只有负责人明确授权后才运行 `deploy`,由脚本固定目标、备份旧 JAR、校验候选和原子切换。重启后最多等待 90 秒,只有根站、租户和移动首页真实就绪才执行后端 + schema 预检;激活、就绪等待或发布后预检失败都会恢复旧 JAR 并再次验证公开端点。发布后预检使用 `RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh`,只核对 AIHR 模块、必需表和生产运行时 schema 保护,不因本轮未发布的静态资源产生假失败;该结果必须明确记录为“后端定向发布”。完整包发布后,必须按 `docs/DEV_SETUP.md` 再启用 `RELEASE_VERIFY_REMOTE_MATCH=true`,同时核对线上静态资源、AIHR 模块和必需表;schema 检查只读、不执行迁移。定向后端或单项 H5 校验不能替代完整包复核。
3. 确认迁移目标库、租户和字符集为 MySQL 8 / `utf8mb4`;脚本只允许在目标业务库执行。
4. 生产组织同步凭据、AI 供应商留存/费用口径和正式试点窗口仍未闭合时,不要把迁移完成宣称为 BRD 验收完成。
## 执行顺序
在生产数据库连接上按以下顺序执行,每个脚本执行后记录退出码和关键表结构。所有脚本均设计为可重复执行,但仍应按顺序执行。
```bash
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_catalog_schema_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_brd_scenarios_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_brd_sop_seed_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_prompt_template_seed_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_evidence_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_case_media_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_case_learning_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260802_case_experience_governance_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_five_dimensions_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_practice_session_evidence_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260723_practice_m2_help_event_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_sop_requester_identity_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_knowledge_feedback_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_tenant_collation_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260716_knowledge_space_platform_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_practice_assignment_batch_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_practice_assignment_contract_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_learning_closure_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_practice_five_position_catalog_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260723_practice_content_operations_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260724_practice_growth_curriculum_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260802_learning_task_exam_bank_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_web_ai_question_reward_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_knowledge_conversation_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_release_collation_compat_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_unify_all_collations_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_service_memory_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260721_org_multi_project_membership_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260721_work_assistant_results_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_work_report_idempotency_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_web_ai_database_secret_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_web_search_qwen_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260723_tenant_org_governance_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260722_knowledge_category_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260730_formal_knowledge_source_governance_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260722_broadcast_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260723_broadcast_targeting_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260724_direct_feedback_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260724_broadcast_attachment_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260725_broadcast_multi_role_insight_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260802_broadcast_delivery_controls_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260722_broadcast_question_context_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260724_agent_orchestration_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260725_deepseek_v4_model_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260725_sop_answer_partial_evidence_prompt_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260729_media_reprocess_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260731_knowledge_fragment_locator_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260801_data_quality_lifecycle_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260802_data_quality_observability_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260803_data_quality_structure_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260804_data_quality_feedback_loop_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260802_feedback_question_bridge_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260805_knowledge_attachment_immutability_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260806_data_quality_extraction_evidence_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260807_data_quality_semantic_conflict_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260808_data_quality_monitoring_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260809_knowledge_glossary_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260810_case_provenance_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260811_rule_evolution_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260812_pipeline_run_sampling_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260813_golden_calibration_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260814_knowledge_privacy_derivative_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260815_knowledge_rollout_compat_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260816_knowledge_version_pointers_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260817_knowledge_review_assistance_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260818_knowledge_generation_revision_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260819_knowledge_retention_deletion_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260820_knowledge_migration_orchestration_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260821_knowledge_shadow_gate_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/aihr_personal_knowledge_mysql8.sql
```
七个数据质量迁移必须按 `20260801 -> 20260802 -> 20260803 -> 20260804 -> 20260805 -> 20260806 -> 20260807` 顺序执行:生命周期迁移创建不可变资产/版本/质量问题/审核/数据集/血缘/outbox;可观测性迁移增加 outbox 死信和查询证据;结构迁移增加 SimHash、结构画像、extractor 版本与近重复聚类;反馈闭环迁移把答案反馈绑定到查询请求和质量问题,并记录 `KEEP/WITHDRAW` 人工结论;附件不可变迁移取消“知识空间内文件名唯一”的覆盖语义;解析证据迁移保存页数、逐页 OCR 可读性和提取器版本;语义与冲突迁移增加异步分析状态、可审计重复关系、claim/观点及冲突关系。同名或精确重复上传可共享只读 OSS 对象,但必须创建新的附件、候选资产和审核记录,不能改写旧附件的 `oss_id/doc_id`。所有算法只创建待审证据,不能自动批准、合并、替代或删除资料;执行 schema 不等于历史资料已获批准。
随后按 `20260808 -> 20260809 -> 20260810 -> 20260811 -> 20260812 -> 20260813 -> 20260814` 执行质量监控、人工术语修订、AI 案例来源、规则演进、处理批次、黄金集校准和隐私派生迁移。`20260811` 只增加规则草稿、状态审计、影子对照和抽样复核;`20260812` 只增加 `pipeline_run` 与自动生成待人工复核样本的调度,不启用规则执法,不改变任何资产生命周期。`20260814` 只增加独立脱敏正文、脱敏文件名、派生 hash、隐私状态、规则版本和复扫摘要字段,历史版本默认保持 `NOT_PROCESSED`,不得通过 SQL 伪造脱敏完成或自动获批。完整迁移后仍须最后执行排序规则兼容迁移。
生命周期兼容发布继续按 `20260815 -> 20260816 -> 20260817 -> 20260818 -> 20260819 -> 20260820 -> 20260821` 执行:先建立按空间的 `LEGACY/SHADOW/ENFORCED` 兼容范围,再增加候选/生效双指针、审核辅助、不可变 generation 与回滚证据、保留和异人删除、历史迁移清单、影子指标与启用门禁。七个脚本均为增量结构迁移;不得在 SQL 阶段自动发布、撤回、删除或切换现有空间。迁移完成后所有空间仍应为 LEGACY 或保持迁移前显式模式,历史资料继续可见。生产发布必须先只读统计资产、版本、生产成员、fragment、OSS 和 Qdrant 数量并备份;先以单空间 dry-run 和 SHADOW 验证,达到门禁后由人工启用。正式迁移任务必须通过 `verified_dry_run_id` 绑定同知识空间、同风险范围、同批大小且无失败的已完成演练;创建正式任务不自动推进批次。任何补链失败都停留在迁移 dead letter,不得切断旧读路径。
生产物理删除不是部署步骤,也不得与 schema/JAR 发布同窗口批量执行。日常纠错只做 `withdraw`;`purge` 必须有业务/法务确认的保留分类、影响快照、异人批准和已到期 `execute_after`。发布后先运行 reconciliation 并处理不一致,再考虑清理。ENFORCED 启用后旧 generation 保留七天;窗口内指标恶化可从管理端回退上一代,超过窗口必须重新构建候选 generation 和重新通过门禁。
8 月 1 日受控内部试点的正式来源注册是独立的业务数据变更,不随通用 schema 自动执行。先逐一核对附件 `id + doc_id`、原文件 SHA-256、发文号/版本、生效日期和适用范围,再由已授权负责人执行 `backend/script/sql/ops/aihr_20260730_aug1_formal_source_registry_mysql8.sql`。执行后必须只读确认恰好 10 条 `FORMAL_POLICY + APPROVED` 记录;任何缺失都保持无正式依据,不得把访谈、经验萃取、AI 生成内容或草稿补进白名单。
顺序原因:场景补充依赖三张场景/Rubric 基础表;五维迁移依赖场景和 Rubric;五岗位题库之后的内容运营迁移为每个场景补齐唯一的初始 Rubric、五个稳定评分维度与训练时评分规则快照列,并只修正仍处于待训练状态的旧每日题场景编号,不重写已完成记录。紧随其后的成长目录迁移只在既有场景和会话表上增加岗位/层级/能力项/审核状态与会话快照,以及复盘会话专用的 `growth_confirmation_level/growth_confirmed_by/growth_confirmed_time`;它只允许记录主管对当前会话下一训练层级的确认,不改写原训练快照或人事结论,并补齐/修复派发请求的 `(tenant_id,request_key,ext_party_id)` 唯一键与最近内容过滤索引。它保留既有启停可用性和历史会话,不把迁移结果写成业务内容签字。生产环境不得设置 `AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP=true`:应用请求只读校验表、列和关键索引,任何缺失都必须先执行本节正式 SQL,不能由任一用户请求触发补列、补索引或状态回填。岗位/SOP/任务/资格迁移只补正式数据契约,不写业务行,也不代表岗位适用范围、任务规则或资格标准已经获得 HR 确认。住宅 SOP 和 Prompt 迁移只补内置内容,不代表内容已完成业务复核。知识会话迁移只新增短期上下文表,不生成业务对话。排序规则兼容迁移先对齐本批发布表,全量排序规则迁移再统一历史 `aihr_*` 表;两者都读取目标库 `aihr_knowledge_info.tenant_id` 的实际排序规则。服务记忆迁移创建候选、统一采集、兼容项目记录和版本留痕四张表;工作上报幂等迁移随后只补原表列与唯一索引;知识分类迁移新增空间内分类表及附件成员的可空 `category_id`,不迁移、删除或扩大任何空间授权与检索范围;正式知识来源治理迁移只创建附件级权威类型、生命周期、版本、生效期、适用范围和原文件哈希契约,不自动批准任何历史附件。移动端政策问答只使用有效的 `FORMAL_POLICY + APPROVED` 注册项,治理表缺失、来源未批准、已失效、版本或哈希为空时均按无正式依据关闭。银城大喇叭 M0 迁移创建消息、阅读和不可变 v1 快照三表,为已存在消息补一次快照,并补发布请求键/载荷哈希、撤回原因及 `(tenant_id,published_by,publish_request_key)` 唯一约束。紧接着 M1 定向迁移补充必读与目标载荷字段,以及规则快照、命中对象快照两表;它只决定提醒/必读对象,不改变全租户公开频道的可见性。直通车迁移创建点对点反馈表,并给正式租户 `000000` 幂等预置总裁、财务、人力、审计、运营五个处理角色;人员仍须由管理员按职责分配,业务匿名不抹除内部审计身份。公司文件迁移创建异步提炼表并给消息增加可空 `attachment_id`,不迁移或公开已有文件;紧随其后的多岗位解读迁移只在附件表增加生成状态、经引用校验的结构化结果和规则版本,并建立异步队列索引,不回填旧文件、不改变文件 READY 或消息发布状态。随后,消息追问迁移只给短期会话增加可空 `broadcast_message_id`;它不保存消息正文,并用于约束同一会话不能切换公司消息。历史普通会话保持 `NULL`。Agent 迁移只新增最小运行审计表,不保存问题、答案、密钥或模型推理。个人资料迁移最后创建独立 `aihr_personal_*` 表,并从当前租户既有 MinIO 配置派生私有 `personal-minio` 配置;`ruoyi-personal` bucket 必须由受控运维流程创建和验证,不能在文档或日志中输出访问密钥。它们同样对齐目标排序规则,兼容历史生产库与全新 MySQL 8 数据库。
`aihr_20260802_broadcast_delivery_controls_mysql8.sql` 必须位于公司文件及多岗位解读之后、消息追问上下文之前。它增加 `ALL/TARGET_ONLY` 可见模式、下载控制、带原文依据的主题标签表和文件访问审计表,不自动修改既有消息受众、下载策略或标签。
## 迁移后只读核验
执行后只读检查结构和内置内容,不修改业务数据:
```sql
SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name IN (
'aihr_prompt_template',
'aihr_practice_scenario', 'aihr_practice_rubric', 'aihr_practice_rubric_dimension', 'aihr_learning_question',
'aihr_practice_audio', 'aihr_practice_audio_upload', 'aihr_practice_calibration', 'aihr_practice_help_event',
'aihr_knowledge_gap', 'aihr_knowledge_answer_feedback',
'aihr_knowledge_space_grant', 'aihr_knowledge_app', 'aihr_knowledge_app_space', 'aihr_knowledge_category',
'aihr_knowledge_query_log', 'aihr_knowledge_admin_audit', 'aihr_knowledge_conversation', 'aihr_agent_run',
'aihr_practice_question_feedback',
'aihr_onboard_exam', 'aihr_onboard_exam_target', 'aihr_onboard_exam_question', 'aihr_exam_question_draw',
'aihr_onboard_exam_attempt', 'aihr_onboard_exam_answer',
'aihr_web_search_provider', 'aihr_web_ai_secret', 'aihr_web_ai_query_audit',
'aihr_community_question', 'aihr_community_answer',
'aihr_incentive_rule', 'aihr_points_ledger',
'aihr_candidate_material', 'aihr_interview_result', 'aihr_candidate_employee_link',
'aihr_position_responsibility', 'aihr_sop_applicability',
'aihr_onboard_task', 'aihr_qualification_gate',
'aihr_org_directory', 'aihr_tenant_org_binding',
'aihr_memory_candidate', 'aihr_assistant_capture', 'aihr_assistant_capture_status_log',
'aihr_daily_work_result', 'aihr_service_memory', 'aihr_service_memory_version',
'aihr_work_report', 'aihr_broadcast_message', 'aihr_broadcast_read', 'aihr_broadcast_version',
'aihr_broadcast_target_rule', 'aihr_broadcast_target_recipient', 'aihr_broadcast_attachment',
'aihr_direct_feedback',
'aihr_personal_space', 'aihr_personal_item', 'aihr_personal_fragment',
'aihr_personal_chat_session', 'aihr_personal_chat_message', 'aihr_personal_cleanup_job',
'aihr_personal_ocr_job', 'aihr_personal_ocr_page', 'aihr_personal_export_task',
'aihr_personal_publish_request'
)
ORDER BY table_name;
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND (
(table_name = 'aihr_case_record' AND column_name IN ('media_oss_id', 'media_url', 'learning_points', 'supervisor_comment', 'knowledge_status', 'source_type', 'applicable_positions', 'content_version', 'source_authorized', 'deidentified', 'effective_from', 'effective_to', 'release_owner', 'release_note', 'released_by', 'released_time', 'retire_reason'))
OR (table_name = 'aihr_practice_session' AND column_name IN ('dim_task_completion', 'dim_response_timeliness', 'response_latency_ms', 'rubric_code', 'rubric_version', 'rubric_snapshot_json', 'annotations_json', 'review_advice', 'incentive_point', 'satisfaction_score', 'satisfaction_comment', 'position_snapshot', 'project_type_snapshot', 'growth_level', 'competency_code', 'collaboration_positions', 'curriculum_version', 'growth_confirmation_level', 'growth_confirmed_by', 'growth_confirmed_time'))
OR (table_name = 'aihr_practice_scenario' AND column_name IN ('content_version', 'content_hash', 'growth_level', 'competency_code', 'collaboration_positions', 'review_status', 'risk_level', 'curriculum_version', 'reviewer', 'reviewer_user_id', 'reviewed_time', 'second_reviewer', 'second_reviewer_user_id', 'second_reviewed_time'))
OR (table_name = 'aihr_practice_assignment' AND column_name IN ('score', 'score_mode', 'due_date', 'request_key', 'content_version', 'question_snapshot', 'reference_answer_snapshot', 'content_hash'))
OR (table_name = 'aihr_sop_answer_review' AND column_name IN ('prompt_version', 'requester_ext_party_id'))
OR (table_name = 'aihr_knowledge_info' AND column_name IN ('code', 'space_type', 'sensitivity_level', 'status'))
OR (table_name = 'aihr_knowledge_attach' AND column_name = 'category_id')
OR (table_name = 'aihr_knowledge_upload_item' AND column_name IN ('space_codes_json', 'source_attach_id'))
OR (table_name = 'aihr_org_snapshot' AND column_name = 'hire_date')
OR (table_name = 'aihr_candidate_material' AND column_name IN ('reviewer', 'reviewed_time'))
OR (table_name = 'aihr_interview_result' AND column_name IN ('questions_json', 'reviewed_score', 'review_status', 'reviewer', 'review_note', 'reviewed_time'))
OR (table_name = 'aihr_position_responsibility' AND column_name IN ('position_code', 'responsibility_text', 'sop_id', 'sop_code'))
OR (table_name = 'aihr_sop_applicability' AND column_name IN ('sop_id', 'sop_code', 'project_type', 'industry', 'org_scope'))
OR (table_name = 'aihr_learning_question' AND column_name IN ('exam_format', 'exam_options_json', 'exam_answers_json', 'exam_explanation', 'exam_enabled', 'review_status', 'review_note', 'reviewed_by', 'reviewed_time'))
OR (table_name = 'aihr_onboard_exam_question' AND column_name IN ('source_question_id', 'source_question_code', 'source_content_version', 'source_content_hash'))
OR (table_name = 'aihr_onboard_task' AND column_name IN ('ext_party_id', 'position_code', 'task_type', 'status', 'assign_by', 'material_type', 'material_attachment_id', 'material_title', 'material_version', 'progress_percent', 'completion_rule', 'exam_id', 'recurrence_type', 'recurrence_series_key', 'dispatch_request_key'))
OR (table_name = 'aihr_qualification_gate' AND column_name IN ('ext_party_id', 'position_code', 'cert_id', 'passed', 'valid_thru'))
OR (table_name = 'aihr_work_report' AND column_name IN ('request_key', 'request_hash'))
OR (table_name = 'aihr_broadcast_message' AND column_name IN ('publish_request_key', 'publish_request_hash', 'withdraw_reason', 'attachment_id'))
OR (table_name = 'aihr_memory_candidate' AND column_name IN ('work_date', 'source_snapshot_json'))
OR (table_name = 'aihr_assistant_capture' AND column_name IN ('work_date', 'source_snapshot_json', 'business_status'))
OR (table_name = 'aihr_knowledge_conversation' AND column_name IN ('project_code', 'broadcast_message_id'))
OR (table_name = 'aihr_agent_run' AND column_name IN ('run_id', 'tenant_id', 'client_key', 'user_id', 'conversation_id', 'context_version', 'intent', 'tool', 'status', 'source_type', 'duration_ms', 'error_code', 'result_ref', 'create_time'))
OR (table_name = 'aihr_daily_work_result' AND column_name = 'content_hash')
OR (table_name = 'aihr_web_search_provider' AND column_name IN ('agent_id', 'agent_version'))
)
ORDER BY table_name, column_name;
SELECT index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index) AS indexed_columns
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'aihr_knowledge_upload_item'
AND index_name = 'idx_aihr_upload_item_source'
GROUP BY index_name;
SELECT MIN(non_unique) AS non_unique,
GROUP_CONCAT(column_name ORDER BY seq_in_index) AS indexed_columns,
SUM(sub_part IS NOT NULL) AS prefix_columns
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'aihr_practice_assignment'
AND index_name IN ('uk_aihr_assignment_request', 'idx_aihr_assignment_recent_content')
GROUP BY index_name
ORDER BY index_name;
SELECT MIN(non_unique) AS non_unique,
GROUP_CONCAT(column_name ORDER BY seq_in_index) AS indexed_columns,
SUM(sub_part IS NOT NULL) AS prefix_columns
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'aihr_broadcast_message'
AND index_name = 'uk_aihr_broadcast_message_publish_request';
SELECT bucket_name, access_policy,
CASE WHEN COALESCE(access_key, '') <> '' AND COALESCE(secret_key, '') <> ''
THEN 'configured' ELSE 'missing' END AS credential_state
FROM sys_oss_config
WHERE tenant_id = '000000' AND config_key = 'personal-minio';
SELECT COUNT(*) AS built_in_prompt_templates
FROM aihr_prompt_template
WHERE tenant_id = '000000'
AND code IN ('summary_card', 'sop_answer', 'mentor_rewrite', 'review_annotation', 'prep_card', 'supervision_image_analysis_maintenance');
SELECT position, COUNT(*) AS scenario_count, SUM(enabled = 0) AS disabled_pending_review
FROM aihr_practice_scenario
WHERE tenant_id = '000000'
GROUP BY position
ORDER BY position;
SELECT question_type, COUNT(*) AS question_count, SUM(enabled = 0) AS disabled_pending_review
FROM aihr_learning_question
WHERE tenant_id = '000000'
GROUP BY question_type
ORDER BY question_type;
SELECT s.position,
COUNT(*) AS enabled_scenarios,
SUM(active_rubric.active_count = 1) AS scenarios_with_one_active_rubric
FROM aihr_practice_scenario s
LEFT JOIN (
SELECT scenario_code, COUNT(*) AS active_count
FROM aihr_practice_rubric
WHERE tenant_id = '000000' AND enabled = 1
GROUP BY scenario_code
) active_rubric ON active_rubric.scenario_code = s.scenario_code
WHERE s.tenant_id = '000000' AND s.enabled = 1
GROUP BY s.position
ORDER BY s.position;
SELECT d.dimension_code, COUNT(*) AS active_dimension_rows
FROM aihr_practice_rubric_dimension d
JOIN aihr_practice_rubric r ON r.tenant_id = d.tenant_id AND r.rubric_code = d.rubric_code
WHERE r.tenant_id = '000000' AND r.enabled = 1
GROUP BY d.dimension_code
ORDER BY d.dimension_code;
```
本批完整发布要求 `release-preflight` 的 `64/64` 必需表与列契约均存在,新增项包括 `aihr_practice_help_event`、`aihr_direct_feedback`、`aihr_broadcast_attachment`、`aihr_broadcast_message.attachment_id` 和 `aihr_agent_run`;资料重试还必须通过 `remote_media_reprocess_contract=1/1`,即 `source_attach_id` 与 `(tenant_id, source_attach_id, id)` 索引同时存在。此前 `57/57`、`62/62` 都只代表较窄的历史发布清单。2026-07-25 生产只读预检已达到 `64/64`,后端 JAR/AIHR 模块与发布源一致,并确认公司文件附件契约 `15/15` 及多岗位解读队列索引存在;这只证明迁移与产物已发布,不替代 Agent、训练或试点业务验收。Agent 审计字段应为 `14/14`,且表中不得出现问题、答案或附件列。工作上报幂等字段为 `2/2`,大喇叭发布/撤回审计字段为 `3/3`,其发布幂等索引为 `0 | tenant_id,published_by,publish_request_key | 0`,M1 必读/目标载荷字段契约为 `2/2`,两张目标表字段契约为 `16/16`,定向规则与对象快照唯一约束分别为 `4/4`、`3/3`,公司文件附件字段和消息绑定字段契约为 `15/15`,并应存在 `(insight_status,status,update_time)` 解读队列索引;公司消息追问会话字段为 `1/1`,工作助手新增字段为 `8/8`,会话证据字段为 `8/8`,专项批量/内容快照字段为 `8/8`,候选资料审核字段为 `2/2`,面试复核字段为 `6/6`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。私有资料配置只应返回 `ruoyi-personal`、私有策略和 `configured` 状态,不显示访问密钥。五个岗位各有 `20` 个场景,`daily/special` 各有 `100` 道题;其中 `14` 个未完成正式审核的高风险场景及其 `28` 道题保持禁用。岗位/SOP/任务/资格表及新记忆表为空是允许的;结构、索引和内置内容通过只证明 schema/seed 与发布产物一致,不证明员工直达反馈、文件提炼、多岗位解读、员工确认记忆、完整个人知识库或严格试点已经完成业务验收。
新增学习任务/题库迁移后,预检还必须确认 `aihr_exam_question_draw`、题库审核字段、试卷来源快照字段,以及任务材料/周期/幂等字段和唯一索引;这只证明结构可用,不代表题库内容已完成业务审核或正式试点验收。
## 迁移后应用回归
1. 重启 `wygj-aihr.service`,确认状态为 `active`。
2. 检查根站、`/h5/`、租户接口和员工首页 API 的 HTTP 与业务码均为 `200`。
3. 用正式测试账号完成员工端训练、主管复盘和管理端五岗位场景、每日/专项题库、Rubric 与任务运营检查;确认停用内容不会被下发,任务创建保留题目/参考答案版本快照,训练完成记录保留开始时 Rubric 快照。对初级或中级会话,主管可选确认下一训练层级;确认必须仅接受初级→中级或中级→高级,并同时在该复盘详情与员工画像中回显,且不得改变职级、薪酬、上岗或资格数据。不要使用开发固定验证码验证生产安全策略。
4. 用正式测试账号在“工作助手”选择当前项目并说“帮我记一下三栋 3203 需要保洁服务”;确认前 `aihr_assistant_capture` 不得新增。分别验证 `PRIVATE` 仅本人可召回、`COMPANY` 只显示 `PENDING` 且没有虚假送达回执;新开对话按房号查询必须只命中对应项目记录,双击确认不得重复写入。测试表达如不需长期保留,应在确认卡选择“暂不保存”,不要直接删除生产审计记录。
5. 在“今日工作成果”按员工 + 项目 + 日期重复生成两次,内容不变时版本哈希必须一致;员工访问团队结果、主管访问非授权项目必须被拒绝。生产无主管正式测试账号时必须明确记录未验收,不以本地 seed 代替。
6. 用员工测试账号进入“成果投稿”,确认其仍只承载优秀案例、操作视频、完整 SOP 和有用知识,不回退为日常事实表单。重复提交同一 `request_key` 只有一条 `PENDING` 记录;普通员工审核仍为 `403`,审核通过不自动写入案例、知识或积分表。
7. 由五通道业务负责人先书面指定主处理人和替补;后端与管理端发布完成后,`superadmin` 在 `/content/direct` 的“处理人配置”中只从当前租户合格候选人绑定,每通道至少 2 人。不得把 APP 用户、超级管理员、停用用户、跨租户用户或未经业务批准的账号绑定为真实收件人;配置接口必须回传并校验当前 `tenantId`,机器证据只记录角色人数而不记录人员身份。随后用员工账号分别向总裁、财务、人力、审计和运营频道提交;匿名反馈在处理端不得显示提交人,但员工本人仍可查看。分别用对应处理角色验证只能看到本频道且只能正式回复一次;`superadmin` 验证全频道兜底。
8. 用 `superadmin` 或 `hr_operator` 上传一个受支持文件,等待状态变为 `READY` 后随消息发布;员工详情必须显示摘要并能鉴权下载原文件,“问这条消息”应引用服务端提取内容。再验证失败文件不能发布、非在职或跨租户账号不能下载、撤回后不能下载或继续追问。
9. 用 `superadmin` 或 `hr_operator` 在当前租户发布同一 `requestId` 两次:同内容必须只生成一条消息和一个 v1 快照,改动正文后必须返回 `409`。撤回时提交非空原因,员工端随即不可见;重复撤回不得改写首个撤回人、时间或原因。再确认管理列表不跨租户返回消息。
10. 用正式移动端员工账号验证数字师傅 Agent:寒暄不得调用 RAG;本人待办/训练显示实时来源;普通图片不要求知识空间;现场制度问题只在授权空间补充 SOP;全网查询必须先返回同意卡;“记一下”确认前不落库。再检查 `aihr_agent_run` 只有主体、意图、工具、状态、来源和耗时等元数据,没有问题、答案或附件。生产无对应角色、媒体或撤权样本时必须记录未验收。
11. 完整包发布后运行 `RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh`;必须同时获得线上资源、AIHR 模块和 schema 成功结果。该检查不替代第 7—10 项的认证态业务闭环。
12. 迁移完成后再按同一正式时间窗运行:
```bash
AIHR_PILOT_TENANT_ID=000000 \
AIHR_PILOT_START_DATE=YYYY-MM-DD \
AIHR_PILOT_END_DATE=YYYY-MM-DD \
AIHR_PILOT_STRICT=true ./scripts/demo-check.sh
```
严格门禁中的校准、SOP 可用率、完训率、满意度、正式组织身份和案例数量仍必须来自真实业务数据;不允许用本 runbook 的内置 seed 或开发库样本替代。