.claude/skills/verify-cas/SKILL.md
Verifies CAS (Optimistic Locking) pattern compliance — version field in DTOs, VersionedBaseService usage, cache invalidation on 409 conflict, frontend VERSION_CONFLICT error handling. Run after adding/modifying state-change endpoints.
npx skillsauth add junnv93/equipment_management_system verify-casInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
상태 변경이 수반되는 백엔드 코드가 CAS 패턴을 올바르게 준수하는지 검증합니다:
version 필드가 포함되어 있는지.update() 대신 updateWithVersion()으로 상태를 변경하는지| File | Purpose |
|---|---|
| apps/backend/src/common/base/versioned-base.service.ts | VersionedBaseService 베이스 클래스 + onVersionConflict 훅 |
| apps/backend/src/common/base/__tests__/versioned-base.service.spec.ts | hook 계약 회귀 테스트 (성공/NotFound/409+hook/hook fail/no-op) |
| apps/backend/src/common/dto/base-versioned.dto.ts | versionedSchema 정의 |
| apps/backend/src/common/cache/cache-invalidation.helper.ts | 캐시 무효화 헬퍼 |
| apps/frontend/hooks/use-optimistic-mutation.ts | 프론트엔드 optimistic mutation 훅 |
VersionedBaseService를 상속하는 서비스와 자체 CAS 구현 서비스를 확인합니다.
기대값: 13개 서브클래스 (grep -l "extends VersionedBaseService" 기준). 2026-04-09 부로 자체 CAS 구현(updatePlanWithCAS 등) 전면 제거 — 모든 상태변경 도메인이 VersionedBaseService.updateWithVersion SSOT 를 통과한다. 신규 모듈도 베이스 클래스 상속 필수.
상세: references/cas-checks.md Step 1
approve/reject/update/close DTO에 versionedSchema가 포함되어 있는지 확인합니다.
PASS: 모든 상태 변경 DTO에 versionedSchema spread. FAIL: 누락 DTO 발견.
상세: references/cas-checks.md Step 2
CAS 서비스에서 .update() 대신 updateWithVersion()을 사용하는지 확인합니다.
PASS: 직접 .update() 호출 없음. FAIL: 버전 체크 없는 .update() 사용.
상세: references/cas-checks.md Step 3
onVersionConflict 훅 사용 권장CAS 실패(409) 시 stale cache 를 방지하기 위해 detail 캐시를 삭제하는지 확인합니다.
권장 패턴 (2026-04-08~): VersionedBaseService.onVersionConflict(id) 훅을
서브클래스에서 override 하여 도메인별 캐시 무효화 정책을 단일 지점에 정의.
updateWithVersion 이 ConflictException throw 직전에 자동 호출되므로
호출 사이트마다 try/catch boilerplate 가 필요 없음.
// ✅ 권장 — 정책 한 곳, 호출 사이트는 깨끗
// scope-aware buildCacheKey 서비스: params 객체 전달
protected async onVersionConflict(id: string): Promise<void> {
await this.cacheService.delete(this.buildCacheKey('detail', { id }));
}
// ❌ 안티패턴 — boilerplate 반복, 누락 시 stale 캐시 위험
try {
await this.updateWithVersion(...);
} catch (error) {
if (error instanceof ConflictException) {
this.cacheService.delete(this.buildCacheKey('detail', { id }));
}
throw error;
}
탐지 명령어:
# updateWithVersion 호출이 있는 서비스에서 ConflictException 인스턴스 체크 패턴 잔존
grep -rln "updateWithVersion" apps/backend/src/modules/ | xargs grep -l "instanceof ConflictException"
예외: raw tx.update 로 CAS 를 수동 수행하는 경로(예: equipment-imports.service.onReturnCompleted, onReturnCanceled, import-orphan-scheduler.detectAndRecover)
는 훅을 우회하므로 인라인 catch 유지가 정당함.
PASS: updateWithVersion 호출 사이트에 boilerplate 없음 + 도메인이 캐시를 가지면 onVersionConflict override 존재.
FAIL: updateWithVersion 호출 후 inline instanceof ConflictException catch + cache.delete 잔존.
상세: references/cas-checks.md Step 4
| Step | 검증 대상 | |---|---| | 5 | 트랜잭션 내 CAS tx 전달 | | 6 | equipment 직접 업데이트 시 version bump | | 7 | equipment 직접 업데이트 후 캐시 무효화 | | 8 | 보상 트랜잭션 패턴 (Compensating Transaction) | | 9 | 프론트엔드 mutation에서 version 전달 | | 10 | 승인 프로세스의 CAS version 교체 (stale requestData) | | 11 | updateWithVersion의 version 인자 출처 (DB 조회값 금지) |
상세: references/cas-checks.md Step 5~11
| # | 검사 | 상태 | 상세 |
| --- | ------------------------- | --------- | ------------------------ |
| 1 | VersionedBaseService 상속 | PASS/FAIL | 누락 서비스 목록 |
| 2 | versionedSchema DTO | PASS/FAIL | 누락 DTO 목록 |
| 3 | updateWithVersion 사용 | PASS/FAIL | 직접 .update() 호출 위치 |
| 4 | 캐시 삭제 (409) | PASS/FAIL | 누락 위치 |
| 5 | 트랜잭션 내 CAS tx 전달 | PASS/FAIL | tx 미전달 위치 |
| 6 | 시스템 내부 version bump | PASS/FAIL | version 누락 위치 |
| 7 | 시스템 내부 캐시 무효화 | PASS/FAIL | 캐시 무효화 누락 위치 |
| 8 | 보상 트랜잭션 패턴 | PASS/FAIL | 보상 로직 누락 위치 |
| 9 | 프론트엔드 version 전달 | PASS/FAIL | 누락 API 함수 |
| 10 | 승인 CAS version 교체 | PASS/FAIL | stale version 사용 위치 |
| 11 | version 인자 출처 | PASS/FAIL | DB 조회 version 사용 |
다음은 위반이 아닙니다:
updateWithVersion(..., casColumnKey: 'casVersion') 형태로 베이스 클래스 SSOT 를 통과한다. version(개정 번호) 와 casVersion(잠금) 이 분리된 신규 테이블은 동일 패턴 사용. casColumnKey 는 'version' | 'casVersion' 리터럴 유니언이라 오타는 컴파일 시 차단.tx.update 수동 CAS 경로 — updateWithVersion 우회 구조라 onVersionConflict 훅이 호출되지 않으므로 인라인 catch 유지가 정당 (예: equipment-imports.service.onReturnCompleted, onReturnCanceled, import-orphan-scheduler.detectAndRecover)testing
Verifies Zod validation pattern compliance — ZodValidationPipe usage (no class-validator), versionedSchema inclusion in state-change DTOs, controller pipe application, query DTO consistency. Run after adding/modifying DTOs or controller endpoints.
testing
Verifies cross-feature workflow E2E test coverage against critical-workflows.md checklist. Checks WF-01~WF-35 coverage, step completeness, role correctness, side-effect verification, and status transition assertions. Run after adding workflow tests or before PR.
testing
SSOT(Single Source of Truth) 임포트 소스를 검증합니다. 타입/enum/상수가 올바른 패키지에서 임포트되는지 확인. 타입/enum 추가/수정 후 사용.
development
Verifies SQL safety — LIKE wildcard escaping, N+1 query pattern detection, COUNT(DISTINCT) for fan-out JOINs, RBAC INNER JOIN enforcement. Run after adding/modifying search or list API endpoints.