#!/usr/bin/env bash set -euo pipefail # This closed policy check intentionally reports only labels and token counts. readonly ROOT='/home/donghyeon/workspace/platform' readonly CENTRAL_DOCS='/home/donghyeon/workspace/docs/platform' readonly MASTER="$CENTRAL_DOCS/plans/2026-07-31-observability-phase4.md" readonly ACCESS="$CENTRAL_DOCS/plans/2026-07-31-observability-access-alerting.md" readonly MANUAL="$ROOT/bootstrap/manual/phase4-observability-access.md" readonly WORKLOG="$CENTRAL_DOCS/runbooks/2026-08-13-observability-phase4-resume-worklog.md" readonly HISTORICAL_PLAN="$CENTRAL_DOCS/plans/2026-08-11-gitea-metrics-checkpoint.md" readonly HISTORICAL_DESIGN="$CENTRAL_DOCS/plans/2026-08-11-gitea-metrics-checkpoint-design.md" failures=0 fail() { printf 'FAIL: %s\n' "$1" >&2 failures=$((failures + 1)) } require_file() { [[ -f "$1" && ! -L "$1" ]] || fail "$2 is not a regular file" } count_token() { local file=$1 token=$2 grep -Foc -- "$token" "$file" || true } require_count() { local file=$1 label=$2 token=$3 expected=$4 actual actual="$(count_token "$file" "$token")" [[ "$actual" == "$expected" ]] || fail "$label token_count=$actual expected=$expected" } require_hash() { local file=$1 label=$2 expected=$3 actual actual="$(sha256sum -- "$file" | awk '{print $1}')" [[ "$actual" == "$expected" ]] || fail "$label sha256=$actual expected=$expected" } require_execution_relationships() { python3 - "$ACCESS" "$MANUAL" <<'PY' import pathlib import sys access = pathlib.Path(sys.argv[1]).read_text(encoding='utf-8') manual = pathlib.Path(sys.argv[2]).read_text(encoding='utf-8') def commands(text): result = [] lines = text.splitlines() index = 0 while index < len(lines): line = lines[index] if line.lstrip().startswith('bash scripts/bootstrap/create-observability-secrets.sh'): start = index parts = [line.strip()] while parts[-1].rstrip().endswith('\\') and index + 1 < len(lines): index += 1 parts.append(lines[index].strip()) result.append((start, ' '.join(parts))) index += 1 return result def slack_execute(items): return [item for item in items if '--execute' in item[1] and '--slack-webhook' in item[1]] def checker_after_count(text, command_index): return sum( index > command_index and '--check-slack-deployment-evidence' in line for index, line in enumerate(text.splitlines()) ) failures = 0 def require(label, actual, expected): global failures if actual != expected: print(f'FAIL: {label} count={actual} expected={expected}') failures += 1 hostile = '''padding padding padding padding padding padding padding padding padding padding padding padding --check-slack-deployment-evidence bash scripts/bootstrap/create-observability-secrets.sh --execute --slack-webhook --accept-no-off-host-slack-recovery ''' hostile_slack = slack_execute(commands(hostile)) require('hostile checker-before-Slack ordering', checker_after_count(hostile, hostile_slack[0][0]), 0) step = '- [ ] **Step 3: 실행 순서를 manual 문서에 고정한다**' start = access.find(step) if start < 0: require('authoritative access cutover block', 0, 1) block = '' else: fence_start = access.find(' ```bash', start) fence_end = access.find(' ```', fence_start + len(' ```bash')) block = access[fence_start:fence_end] if fence_start >= 0 and fence_end >= 0 else '' require('authoritative access cutover block', int(bool(block)), 1) access_slack = slack_execute(commands(block)) require('authoritative access Slack execute command', len(access_slack), 1) if len(access_slack) == 1: require('authoritative access Slack risk flag', int('--accept-no-off-host-slack-recovery' in access_slack[0][1]), 1) manual_slack = slack_execute(commands(manual)) require('manual Slack execute command', len(manual_slack), 1) if len(manual_slack) == 1: command_index, command = manual_slack[0] require('manual Slack risk flag', int('--accept-no-off-host-slack-recovery' in command), 1) require('manual deployment checker after Slack bootstrap', checker_after_count(manual, command_index), 1) sys.exit(1 if failures else 0) PY } require_file "$MASTER" master require_file "$ACCESS" access require_file "$MANUAL" manual require_file "$WORKLOG" worklog require_file "$HISTORICAL_PLAN" historical_plan require_file "$HISTORICAL_DESIGN" historical_design if (( failures == 0 )); then require_count "$MASTER" 'master deployment gate' '--check-slack-deployment-evidence' 1 require_count "$ACCESS" 'access deployment gate' '--check-slack-deployment-evidence' 2 require_count "$ACCESS" 'access strict recovery gate' '--check-slack-recovery-evidence' 1 require_count "$MANUAL" 'manual deployment gate' '--check-slack-deployment-evidence' 1 require_count "$MANUAL" 'manual strict recovery gate' '--check-slack-recovery-evidence' 1 require_count "$MASTER" 'master strict DR-only wording' 'off-host Slack DR-complete 판정에만 사용' 1 require_count "$ACCESS" 'access strict DR-only wording' 'operational apply를 허용하지 않는다' 1 require_count "$MANUAL" 'manual risk-path DR-deferred wording' '이 경로는 DR-complete을 주장하지 않습니다' 1 require_count "$MANUAL" 'manual risk flag' '--accept-no-off-host-slack-recovery' 1 require_count "$MANUAL" 'manual exact risk confirmation' 'ACCEPT NO OFF-HOST SLACK RECOVERY default' 1 require_count "$MANUAL" 'manual kubectl prohibition' 'kubectl로 Secret을 수동 생성하지 않습니다' 1 require_count "$MANUAL" 'manual false recovery prohibition' '거짓 `RECOVERY SLACK default` 확인을 입력하지 않습니다' 1 require_count "$MANUAL" 'manual two deployment-gate calls' '두 번 호출' 1 require_count "$MANUAL" 'manual v2 acceptance schema' 'platform-observability-rules-alerts-v2' 1 require_count "$MANUAL" 'manual bare risk ledger value' '`RISK_ACCEPTED`' 1 require_count "$MANUAL" 'manual bare recovery ledger value' '`RECOVERY`' 1 require_count "$ACCESS" 'access risk flag order' '--accept-no-off-host-slack-recovery' 3 require_count "$MASTER" 'master operational acceptance status' 'Phase 4 operational acceptance: complete with active Slack DR exception' 1 require_count "$MASTER" 'master deferred DR status' 'Slack off-host disaster recovery: deferred / not complete' 1 require_count "$ACCESS" 'access operational acceptance status' 'Phase 4 operational acceptance: complete with active Slack DR exception' 1 require_count "$ACCESS" 'access deferred DR status' 'Slack off-host disaster recovery: deferred / not complete' 1 require_count "$WORKLOG" 'worklog user decision' 'Slack risk acceptance decision' 1 require_count "$WORKLOG" 'worklog KeePass deferred' 'KeePass work remains deferred' 1 require_count "$WORKLOG" 'worklog absent Slack Secret' 'alertmanager-slack-webhook Secret remains absent' 1 require_hash "$HISTORICAL_PLAN" 'historical plan byte preservation' '10ccc38835928304bd631a60680ecc8e6c1d565d51cbe7047169eb609aa38ce5' require_hash "$HISTORICAL_DESIGN" 'historical design byte preservation' 'a9fd69e0241e5b56c5328076648a8ecf4a6f71d416aae7d53453ce7ff969249d' if ! require_execution_relationships; then failures=$((failures + 1)) fi fi if [[ -d "$ROOT/docs" ]]; then fail 'platform docs boundary token_count=1 expected=0' fi if (( failures != 0 )); then exit 1 fi printf 'PASS: observability Slack risk policy token_counts=exact\n'