164 lines
7.4 KiB
Bash
164 lines
7.4 KiB
Bash
#!/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'
|