fix(gates): detect unmasked case pii
This commit is contained in:
@@ -48,8 +48,8 @@
|
||||
|
||||
### 2026-07-14 增量:BRD 内容数量门槛
|
||||
|
||||
- `scripts/demo-check.sh` 的严格模式现在额外检查已解析的住宅 SOP 文档数量 `>=5`(`aihr_knowledge_attach.type='sop' AND status=2`)和已入库案例数量 `>=20`(`aihr_case_record.status='已入库'`)。
|
||||
- 这两个查询只证明数量,不证明 5 个核心流程覆盖、案例脱敏质量、每周每项目产出或视频学习内容;这些仍需业务内容复核和正式窗口数据。
|
||||
- `scripts/demo-check.sh` 的严格模式现在额外检查已解析的住宅 SOP 文档数量 `>=5`(`aihr_knowledge_attach.type='sop' AND status=2`)、已入库案例数量 `>=20`(`aihr_case_record.status='已入库'`)和案例文本中已知手机号/邮箱/房号模式为 0。
|
||||
- 这些查询只证明数量和已知模式脱敏,不证明 5 个核心流程覆盖、姓名等未结构化 PII、每周每项目产出或视频学习内容;这些仍需业务内容复核和正式窗口数据。
|
||||
- `scripts/tests/demo-check-pilot-gates.test.sh` 已补充跨租户、错误状态和非 SOP 文档样本,防止严格门禁用错误数据刷过。
|
||||
|
||||
### P2/P3:明确暂缓,不应混入当前修复
|
||||
|
||||
@@ -79,7 +79,7 @@ check_pilot_samples() {
|
||||
[[ "$(pilot_sql_scalar "SELECT DATE('$pilot_end') IS NOT NULL")" == "1" ]] || fail "invalid AIHR_PILOT_END_DATE: $pilot_end"
|
||||
[[ "$pilot_start" > "$pilot_end" ]] && fail "pilot start date must not be after end date"
|
||||
|
||||
local scenario_count completed_people pilot_people org_active org_phone_linked org_pilot_people org_pilot_projects org_completed_people audio_table_count prompt_version_schema_count sop_document_count case_stored_count
|
||||
local scenario_count completed_people pilot_people org_active org_phone_linked org_pilot_people org_pilot_projects org_completed_people audio_table_count prompt_version_schema_count sop_document_count case_stored_count case_unmasked_count
|
||||
local calibration_stats calibration_count calibration_matched sop_total sop_usable sop_pending satisfaction_count satisfaction_average
|
||||
local org_identity_cte="
|
||||
WITH active_org AS (
|
||||
@@ -126,6 +126,7 @@ check_pilot_samples() {
|
||||
prompt_version_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'aihr_sop_answer_review' AND column_name = 'prompt_version'")"
|
||||
sop_document_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_knowledge_attach WHERE tenant_id = '000000' AND type = 'sop' AND status = 2")"
|
||||
case_stored_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '000000' AND status = '已入库'")"
|
||||
case_unmasked_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_case_record WHERE tenant_id = '000000' AND status = '已入库' AND CONCAT_WS(' ', title, transcript, summary) REGEXP '1[3-9][0-9]{9}|[[:alnum:]._%+-]+@[[:alnum:].-]+\\.[[:alpha:]]{2,}|[0-9]{1,2}(栋|幢|号楼|单元)[0-9]{3,4}|[0-9]{1,2}[--][0-9]{3,4}'")"
|
||||
completed_people="$(pilot_sql_scalar "
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
@@ -267,12 +268,13 @@ check_pilot_samples() {
|
||||
AND s.finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
|
||||
)
|
||||
SELECT COALESCE(ROUND(AVG(satisfaction_score), 1), 0) FROM formal_sessions WHERE satisfaction_score IS NOT NULL")"
|
||||
echo "OK: pilot gates window=${pilot_start}..${pilot_end} scenarios=${scenario_count}/12 sop_docs=${sop_document_count}/5 stored_cases=${case_stored_count}/20 ten_sessions=${completed_people}/${pilot_people} org_login=${org_phone_linked}/${org_active} org_pilot=${org_pilot_people}/20 projects=${org_pilot_projects}/1-2 org_ten_sessions=${org_completed_people}/${org_pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending} satisfaction=${satisfaction_average}/5 responses=${satisfaction_count} audio_schema=${audio_table_count} prompt_version_schema=${prompt_version_schema_count}"
|
||||
echo "OK: pilot gates window=${pilot_start}..${pilot_end} scenarios=${scenario_count}/12 sop_docs=${sop_document_count}/5 stored_cases=${case_stored_count}/20 case_unmasked=${case_unmasked_count} ten_sessions=${completed_people}/${pilot_people} org_login=${org_phone_linked}/${org_active} org_pilot=${org_pilot_people}/20 projects=${org_pilot_projects}/1-2 org_ten_sessions=${org_completed_people}/${org_pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending} satisfaction=${satisfaction_average}/5 responses=${satisfaction_count} audio_schema=${audio_table_count} prompt_version_schema=${prompt_version_schema_count}"
|
||||
|
||||
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || {
|
||||
(( scenario_count >= 12 )) || fail "pilot enabled scenarios ${scenario_count}/12"
|
||||
(( sop_document_count >= 5 )) || fail "pilot residential SOP documents ${sop_document_count}/5"
|
||||
(( case_stored_count >= 20 )) || fail "pilot stored cases ${case_stored_count}/20"
|
||||
(( case_unmasked_count == 0 )) || fail "pilot stored cases contain unmasked known PII (${case_unmasked_count})"
|
||||
(( audio_table_count == 1 )) || fail "pilot audio schema unavailable (aihr_practice_audio missing)"
|
||||
(( prompt_version_schema_count == 1 )) || fail "pilot RAG traceability schema unavailable (aihr_sop_answer_review.prompt_version missing)"
|
||||
(( org_phone_linked >= 20 )) || fail "pilot mobile-login org members ${org_phone_linked}/20 (active org ${org_active})"
|
||||
|
||||
@@ -57,7 +57,9 @@ CREATE DATABASE \`$TEST_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
USE \`$TEST_DB\`;
|
||||
CREATE TABLE aihr_practice_scenario (tenant_id varchar(20), position varchar(80), enabled tinyint);
|
||||
CREATE TABLE aihr_knowledge_attach (tenant_id varchar(20), type varchar(50), status tinyint);
|
||||
CREATE TABLE aihr_case_record (tenant_id varchar(20), status varchar(30));
|
||||
CREATE TABLE aihr_case_record (
|
||||
tenant_id varchar(20), status varchar(30), title varchar(120), transcript text, summary varchar(1000)
|
||||
);
|
||||
CREATE TABLE aihr_practice_session (tenant_id varchar(20), session_id varchar(100), ext_party_id varchar(100), mode varchar(30), finished_time datetime, satisfaction_score tinyint);
|
||||
CREATE TABLE aihr_practice_assignment (tenant_id varchar(20), ext_party_id varchar(100), create_time datetime);
|
||||
CREATE TABLE aihr_org_snapshot (
|
||||
@@ -87,15 +89,15 @@ INSERT INTO aihr_knowledge_attach VALUES
|
||||
('other','sop',2),
|
||||
('000000','sop',1);
|
||||
INSERT INTO aihr_case_record
|
||||
SELECT '000000','已入库' FROM (
|
||||
SELECT '000000','已入库', CONCAT('case-', n), '已按流程回访,手机号1390****1111', '已完成' FROM (
|
||||
SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
|
||||
UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10
|
||||
UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
|
||||
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL SELECT 20
|
||||
) n;
|
||||
INSERT INTO aihr_case_record VALUES
|
||||
('000000','已转写'),
|
||||
('other','已入库');
|
||||
('000000','已转写','pending','',''),
|
||||
('other','已入库','other','','');
|
||||
INSERT INTO aihr_org_snapshot VALUES
|
||||
('000000','active','A','P1','13900000001'),
|
||||
('000000','active','B','P1','13900000001'),
|
||||
@@ -145,6 +147,7 @@ pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10
|
||||
[[ "$pilot_output" == *'org_ten_sessions=1/2'* ]] || { echo "FAIL: formal 9/10 boundary: $pilot_output" >&2; exit 1; }
|
||||
[[ "$pilot_output" == *'calibration=1/20 consistency=1/1'* ]] || { echo "FAIL: calibration window: $pilot_output" >&2; exit 1; }
|
||||
[[ "$pilot_output" == *'sop_docs=5/5 stored_cases=20/20'* ]] || { echo "FAIL: BRD content quantity gates: $pilot_output" >&2; exit 1; }
|
||||
[[ "$pilot_output" == *'case_unmasked=0'* ]] || { echo "FAIL: redacted case content gate: $pilot_output" >&2; exit 1; }
|
||||
[[ "$pilot_output" == *'sop_usable=1/1 sop_pending=1'* ]] || { echo "FAIL: SOP window: $pilot_output" >&2; exit 1; }
|
||||
[[ "$pilot_output" == *'satisfaction=5.0/5 responses=19'* ]] || { echo "FAIL: safe satisfaction identity scope: $pilot_output" >&2; exit 1; }
|
||||
|
||||
@@ -153,6 +156,10 @@ pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10
|
||||
[[ "$pilot_output" == *'org_ten_sessions=2/2'* ]] || { echo "FAIL: formal 10/10 boundary: $pilot_output" >&2; exit 1; }
|
||||
[[ "$pilot_output" == *'satisfaction=5.0/5 responses=20'* ]] || { echo "FAIL: formal satisfaction scope after window update: $pilot_output" >&2; exit 1; }
|
||||
|
||||
pilot_sql_scalar "UPDATE aihr_case_record SET transcript = '手机号13900000000' WHERE tenant_id = '000000' AND status = '已入库' LIMIT 1" >/dev/null
|
||||
pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
|
||||
[[ "$pilot_output" == *'case_unmasked=1'* ]] || { echo "FAIL: unmasked case PII was not detected: $pilot_output" >&2; exit 1; }
|
||||
|
||||
if (AIHR_PILOT_STRICT=true AIHR_PILOT_START_DATE= AIHR_PILOT_END_DATE= check_pilot_samples) >/tmp/wygj-pilot-strict-test.log 2>&1; then
|
||||
echo "FAIL: strict mode accepted an implicit date window" >&2
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user