Files
prop-ai-hr/docs/BRD_PRODUCTION_MIGRATION_RUNBOOK.md
T
admin 09278d40c6 merge: integrate broadcast question correction
# Conflicts:
#	docs/BRD_PRODUCTION_MIGRATION_RUNBOOK.md
2026-07-25 00:13:49 +08:00

241 lines
25 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-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 的数字师傅 Agent、`aihr_agent_run` 和对应移动端改造当前仅是本地发布候选;下文列入迁移顺序是为了下一次完整包发布,不代表生产已存在或已验收。
## 发布前置条件
1. 确认发布窗口、DB 备份和回滚负责人;迁移脚本包含 `ALTER TABLE`,不能在无备份状态执行。
2. 发布前先执行 `scripts/release-preflight.sh` 的本地只读检查。完整包发布后,必须按 `docs/DEV_SETUP.md` 使用 `RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh` 核对线上静态资源、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_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_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_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/aihr_personal_knowledge_mysql8.sql
```
顺序原因:场景补充依赖三张场景/Rubric 基础表;五维迁移依赖场景和 Rubric;五岗位题库之后的内容运营迁移为每个场景补齐唯一的初始 Rubric、五个稳定评分维度与训练时评分规则快照列,并只修正仍处于待训练状态的旧每日题场景编号,不重写已完成记录。岗位/SOP/任务/资格迁移只补正式数据契约,不写业务行,也不代表岗位适用范围、任务规则或资格标准已经获得 HR 确认。住宅 SOP 和 Prompt 迁移只补内置内容,不代表内容已完成业务复核。知识会话迁移只新增短期上下文表,不生成业务对话。排序规则兼容迁移先对齐本批发布表,全量排序规则迁移再统一历史 `aihr_*` 表;两者都读取目标库 `aihr_knowledge_info.tenant_id` 的实际排序规则。服务记忆迁移创建候选、统一采集、兼容项目记录和版本留痕四张表;工作上报幂等迁移随后只补原表列与唯一索引;知识分类迁移新增空间内分类表及附件成员的可空 `category_id`,不迁移、删除或扩大任何空间授权与检索范围;银城大喇叭 M0 迁移创建消息、阅读和不可变 v1 快照三表,为已存在消息补一次快照,并补发布请求键/载荷哈希、撤回原因及 `(tenant_id,published_by,publish_request_key)` 唯一约束。紧接着 M1 定向迁移补充必读与目标载荷字段,以及规则快照、命中对象快照两表;它只决定提醒/必读对象,不改变全租户公开频道的可见性。随后,消息追问迁移只给短期会话增加可空 `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'))
OR (table_name = 'aihr_practice_scenario' AND column_name IN ('content_version', 'content_hash'))
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 = 'space_codes_json')
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 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_direct_feedback`、`aihr_broadcast_attachment`、`aihr_broadcast_message.attachment_id` 和 `aihr_agent_run`。此前 `57/57`、`62/62` 都只代表较窄的历史发布清单。后续线上只读复核已确认 M1 的目标表和必读字段,但当前线上不存在 `aihr_practice_help_event`,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`,公司消息追问会话字段为 `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` 验证全频道兜底。
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 或开发库样本替代。