247 lines
14 KiB
Bash
Executable File
247 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
SCRIPT="$ROOT_DIR/scripts/demo-check.sh"
|
|
|
|
assert_contains() {
|
|
local text="$1"
|
|
grep -Fq "$text" "$SCRIPT" || {
|
|
echo "FAIL: demo-check.sh missing: $text" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
assert_not_contains() {
|
|
local text="$1"
|
|
if grep -Fq "$text" "$SCRIPT"; then
|
|
echo "FAIL: demo-check.sh still contains unsafe match: $text" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
assert_contains 'AIHR_PILOT_START_DATE'
|
|
assert_contains 'AIHR_PILOT_END_DATE'
|
|
assert_contains 'AIHR_PILOT_TENANT_ID'
|
|
assert_contains 'AIHR_DEMO_CHECK_LIBRARY_ONLY'
|
|
assert_contains 'safe_org AS'
|
|
assert_contains 'HAVING SUM(s.session_count) >= 10'
|
|
assert_contains 'HAVING COUNT(DISTINCT ext_party_id) = 1'
|
|
assert_contains 'finished_time >='
|
|
assert_contains 'reviewed_time >='
|
|
assert_not_contains 'p.identity_key = o.ext_party_id OR p.identity_key = o.person_phone'
|
|
assert_not_contains 's.ext_party_id = o.ext_party_id OR s.ext_party_id = o.person_phone'
|
|
|
|
if (AIHR_DEMO_CHECK_LIBRARY_ONLY=true AIHR_PILOT_STRICT=true "$SCRIPT") >/tmp/wygj-pilot-library-strict.log 2>&1; then
|
|
echo "FAIL: library-only mode bypassed strict pilot checks" >&2
|
|
exit 1
|
|
fi
|
|
grep -Fq 'AIHR_DEMO_CHECK_LIBRARY_ONLY cannot bypass strict pilot checks' /tmp/wygj-pilot-library-strict.log || {
|
|
echo "FAIL: library-only strict guard is unclear" >&2
|
|
exit 1
|
|
}
|
|
|
|
if ! command -v docker >/dev/null || ! docker ps --format '{{.Names}}' | grep -qx 'wygj-mysql'; then
|
|
echo "SKIP: behavioral pilot gate test requires wygj-mysql"
|
|
exit 0
|
|
fi
|
|
|
|
export AIHR_DEMO_CHECK_LIBRARY_ONLY=true
|
|
# shellcheck source=../demo-check.sh
|
|
source "$SCRIPT"
|
|
|
|
unset AIHR_PILOT_DB
|
|
[[ "$(pilot_sql_scalar 'SELECT 1')" == "1" ]] || {
|
|
echo "FAIL: default ry-vue pilot database is unavailable" >&2
|
|
exit 1
|
|
}
|
|
|
|
TEST_DB="wygj_demo_check_test_$$"
|
|
cleanup() {
|
|
docker exec wygj-mysql mysql -uroot -proot -e "DROP DATABASE IF EXISTS \`$TEST_DB\`" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 <<SQL >/dev/null
|
|
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), scenario_type varchar(80), scenario_name varchar(120), enabled tinyint);
|
|
CREATE TABLE aihr_prompt_template (tenant_id varchar(20), code varchar(100), 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), title varchar(120), transcript text, summary varchar(1000),
|
|
media_oss_id bigint, media_url varchar(500), learning_points text, supervisor_comment 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,
|
|
dim_task_completion int, dim_response_timeliness int, response_latency_ms int, annotations_json text,
|
|
review_advice varchar(1000), incentive_point int, satisfaction_score tinyint, satisfaction_comment varchar(500)
|
|
);
|
|
CREATE TABLE aihr_practice_assignment (
|
|
tenant_id varchar(20), ext_party_id varchar(100), create_time datetime,
|
|
answer_text text, feedback varchar(500), correct tinyint, finished_time datetime, score int, score_mode varchar(30)
|
|
);
|
|
CREATE TABLE aihr_org_snapshot (
|
|
tenant_id varchar(20), employment_status varchar(20), ext_party_id varchar(100),
|
|
project_code varchar(50), person_phone varchar(30), hire_date date
|
|
);
|
|
CREATE TABLE aihr_practice_calibration (
|
|
id bigint auto_increment primary key, tenant_id varchar(20), session_id varchar(100),
|
|
original_score int, corrected_score int, create_time datetime
|
|
);
|
|
CREATE TABLE aihr_sop_answer_review (
|
|
tenant_id varchar(20), category varchar(100), status varchar(30), usable tinyint,
|
|
reviewed_time datetime, create_time datetime,
|
|
requester_ext_party_id varchar(100), prompt_version varchar(80)
|
|
);
|
|
|
|
INSERT INTO aihr_practice_scenario
|
|
SELECT '000000','生活顾问', CASE
|
|
WHEN n = 1 THEN '服务推介(增值)'
|
|
WHEN n = 2 THEN '日常服务'
|
|
ELSE '投诉处理'
|
|
END, CONCAT('scenario-', n), 1 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
|
|
) n;
|
|
INSERT INTO aihr_prompt_template (tenant_id, code, enabled) VALUES
|
|
('000000','summary_card',1),
|
|
('000000','sop_answer',1),
|
|
('000000','mentor_rewrite',1),
|
|
('000000','review_annotation',1),
|
|
('000000','prep_card',1),
|
|
('000000','supervision_image_analysis_maintenance',1);
|
|
INSERT INTO aihr_knowledge_attach
|
|
SELECT '000000','sop',2 FROM (
|
|
SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
|
|
) n;
|
|
INSERT INTO aihr_knowledge_attach VALUES
|
|
('000000','training',2),
|
|
('other','sop',2),
|
|
('000000','sop',1);
|
|
INSERT INTO aihr_case_record (tenant_id, status, title, transcript, summary)
|
|
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 (tenant_id, status, title, transcript, summary) VALUES
|
|
('000000','已转写','pending','',''),
|
|
('other','已入库','other','','');
|
|
INSERT INTO aihr_org_snapshot VALUES
|
|
('000000','active','A','P1','13900000001','2026-01-01'),
|
|
('000000','active','B','P1','13900000001','2026-01-01'),
|
|
('000000','active','C','P2','D','2026-01-01'),
|
|
('000000','active','D','P2','13900000004','2026-01-01'),
|
|
('000000','active','E','P3','13900000005','2026-01-01'),
|
|
('000000','active','F','P3','13900000006','2026-01-01');
|
|
INSERT INTO aihr_practice_assignment (tenant_id, ext_party_id, create_time) VALUES
|
|
('000000','D','2026-07-08 08:00:00'),
|
|
('000000','13900000001','2026-07-08 08:00:00'),
|
|
('000000','E','2026-07-08 08:00:00'),
|
|
('000000','13900000005','2026-07-08 08:00:00'),
|
|
('000000','F','2026-07-08 08:00:00');
|
|
INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time)
|
|
SELECT '000000', CONCAT('e-', n), CASE WHEN n <= 5 THEN 'E' ELSE '13900000005' END, 'mobile', '2026-07-08 09:00:00'
|
|
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
|
|
) n;
|
|
INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time)
|
|
SELECT '000000', CONCAT('f-', n), 'F','mobile','2026-07-08 10:00:00' 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
|
|
) n;
|
|
INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time)
|
|
VALUES
|
|
('000000','e-out','E','mobile','2026-07-06 10:00:00'),
|
|
('000000','unsafe-inside','D','mobile','2026-07-08 10:00:00');
|
|
UPDATE aihr_practice_session SET satisfaction_score = 5 WHERE finished_time >= '2026-07-07 00:00:00';
|
|
INSERT INTO aihr_practice_calibration (tenant_id,session_id,original_score,corrected_score,create_time) VALUES
|
|
('000000','f-1',80,82,'2026-07-08 11:00:00'),
|
|
('000000','unsafe-inside',80,40,'2026-07-08 11:00:00'),
|
|
('000000','e-out',80,40,'2026-07-06 11:00:00');
|
|
INSERT INTO aihr_sop_answer_review
|
|
(tenant_id, category, status, usable, reviewed_time, create_time, requester_ext_party_id, prompt_version)
|
|
VALUES
|
|
('000000','sop','已评审',1,'2026-07-08 12:00:00','2026-07-08 11:00:00','E','v1'),
|
|
('000000','sop','已评审',0,'2026-07-06 12:00:00','2026-07-06 11:00:00','E','v1'),
|
|
('000000','sop','待评审',NULL,NULL,'2026-07-08 13:00:00','E','v1'),
|
|
('000000','training','已评审',0,'2026-07-08 12:30:00','2026-07-08 11:30:00','E','v1'),
|
|
('000000','training','待评审',NULL,NULL,'2026-07-08 13:00:00','E','v1'),
|
|
('000000','training','待评审',NULL,NULL,'2026-07-06 13:00:00','E','v1');
|
|
SQL
|
|
|
|
export AIHR_PILOT_DB="$TEST_DB"
|
|
pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
|
|
[[ "$pilot_output" == *'ten_sessions=1/5'* ]] || { echo "FAIL: 9/10 boundary or raw window count: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'catalog_schema=1/3'* ]] || { echo "FAIL: missing catalog schema was not visible: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'core_flow=service_promotion:1 daily_service:1'* ]] || { echo "FAIL: BRD core-flow coverage counts: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'org_identity=2/6'* ]] || { echo "FAIL: unsafe org identities were not excluded: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'org_pilot=2/20'* ]] || { echo "FAIL: collision or dual-key identity was miscounted: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'projects=1/1-2'* ]] || { echo "FAIL: safe pilot project count: $pilot_output" >&2; exit 1; }
|
|
[[ "$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" == *'case_media_schema=2/2'* ]] || { echo "FAIL: case media schema gate: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'case_learning_schema=2/2'* ]] || { echo "FAIL: case learning schema gate: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'practice_session_schema=8/8 daily_drill_schema=6/6'* ]] || { echo "FAIL: practice schema gates: $pilot_output" >&2; exit 1; }
|
|
[[ "$pilot_output" == *'prompt_templates=6/6'* ]] || { echo "FAIL: built-in prompt template 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; }
|
|
|
|
override_output="$(AIHR_PILOT_TENANT_ID=other AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
|
|
[[ "$override_output" == *'scenarios=0/12'* ]] || { echo "FAIL: pilot tenant override was ignored: $override_output" >&2; exit 1; }
|
|
|
|
pilot_sql_scalar "INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time, satisfaction_score) VALUES ('000000','e-10','13900000005','mobile','2026-07-09 09:00:00',5)" >/dev/null
|
|
pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
|
|
[[ "$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; }
|
|
|
|
pilot_sql_scalar "UPDATE aihr_case_record SET transcript = '已完成回访', learning_points = '[\"手机号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 learning point PII was not detected: $pilot_output" >&2; exit 1; }
|
|
|
|
pilot_sql_scalar "UPDATE aihr_case_record SET learning_points = '[\"已完成回访\"]', supervisor_comment = '请联系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 supervisor comment 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
|
|
fi
|
|
grep -Fq 'strict pilot check requires AIHR_PILOT_START_DATE and AIHR_PILOT_END_DATE' /tmp/wygj-pilot-strict-test.log || {
|
|
echo "FAIL: strict mode missing-date error is unclear" >&2
|
|
exit 1
|
|
}
|
|
|
|
pilot_sql_scalar "ALTER TABLE aihr_case_record DROP COLUMN learning_points, DROP COLUMN supervisor_comment" >/dev/null
|
|
old_case_schema_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
|
|
[[ "$old_case_schema_output" == *'case_learning_schema=0/2'* ]] || {
|
|
echo "FAIL: old case schema did not remain inspectable: $old_case_schema_output" >&2
|
|
exit 1
|
|
}
|
|
[[ "$old_case_schema_output" == *'case_unmasked=0'* ]] || {
|
|
echo "FAIL: old case schema PII fallback failed: $old_case_schema_output" >&2
|
|
exit 1
|
|
}
|
|
|
|
pilot_sql_scalar "DROP TABLE aihr_case_record" >/dev/null
|
|
missing_case_table_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
|
|
[[ "$missing_case_table_output" == *'stored_cases=0/20 case_unmasked=0'* ]] || {
|
|
echo "FAIL: missing case table did not remain inspectable: $missing_case_table_output" >&2
|
|
exit 1
|
|
}
|
|
[[ "$missing_case_table_output" == *'case_media_schema=0/2 case_learning_schema=0/2'* ]] || {
|
|
echo "FAIL: missing case table schema gate was not visible: $missing_case_table_output" >&2
|
|
exit 1
|
|
}
|
|
|
|
echo "PASS: demo pilot gates use a bounded window and unique formal identities"
|