338 lines
40 KiB
Markdown
338 lines
40 KiB
Markdown
# 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_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_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_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_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/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 伪造脱敏完成或自动获批。完整迁移后仍须最后执行排序规则兼容迁移。
|
||
|
||
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 数据库。
|
||
|
||
## 迁移后只读核验
|
||
|
||
执行后只读检查结构和内置内容,不修改业务数据:
|
||
|
||
```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_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'))
|
||
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_onboard_task' AND column_name IN ('ext_party_id', 'position_code', 'task_type', 'status', 'assign_by'))
|
||
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 与发布产物一致,不证明员工直达反馈、文件提炼、多岗位解读、员工确认记忆、完整个人知识库或严格试点已经完成业务验收。
|
||
|
||
## 迁移后应用回归
|
||
|
||
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 或开发库样本替代。
|