#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" SCRIPT="$ROOT_DIR/scripts/verify-aug1-release-readiness.sh" PREFLIGHT="$ROOT_DIR/scripts/release-preflight.sh" DEV_SETUP="$ROOT_DIR/docs/DEV_SETUP.md" bash -n "$SCRIPT" bash -n "$PREFLIGHT" pending_output="$("$SCRIPT" --execute 2>&1 || true)" grep -Fq 'releaseStatus must be APPROVED' <<<"$pending_output" grep -Fq 'strict approval did not return exactly five scenario snapshots' <<<"$pending_output" test_tmp_base="${TMPDIR:-/tmp}" test_tmp="$(mktemp -d "$test_tmp_base/aug1-readiness-test.XXXXXX")" cleanup() { case "$test_tmp" in "$test_tmp_base"/aug1-readiness-test.*) rm -rf -- "$test_tmp" ;; *) echo "FAIL: unsafe test cleanup path: $test_tmp" >&2; exit 1 ;; esac } trap cleanup EXIT cp "$ROOT_DIR/docs/content-candidates/aug1-life-advisor-content-candidates-v0.1.json" \ "$test_tmp/aug1-life-advisor-content-candidates-v0.1.json" node - "$test_tmp/aug1-life-advisor-content-candidates-v0.1.json" \ "$test_tmp/approval.json" "$test_tmp/scenario-rows.tsv" <<'NODE' const fs = require('node:fs') const crypto = require('node:crypto') const [candidatePath, approvalPath, rowsPath] = process.argv.slice(2) const bytes = fs.readFileSync(candidatePath) const candidate = JSON.parse(bytes) const source = { sourceId: 'FORMAL-OPS-001', title: 'test-only formal operations standard', version: 'test-v1', effectiveDate: '2026-07-01', scope: 'test-only', sha256: 'a'.repeat(64), formalPolicy: true, } const scenarioApprovals = {} const rows = [] candidate.scenarios.forEach((item, index) => { const highRisk = item.proposedRiskLevel === '高风险' const version = `aug1-v${index + 1}` const hash = String(index + 1).repeat(64) scenarioApprovals[item.candidateId] = { status: 'APPROVED', scenarioCode: item.scenarioDraft.id, riskLevel: item.proposedRiskLevel, sourceRefs: ['FORMAL-OPS-001:section-1'], reviewedBy: `reviewer-${index + 1}`, reviewedAt: '2026-07-30T09:00:00+08:00', ...(highRisk ? { secondReviewedBy: `second-reviewer-${index + 1}`, secondReviewedAt: '2026-07-30T10:00:00+08:00', } : {}), businessEvidenceRef: `evidence/scenario-${index + 1}.pdf`, businessEvidenceSha256: 'c'.repeat(64), productionContentVersion: version, productionContentHash: hash, } rows.push([ item.scenarioDraft.id, version, hash, '1', '已发布', item.proposedRiskLevel, String(100 + index), '2026-07-30T09:00:00', highRisk ? String(200 + index) : '0', highRisk ? '2026-07-30T10:00:00' : '-', 'FORMAL-OPS-001:section-1', ].join('\t')) }) const policyQuestionApprovals = {} candidate.policyQuestionCandidates.forEach((item, index) => { policyQuestionApprovals[item.id] = { status: 'APPROVED', answerDisposition: item.category === 'NO_EVIDENCE' ? 'FORMAL_NO_EVIDENCE_BOUNDARY' : item.category === 'UNAUTHORIZED' ? 'FORMAL_UNAUTHORIZED_BOUNDARY' : 'FORMALLY_SOURCED', sourceRefs: ['FORMAL-OPS-001:section-2'], expectedBehavior: item.expectedBehavior, observedBehavior: 'test-only observed result', evidenceRef: `evidence/question-${index + 1}.json`, evidenceSha256: 'b'.repeat(64), reviewedBy: `question-reviewer-${index + 1}`, reviewedAt: '2026-07-30T11:00:00+08:00', } }) const signoffs = Object.fromEntries([ 'businessOwner', 'knowledgeOwner', 'trainingOwner', 'channelOwner', 'releaseOwner', ].map((role) => [role, { status: 'APPROVED', reviewedBy: `${role}-reviewer`, reviewedAt: '2026-07-30T12:00:00+08:00', }])) const channelApprovals = Object.fromEntries([ 'direct_president', 'direct_finance', 'direct_hr', 'direct_audit', 'direct_operations', ].map((role) => [role, { status: 'APPROVED', minimumActiveHandlers: 2, bindingEvidenceRef: `evidence/${role}-binding.json`, bindingEvidenceSha256: 'd'.repeat(64), positiveAccessEvidenceRef: `evidence/${role}-positive.json`, positiveAccessEvidenceSha256: 'e'.repeat(64), crossChannelDenialEvidenceRef: `evidence/${role}-denial.json`, crossChannelDenialEvidenceSha256: 'f'.repeat(64), singleReplyEvidenceRef: `evidence/${role}-reply.json`, singleReplyEvidenceSha256: '1'.repeat(64), reviewedBy: `${role}-reviewer`, reviewedAt: '2026-07-30T11:30:00+08:00', }])) const expectedApkSignerSha256 = '2'.repeat(64) fs.writeFileSync(approvalPath, JSON.stringify({ schemaVersion: '1.0', releaseTarget: '2026-08-01', tenantId: '000000', candidateFile: 'aug1-life-advisor-content-candidates-v0.1.json', candidateSha256: crypto.createHash('sha256').update(bytes).digest('hex'), expectedApkSignerSha256, releaseStatus: 'APPROVED', formalContentVersion: 'aug1-formal-v1', sourceRegistry: [source], scenarioApprovals, policyQuestionApprovals, channelApprovals, distributionApproval: { status: 'APPROVED', scope: 'CONTROLLED_INTERNAL_TEST', legalDecision: 'CONTROLLED_INTERNAL_TEST_EXCEPTION_ACCEPTED', signerDecision: 'DCLOUD_TEST_SIGNER_ACCEPTED_FOR_CONTROLLED_INTERNAL_TEST', signerSha256: expectedApkSignerSha256, distributionEvidenceRef: 'evidence/controlled-distribution.json', distributionEvidenceSha256: '3'.repeat(64), reviewedBy: 'distribution-reviewer', reviewedAt: '2026-07-30T12:30:00+08:00', }, signoffs, })) fs.writeFileSync(rowsPath, rows.join('\n') + '\n') NODE mock_bin="$test_tmp/mock-bin" mkdir -p "$mock_bin" cat > "$mock_bin/systemctl" <<'MOCK_SYSTEMCTL' #!/usr/bin/env bash set -euo pipefail printf '%s\n' "$MOCK_SERVICE_PID" MOCK_SYSTEMCTL cat > "$mock_bin/mysql" <<'MOCK_MYSQL' #!/usr/bin/env bash set -euo pipefail query="$(cat)" if [[ "$query" == *'COUNT(DISTINCT u.user_id)'* ]]; then handler_count="${MOCK_HANDLER_COUNT:-2}" printf 'direct_audit\t%s\n' "$handler_count" printf 'direct_finance\t%s\n' "$handler_count" printf 'direct_hr\t%s\n' "$handler_count" printf 'direct_operations\t%s\n' "$handler_count" printf 'direct_president\t%s\n' "$handler_count" elif [[ "$query" == *'FROM aihr_practice_scenario'* ]]; then cat "$MOCK_SCENARIO_ROWS" else echo 'FAIL: unexpected mock MySQL query' >&2 exit 90 fi MOCK_MYSQL cat > "$mock_bin/ssh" <<'MOCK_SSH' #!/usr/bin/env bash set -euo pipefail while (($#)); do if [[ "$1" == '--' ]]; then shift break fi shift done remote_script="$MOCK_REMOTE_SCRIPT" cat > "$remote_script" export MOCK_SERVICE_PID="$$" PATH="$MOCK_REMOTE_BIN:$PATH" bash "$remote_script" "$@" MOCK_SSH chmod +x "$mock_bin/systemctl" "$mock_bin/mysql" "$mock_bin/ssh" run_mock_readiness() { PATH="$mock_bin:$PATH" \ MOCK_REMOTE_BIN="$mock_bin" \ MOCK_REMOTE_SCRIPT="$test_tmp/remote.sh" \ MOCK_SCENARIO_ROWS="$test_tmp/scenario-rows.tsv" \ AIHR_SMS_DEV_FIXED_CODE='test-only-fixed-code' \ AIHR_SMS_PROD_FIXED_CODE_ENABLED="${AIHR_SMS_PROD_FIXED_CODE_ENABLED:-true}" \ AIHR_AUG1_APPROVAL_PATH="$test_tmp/approval.json" \ "$SCRIPT" --execute } ready_output="$(run_mock_readiness)" grep -Fq 'AUG1_FIXED_CODE_NO_REAL_SMS_MODE=PASS' <<<"$ready_output" grep -Fq 'AUG1_FORMAL_SCENARIOS=5/5' <<<"$ready_output" grep -Fq 'AUG1_CHANNEL_ACCEPTANCE=5/5' <<<"$ready_output" grep -Fq 'AUG1_DISTRIBUTION_APPROVAL=PASS' <<<"$ready_output" grep -Fq 'AUG1_RELEASE_READINESS=PASS' <<<"$ready_output" handler_failure="$( MOCK_HANDLER_COUNT=1 run_mock_readiness 2>&1 || true )" grep -Fq 'requires an active primary handler and backup; got 1/2' <<<"$handler_failure" fixed_mode_failure="$( AIHR_SMS_PROD_FIXED_CODE_ENABLED=false run_mock_readiness 2>&1 || true )" grep -Fq 'production fixed-code mode is not explicitly enabled' <<<"$fixed_mode_failure" grep -Fq 'AIHR_SMS_DEV_FIXED_CODE' "$SCRIPT" grep -Fq 'AIHR_SMS_PROD_FIXED_CODE_ENABLED' "$SCRIPT" grep -Fq 'AUG1_FIXED_CODE_NO_REAL_SMS_MODE=PASS' "$SCRIPT" grep -Fq 'COUNT(DISTINCT u.user_id)' "$SCRIPT" grep -Fq 'handler_count >= 2' "$SCRIPT" grep -Fq 'u.status = '\''0'\''' "$SCRIPT" grep -Fq 'u.del_flag = '\''0'\''' "$SCRIPT" grep -Fq 'review_status" == "已发布"' "$SCRIPT" grep -Fq 'second_reviewer_user_id" != "$reviewer_user_id"' "$SCRIPT" grep -Fq 'production content hash does not match the approved snapshot' "$SCRIPT" grep -Fq 'AUG1_FORMAL_SCENARIOS=5/5' "$SCRIPT" grep -Fq 'AUG1_FORMAL_POLICY_QUESTIONS=30/30' "$SCRIPT" grep -Fq 'AUG1_CHANNEL_ACCEPTANCE=5/5' "$SCRIPT" grep -Fq 'AUG1_DISTRIBUTION_APPROVAL=PASS' "$SCRIPT" if grep -Eiq '(^|[[:space:]])(insert|update|delete|replace|alter|drop|truncate)[[:space:]]' "$SCRIPT"; then echo 'FAIL: August 1 release readiness verifier contains a mutating SQL verb' >&2 exit 1 fi if grep -Fq '/resource/sms/code' "$SCRIPT" || grep -Fq '/auth/mobile/sms-login' "$SCRIPT"; then echo 'FAIL: final readiness verifier must inspect fixed-code state without requesting or consuming a code' >&2 exit 1 fi if grep -Eq 'echo[[:space:]].*(fixed_code|reviewedBy|user_id)' "$SCRIPT"; then echo 'FAIL: final readiness verifier may expose a code or identity' >&2 exit 1 fi grep -Fq 'verify_aug1_readiness="${RELEASE_VERIFY_AUG1_READINESS:-false}"' "$PREFLIGHT" grep -Fq 'verify-aug1-release-readiness.sh" --execute' "$PREFLIGHT" grep -Fq 'RELEASE_VERIFY_AUG1_READINESS=true' "$DEV_SETUP" echo 'PASS: August 1 final readiness gate is fixed-code-only, read-only, content-bound and handler-bound'