Files
hanarang-rails/.plans/design/skill-enforcement.md
이랑이 bac114d469 docs(plans): Sprint 000 — 전체 플랜 문서 세트 작성
- CLAUDE.md 를 .claude/rules/ 4파일로 분할 (project/stack/principles/workflow)
- Plans.md 루트 인덱스 (스프린트 목차 + 참조만)
- hooks/ pre/post-tool.sh 스켈레톤 (no-op, Sprint 002에서 구현)
- .plans/OVERVIEW.md — 목표/범위/성공기준 8개
- .plans/failure-audit.md — F1~F6 실패 감사 (증거 기반)
- .plans/design/ 6개 문서:
  * state-machine.md (XState v5 FSM 설계)
  * sprint-contract.md (Zod schema + validator)
  * skill-enforcement.md (4계층 방어)
  * handoff.md (상태 전이 기반 자매 통신)
  * retry-policy.md (backoff + escalation)
  * qa-template.md (체크리스트 runtime)
- .plans/sprints/ 8개 스프린트 명세 (SPRINT-000~007)
- .plans/migration/from-hanarang-harness.md (자산 매트릭스 + 단계별 가이드)

총 17개 문서, 약 2146 lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:36:49 +09:00

5.0 KiB

Design — Skill Enforcement

처방 대상: F1 (하네스 skill bypass)

문제

기존 hanarang-harness 에서 나랑이가 "worker 스폰해서 바로 시작한다" 라며 skill 진입을 건너뛰었다. skill 이 권고 수준이라 강제력이 없었다. 결과적으로 파이프라인 일관성이 무너졌다.

해결 전략 — 다층 방어

  1. Layer 1 — 시스템 프롬프트 강제문 (약한 방어)

    • 자매 시스템 프롬프트에 "반드시 /rails 스킬을 거쳐야 한다" 명시
    • LLM 지시 준수 기대치에 의존 — 약함
  2. Layer 2 — Pre-Tool Hook (중간 방어)

    • 자매가 Write / Edit / Bash 를 호출하기 직전
    • 현재 세션이 rails-skill-context 를 세팅했는지 확인
    • 세팅 안 됐으면 exit 2 로 차단하고 "먼저 /rails 를 호출하세요" 메시지
  3. Layer 3 — Post-Tool Hook (강한 방어)

    • 자매가 작업 결과를 커밋한 후
    • .rails/skill-trace.jsonl 을 확인해 파이프라인이 skill 경로를 탔는지 감사
    • 우회 감지 시 작업을 자동 revert + escalation
  4. Layer 4 — Contract Validator (최종 방어)

    • Sprint Contract 의 DoD 에 skill-path-taken 체크 포함
    • contract validator 가 .rails/skill-trace.jsonl 을 읽어 검증
    • 우회한 경우 ABORT_PRECHECK 반환

구현 — Layer 2/3 상세

Pre-Tool Hook 로직

#!/usr/bin/env bash
# hooks/pre-tool.sh
set -euo pipefail

# stdin 에서 event JSON 읽기
EVENT=$(cat)

# 이 이벤트가 Write / Edit / Bash 인지 확인
TOOL=$(echo "$EVENT" | jq -r '.tool_name // empty')
case "$TOOL" in
  Write|Edit|Bash) ;;
  *) exit 0 ;;
esac

# 작업 디렉토리 추출
CWD="${CLAUDE_PROJECT_DIR:-$(pwd)}"

# skill context 파일 확인
SKILL_CTX="$CWD/.rails/skill-context.json"
if [ ! -f "$SKILL_CTX" ]; then
  echo "[rails-enforce] Skill context missing. /rails 스킬을 먼저 호출하세요." >&2
  exit 2   # 차단
fi

# skill context 가 유효한지 (최근 N초 이내) 확인
AGE=$(jq -r '.ageSeconds // 999' "$SKILL_CTX")
if [ "$AGE" -gt 300 ]; then
  echo "[rails-enforce] Skill context stale (>${AGE}s). 세션을 재시작하세요." >&2
  exit 2
fi

exit 0

Post-Tool Hook 로직

#!/usr/bin/env bash
# hooks/post-tool.sh
set -euo pipefail

EVENT=$(cat)
CWD="${CLAUDE_PROJECT_DIR:-$(pwd)}"
TRACE="$CWD/.rails/skill-trace.jsonl"

# 이 도구 사용을 trace 에 append
mkdir -p "$(dirname "$TRACE")"
echo "$EVENT" | jq -c \
  '{ ts: now, tool: .tool_name, cwd: env.PWD, session_id: env.CLAUDE_SESSION_ID }' \
  >> "$TRACE"

exit 0

Skill Context 구조

/rails <subcommand> 슬래시 커맨드 진입 시 skill 이 생성:

{
  "skillName": "rails",
  "subcommand": "plan",
  "sprintId": "SPRINT-001",
  "contractId": "01HW0XYZ...",
  "sessionId": "<session-id>",
  "createdAt": "2026-04-10T12:00:00Z",
  "ageSeconds": 5,
  "allowedTools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
}

skill 의 entry 스크립트가 이 파일을 기록한다. 이 파일이 있으면 "skill 진입 완료" 증거.

Skill Trace 파일

.rails/skill-trace.jsonl — 자매가 파이프라인 진행 중 호출한 도구 로그:

{"ts":1765789200,"tool":"Read","cwd":"/home/narang/projects/arang","session_id":"ab12"}
{"ts":1765789201,"tool":"Bash","cwd":"/home/narang/projects/arang","session_id":"ab12"}
{"ts":1765789202,"tool":"Write","cwd":"/home/narang/projects/arang","session_id":"ab12"}

Contract validator 는 이 파일에서 다음을 확인:

  • trace 가 존재하는가?
  • 첫 이벤트 이전에 skill-context.json 이 세팅됐는가?
  • 세션 ID 가 일관된가? (중간에 다른 세션이 개입 안 했는가)

우회 사례 & 대응

우회 시도 감지 대응
자매가 skill 무시하고 직접 Write Pre-hook (skill-context 없음) exit 2 로 차단 + 에러 메시지
자매가 skill 호출 후 딴 데서 작업 Post-hook (cwd mismatch) trace 에 기록, validator 에서 FAIL
자매가 skill 호출 후 stale 세션 재사용 Pre-hook (ageSeconds > 300) exit 2 + 재시작 안내
자매가 trace 파일 삭제 Pre-hook (trace 파일 없음) exit 2
자매가 trace 파일 변조 Post-hook signature 불일치 (향후) hash chain 으로 감지

예외 — Escape Hatch

긴급 상황에서 enforcement 를 일시 해제할 필요가 있을 수 있다:

  • 환경 변수 RAILS_ENFORCE=off 세팅 시 hook 이 warn 만 남기고 통과
  • 단, 반드시 로그에 기록되고 다음 스프린트 시작 시 경고 표시
  • production 에서는 기본 on

실패 시 복구

skill 우회가 감지된 경우:

  1. 자매의 최근 commit 을 git revert 로 되돌림 (hanarang-rails orchestrator 권한)
  2. 파이프라인 FSM 을 이전 상태로 rollback
  3. 디스코드에 에스컬레이션 메시지 전송
  4. 로그에 구조화 기록

참고

  • principles.md 원칙 4
  • failure-audit.md F1
  • Claude Code hooks: .claude/settings.jsonPreToolUse / PostToolUse matcher