콘텐츠로 이동
OmniRoute source

Quality-Gate System — Critical Assessment, Catalog and Replication Playbook (한국어)

종합 등급: A− / “고급”. 전체 프로젝트 중 상위 약 5~10%. 이 시스템은 업계에서 명시적으로 이름 붙인 여러 패턴을 독립적으로 구현하고 있습니다. 이는 가장 강력한 정합성의 신호입니다. 체크리스트를 그대로 모방한 것이 아니라 올바른 관행에 자연스럽게 도달했습니다.

참조 프레임워크 현재 수준 등급
OWASP DSOMM (5개 수준, 5개 차원) 견고한 레벨 3이며, _테스트 강도_와 _정적 분석 깊이_에서는 레벨 4에 도달하고 있습니다. 대부분의 조직은 레벨 1~2에 머뭅니다. L3→L4
OpenSSF Scorecard (18개 검사) CI-Tests, Code-Review, Dependency-Update-Tool, Fuzzing, SAST, Signed-Releases(출처 증명), Token-Permissions, Vulnerabilities, Dangerous-Workflow를 통과합니다. 격차: main의 Branch-Protection이 꺼져 있으며, 일부 액션은 고정되어 있지 않습니다. ~7–8/10
SLSA (4개 수준) npm publish --provenance + id-token: write + GitHub 호스팅 빌드 = L2이며, L3에 근접하고 있습니다. L3 이상에 필요한 강화된/밀폐형 빌더가 없습니다. L2→L3
SonarQube “Clean as You Code” 철학이 동일합니다. 래칫은 _비회귀_를 보장합니다(새 코드가 지표를 악화시키지 않음). 차이점: Sonar는 소수의 조건을 권장하지만, 당사 시스템에는 약 46개의 게이트가 있어 피로도 위험이 있습니다. 정합, 단 주의 필요
Quality-Ratchet 패턴 래칫 + dedicatedGate + tightenSlack + --require-tighten + 정상적 건너뛰기를 갖춘 참조 구현입니다. 대부분의 공개 예시보다 더 정교합니다. 모범적
DORA 2024 안정성 축에서 매우 강력합니다. 위험 요소: 과도한 게이트는 _리드 타임_을 늘릴 수 있습니다. 빠른 게이트 분리로 완화했지만, 커버리지 격차가 존재합니다(2부 참조). 강력함(안정성)
OWASP LLM Top 10 (2025) 런타임 가드 + promptfoo(평가) + garak(레드팀)을 통해 위험 #1(프롬프트 인젝션)을 다룹니다. 업계 표준 도구를 사용합니다. 대응 완료
변이 테스트 Stryker를 매일 밤 실행하며 임계값은 70/50이고, 8개의 핵심 모듈을 대상으로 합니다. 업계 합의(기존 코드 60% / 신규 코드 80%, 매일 밤 실행)를 상회합니다. 격차: 점수가 아직 래칫으로 관리되지 않습니다. 거의 완성

파트 2 — 비판적 평가 (강점 + 솔직한 약점)

섹션 제목: “파트 2 — 비판적 평가 (강점 + 솔직한 약점)”
  1. 다중 메트릭 래칫 엔진. 시스템의 핵심입니다. quality-baseline.json에 24개의 메트릭이 있습니다.
    • 각각 방향(up/down), 허용 오차(eps), 여유분 (tightenSlack), dedicatedGate 플래그를 갖는 4개의 전용 기준선이 있습니다. 한 번 수정된 항목은 계속 수정된 상태로 유지됩니다. 즉, 코드베이스 엔트로피에 대한 해독제입니다.
  2. 공급망을 위한 심층 방어. SAST(CodeQL/Sonar) + 시크릿(gitleaks 및 useDefault) + SCA(osv/npm-audit/Trivy/Dependabot) + 라이선스 + lockfile + SBOM + SLSA 출처 증명 + Scorecard + 워크플로 강화(zizmor). 이처럼 완전한 스택을 갖춘 코드베이스는 드뭅니다.
  3. Goodhart의 법칙에 대한 해독제. 커버리지를 목표로 삼는 것은 전형적인 안티패턴입니다 (“측정 지표가 목표가 되는 순간, 더 이상 좋은 측정 지표가 아니다”). 여기에는 이를 보완하는 장치가 있습니다. 변이 테스트(테스트가 단순히 해당 줄을 실행하는지가 아니라 실제로 버그를 잡아내는지를 측정), check-test-masking(통과를 위해 assert를 약화하는 행위를 차단), 모듈별 최소 커버리지(쉬운 부분뿐 아니라 고위험 코드도 테스트하도록 강제), check-pr-evidence(강제 규칙 #18).
  4. 환각 방지 / 일관성 게이트. 드물지만 가치 있는 범주입니다. check-known-symbols, check-fetch-targets, check-openapi-routes, check-docs-symbols는 문서, 명세, 문자열 디스패치가 실제로 존재하는 심볼을 가리키도록 보장합니다. lint/test로는 잡을 수 없는 “부패”를 탐지합니다.
  5. 권고→차단 수명 주기. 새로운 게이트는 권고 수준으로 도입되어 성숙하는 동안에는 병합을 차단하지 않다가, 사이클 종료 시 차단 게이트로 전환됩니다. 상한선을 잃지 않으면서 마찰을 줄입니다.
  6. 인프라 누락 시 정상적인 건너뛰기. 스캐너(--ratchet)는 바이너리/네트워크 실패 시 exit 0으로 종료됩니다. 인프라 누락으로 인해 정당한 PR이 차단되는 일이 없습니다. 성숙한 엔지니어링입니다.
  7. 문화의 코드화. 강제 규칙 + trust-but-verify + 만료된 allowlist + 증거 게이트를 통해 규율을 자동 검증 체계로 전환합니다.
  1. 🔴 fast-gates 분리에는 여전히 구조적 허점이 있습니다. quality.yml(PR→release/**)은 이제 코드 PR에 대해 typecheck, 빠르고 결정론적인 테스트, 권고 수준의 프로덕션 빌드를 실행하지만, 여전히 ci.yml의 전체 release-PR 범위(커버리지 래칫, 패키지 아티팩트, 통합 테스트, E2E, SonarQube)를 실행하지 않습니다. 동기(속도)는 타당하지만, 게이트는 병합이 일어나는 지점에 있어야 합니다(shift-left). 가장 큰 미해결 구조적 수정 사항입니다.
  2. 🟠 게이트 난립/피로 위험. 약 46개의 게이트 + 25개의 job은 매우 많습니다. Sonar도 조건이 너무 많으면 “게이트 피로”와 우선순위 논쟁이 발생하고, 결국 게이트가 무시될 위험이 있다고 경고합니다. DORA는 과도한 게이트가 lead-time을 늘린다고 경고합니다. 권고 계층과 비절대적 래칫으로 이를 완화하고 있지만, 게이트별 정기적인 ROI 검토가 누락되어 있습니다(문서 동기화를 위한 일부 마이크로 게이트는 통합할 수 있습니다).
  3. 🟠 변이 점수는 아직 래칫이 아닙니다. 커버리지 조작에 대한 가장 강력한 해독제가 권고 수준에 머물러 있습니다. 이는 가장 가치가 높은 미해결 항목입니다(이미 90% 구현됨).
  4. 🟡 적절한 범위에서는 차단해야 하는 권고 항목. osv(vulnCount)와 oasdiff는 동결된 기준선이 있음에도 권고 수준입니다. osv를 권고 수준으로 두는 것은 타당합니다(기존 의존성에서 새로운 CVE가 발견되면 관련 없는 PR까지 차단될 수 있음). 하지만 중간 지점이 있습니다(Trivy에서 했던 것처럼 CRITICAL 이상이면서 수정 가능한 취약점만 차단). oasdiff가 권고 수준이라는 것은 계약을 깨뜨리는 변경도 통과할 수 있다는 의미입니다.
  5. 🟡 런타임 보안은 nightly에서만 실행됩니다. schemathesis/garak/promptfoo/chaos/k6는 야간에 실행됩니다. 올바른 결정이지만(느리고 라이브 서버가 필요함), PR이 삽입 방어 회귀를 유발하더라도 다음 날 밤이 되어서야 탐지될 수 있습니다.
  6. 🟡 main의 브랜치 보호가 꺼져 있습니다. BRANCH_LOCK_TOKEN은 release 브랜치를 잠그지만 main 자체는 보호되지 않습니다. Scorecard/DSOMM에서 감점 요인입니다. 소유자의 조치가 필요합니다.
  7. 🟡 CodeQL은 default-setup이며 semgrep은 코드화되지 않았습니다. default-setup은 정상적으로 작동하지만(알림 0건), 커밋된 codeql.yml을 사용하면 더 세밀하게 제어할 수 있습니다. semgrep은 저장소에서 버전 관리되지 않고 외부 클라우드 플랫폼을 통해 실행됩니다.

파트 3 — 품질 체크포인트 전체 카탈로그(이식 가능)

섹션 제목: “파트 3 — 품질 체크포인트 전체 카탈로그(이식 가능)”

아래 12개 범주는 재사용 가능한 형태의 “품질 시스템”입니다. 각 범주에는 목표(보호할 대상), 사용 도구, 그리고 모든 스택에서 재현할 수 있는 도구 독립적 대안이 나열되어 있습니다.

1. 스타일 및 서식(결정적, 신속)

섹션 제목: “1. 스타일 및 서식(결정적, 신속)”
  • OmniRoute: lint-staged를 통한 Prettier + ESLint(pre-commit), 2칸 들여쓰기/큰따옴표/100열.
  • 일반: 자동 수정 가능한 포매터 하나 + 린터 하나를 staged 파일에 대해 pre-commit에서 실행.
  • OmniRoute: typecheck:core(차단) + typecheck:noimplicit:core(권고) + type-coverage 래칫 92.17% + 파일별 any 예산.
  • 일반: CI에서 엄격한 타입 검사 + 래칫 방식의 타입 커버리지 지표 + 파일별 any/탈출구 예산.
  • OmniRoute: 서로 겹치지 않는 러너 2개(Node 네이티브 + vitest), 샤드 8개, 전역 커버리지 60/60/60/60 + 래칫 약 76% + 핵심 모듈을 위한 모듈별 하한 8개 + 야간 속성 기반 테스트 + 야간 변이 테스트.
  • 일반: 테스트 러너 + 절대적 커버리지 하한(제로 방지) + 커버리지 래칫(회귀 방지) + 고위험 코드의 모듈별 하한(굿하트의 법칙 방지) + 순수 로직에 대한 속성 기반 테스트 + 테스트 품질의 실질적인 척도로서 야간 변이 테스트.
  • OmniRoute: pr-test-policy(프로덕션 코드에 테스트 요구), check-test-masking(약화된 어서션 차단), pr-evidence(성공 주장에 증거 블록 요구), test-discovery(모든 테스트가 러너에 의해 수집되도록 보장).
  • 일반: “새 코드 ⇒ 새 테스트” 게이트 + 제거된 어서션/항진식 탐지기 + 증거 요구사항(TDD 또는 현행 테스트) + glob 외부에 고립된 테스트가 없도록 보장.
  • OmniRoute: ESLint 경고(3769↓), jscpd 중복(5.72%↓), 순환 복잡도+최대 줄 수 복잡도(1800↓), sonarjs 인지 복잡도(753↓), knip 미사용 코드/미사용 export(339↓), 파일별 파일 크기(동결, 축소만 허용), 순환 의존성(커스텀 Tarjan, 차단).
  • 일반: 모든 건전성 지표(경고, 중복, 순환 및 인지 복잡도, 미사용 코드, 파일 크기, import 순환)에 래칫 적용. 방향은 항상 “악화 금지”.
  • OmniRoute: CodeQL(알림 래칫 = 0), gitleaks([extend] useDefault=true — 매우 중요!), SonarQube, 커스텀 보안 규칙(public-creds, error-helper, route-guard-membership, route-validation).
  • 일반: 알림 래칫을 적용한 SAST(CodeQL/Sonar/semgrep) + 상속된 기본 규칙 세트를 사용하는 비밀 정보 스캐너(기본값을 재정의하는 커스텀 설정은 사각지대를 만듦) + 프로젝트별 Hard Rule 보안 게이트.
  • OmniRoute: osv-scanner + npm-audit + Trivy + Dependabot(SCA), license-checker(SPDX 허용 목록), lockfile-lint(HTTPS+sha512+registry), check-deps 안티 슬롭스쿼팅(허용 목록 + 사용 기간 ≥72시간).
  • 일반: 다중 소스 SCA + 라이선스 허용 목록 + lockfile 무결성 검사 + 사용 기간/타이포스쿼팅 검사를 포함한 의존성 허용 목록 + 그룹화된 업데이트 봇.
  • OmniRoute: SBOM(CycloneDX + syft), SLSA 출처 증명(--provenance), OpenSSF Scorecard(매주), 워크플로 강화(zizmor: artipacked→persist-credentials:false, 캐시 포이즈닝, 토큰 권한).
  • 일반: 게시 시 SBOM 생성 + 서명된 출처 증명(SLSA L2+) + 예약 실행되는 Scorecard + 모든 워크플로 강화(최소 권한 토큰, 푸시하지 않는 checkout에서는 자격 증명 유지 금지, action을 SHA로 고정).
  • OmniRoute: oasdiff(파괴적 OpenAPI 변경), schemathesis(야간 계약 퍼징), openapi-coverage(문서화된 route 비율, 래칫 38.3%), openapi-security-tiers(spec 대 route-guard).
  • 일반: 파괴적 변경 계약 diff(oasdiff/buf) + spec에 대한 속성 기반 퍼징(schemathesis) + 래칫 방식의 문서화 커버리지 + spec↔코드 일관성.
  • OmniRoute: docs-sync(미러링된 버전), docs-counts-sync(문서와 코드 간 수치), env-doc-sync, doc-links, fabricated-docs, cli-i18n, i18n-ui-coverage(--threshold=65 + 래칫 80.1%).
  • 일반: 문서와 코드 간 버전/개수/환경 변수 동기화(신뢰가 아닌 게이트) + 내부 링크 검증 + 래칫 방식의 i18n 커버리지.

11. 환각 방지/일관성(드문 범주)

섹션 제목: “11. 환각 방지/일관성(드문 범주)”
  • OmniRoute: known-symbols(문자열 디스패치 ⇒ 현행 심볼), provider-consistency, fetch-targets(클라이언트 fetch ⇒ 실제 route), docs-symbols, db-rules(Hard Rules #2/#5), migration-numbering.
  • 일반: 모든 “중복된 진실의 원천”(레지스트리, 문자열 디스패치, 계층 간 참조)에 대해 양쪽이 일치함을 입증하는 게이트 적용. 타입 검사/테스트가 잡지 못하는 부패를 탐지.
  • OmniRoute: chaos(장애 주입), heap-growth(누수), k6(소크), promptfoo+garak(LLM 레드팀 OWASP LLM Top 10), 복원력 법칙 3가지(circuit-breaker/cooldown/lockout).
  • 일반: 여러분의 도메인에 존재하는 장애 모드를 식별하고 각각에 대한 게이트를 마련합니다(야간 실행이어도 무방). AI 앱의 경우: 인젝션 레드팀. 분산 시스템의 경우: chaos + 누수 + 소크.

파트 4 — 모든 프로젝트에 적용 가능한 재현 계획

섹션 제목: “파트 4 — 모든 프로젝트에 적용 가능한 재현 계획”

각각 독립적으로 가치를 제공하도록 단계별로 구축하세요. 12개 범주를 한꺼번에 모두 시도하지 마세요 — 그러면 파트 2에서 경고한 게이트 피로가 그대로 발생합니다. 모든 신규 게이트는 권고 상태로 도입하고, 안정화되면 차단 상태로 전환합니다.

재사용 가능한 핵심 요소: “래칫 게이트의 구조”

섹션 제목: “재사용 가능한 핵심 요소: “래칫 게이트의 구조””

전체 시스템은 다음과 같은 3개 파일 패턴을 중심으로 작동합니다. 먼저 이 패턴을 복사하세요.

  1. baseline.json — 고정된 메트릭 값 + direction (up/down) + eps(플레이크 방지) + tightenSlack + dedicatedGate.
  2. collect-metrics.<ext> — 도구를 실행하고 수치를 추출하여 metrics.json에 기록합니다.
  3. check-ratchet.<ext> — metrics.json과 baseline.json을 비교합니다. eps를 초과하여 회귀한 경우에만 exit 1을 반환하고, 도구/인프라가 없으면 exit 0을 반환하여 정상적으로 건너뜁니다. --require-tighten을 사용하면 기준선을 업데이트하지 않은 채 개선된 경우 exit 1을 반환하여 개선 성과를 고정합니다.

이 패턴을 갖추면 모든 신규 메트릭(커버리지, 복잡도, 경고, SAST 경고, 번들 크기, 변이 점수 등)은 기준선에 한 줄만 추가하면 됩니다.

CI를 갖추고, 포매터 + 린터 + 타입 검사 + 테스트 러너 1개 + 절대적 커버리지 하한선 (예: 60%)을 설정합니다. 프리커밋에서는 빠르고 자동 수정 가능한 검사를 실행합니다. 결과: 어떤 PR도 기본 사항을 훼손하지 않습니다.

단계 1 — 래칫 엔진(2주 차) — 모든 것의 기반

섹션 제목: “단계 1 — 래칫 엔진(2주 차) — 모든 것의 기반”

위의 3개 파일을 구현합니다. 경고, 커버리지, 복잡도, 중복, 데드 코드, 파일 크기에 대한 기준선을 고정합니다. 결과: 이제부터 코드베이스는 개선만 가능합니다.

단계 2 — 정적 분석 심화(3주 차)

섹션 제목: “단계 2 — 정적 분석 심화(3주 차)”

경고 래칫을 적용한 SAST(CodeQL/Sonar/semgrep), 비밀 정보 스캐너(기본 규칙 세트를 상속), SCA(osv/Dependabot) + 라이선스 허용 목록 + lockfile-lint를 도입합니다. 결과: 알려진 취약점과 유출된 비밀 정보가 검사를 통과하지 못합니다.

게시 시 SBOM 생성 + 서명된 출처 증명(SLSA L2) + 예약 실행되는 Scorecard + 워크플로 강화 (zizmor: 최소 토큰, 자격 증명 미보존, 액션 버전 고정)를 적용합니다. 결과: 추적 가능하고 변조 방지된 릴리스가 만들어집니다.

단계 4 — 테스트 강도 강화(5~6주 차)

섹션 제목: “단계 4 — 테스트 강도 강화(5~6주 차)”

유용하다면 두 번째 러너를 도입합니다. 핵심 모듈별 커버리지 하한선(굿하트의 법칙 방지), 순수 로직에 대한 속성 기반 테스트, 매일 밤 변이 테스트를 적용하고 → 첫 번째 점수가 나오면 mutationScore를 래칫으로 설정합니다. 결과: 커버리지가 허영 메트릭에 그치지 않으며, 테스트가 실제로 버그를 잡아낸다는 사실을 입증할 수 있습니다.

단계 5 — 계약 및 동적 분석(7주 차)

섹션 제목: “단계 5 — 계약 및 동적 분석(7주 차)”

공개 API가 있다면 oasdiff(호환성을 깨는 변경, 차단) + schemathesis(매일 밤 퍼징)를 적용합니다. 도메인에 적합한 경우 DAST/레드팀 테스트를 매일 밤 실행합니다. 결과: 계약이 조용히 깨지지 않습니다.

단계 6 — 환각 방지 및 도메인 검사(8주 차)

섹션 제목: “단계 6 — 환각 방지 및 도메인 검사(8주 차)”

프로젝트의 각 “중복된 진실”마다 일관성 게이트를 하나씩 둡니다. 도메인별 실패 모드 게이트도 적용합니다(AI의 경우 인젝션 레드팀 테스트). 결과: 구조적 부패와 도메인 실패에 대한 안전망이 생깁니다.

  • 모든 신규 게이트에 권고→차단 주기를 적용합니다.
  • stale-allowlist: 모든 억제 항목에는 근거 + 이슈가 있어야 하며, 더 이상 필요하지 않은 억제 항목을 감지합니다.
  • evidence-gate: PR의 성공 주장에는 증거(테스트 또는 지속적으로 유지되는 테스트)가 필요합니다.
  • 게이트별 분기별 ROI 검토를 수행합니다(효과가 없는 게이트는 폐지하거나 투자를 중단하여 피로를 방지합니다).
  • 프로젝트의 강제 규칙을 실행 가능한 게이트로 승격합니다.
  • 절대 기준이 아닌 래칫. 고정된 수치가 아니라 _비회귀_를 게이트 조건으로 삼습니다(0 방지 하한선 제외).
  • 절대 하한선과 래칫을 함께 적용. 하한선은 붕괴를 방지하고, 래칫은 점진적인 저하를 방지합니다.
  • 설계 단계부터 굿하트의 법칙 방지. 모든 목표 메트릭에는 균형추가 필요합니다(커버리지 ⇒ 변이 테스트 + 마스킹 방지, 테스트하기 어려운 코드도 반드시 테스트하도록 모듈별 하한선 적용).
  • 정상적인 건너뛰기. 인프라가 없다는 이유로 차단하지 않으며, 실제 회귀만 차단합니다.
  • 비용이 큰 메트릭에는 dedicatedGate 사용. 외부 바이너리가 필요한 메트릭은 동기식 중앙 래칫 외부의 전용 스크립트로 처리하며, 건너뛰기 동작을 포함합니다.
  • 병합이 일어나는 지점에 게이트 배치. 빠른 게이트와 실제 병합 사이에 공백을 두지 마세요(빠른 게이트 분리에서 얻은 교훈).
  • 차단 게이트는 적게, 신중하게 선택. Sonar/DORA가 보여주듯 조건이 너무 많으면 피로가 발생합니다. 차단 게이트의 장벽보다는 권고 + 래칫을 선호하세요.

파트 5 — 권장 개선 사항(우선순위별, 호환 가능)

섹션 제목: “파트 5 — 권장 개선 사항(우선순위별, 호환 가능)”

P0 — 가장 높은 ROI, 거의 준비 완료

  1. 변이 점수 래칫(첫 번째 야간 Stryker 실행에서 값이 산출된 이후). 커버리지-굿하트 현상에 대응하는 핵심 수단이며, 약 90% 완료됨.
  2. 남아 있는 빠른 게이트의 허점 해소 — 권고 기간 1주가 지난 후 quality.yml의 프로덕션 빌드를 필수로 승격하고, 결정론적이며 릴리스 PR에만 적용되는 검사들을 계속 PR→release 경로로 이동.
  3. main 브랜치 보호(소유자 설정) — Scorecard를 높이고 DSOMM 격차를 해소.

P1 — 가치 높음 4. osv/oasdiff → 적절한 범위에서 차단형으로 전환 — osv는 CRITICAL이면서 수정 가능한 항목만 차단(Trivy와 같은 2단계 방식); oasdiff는 호환성을 깨는 변경을 차단. 5. require-tighten → 차단형으로 전환(사이클 종료 시점) — 지표 개선을 고정. 6. ci-summary에서 게이트별 ROI/소요 시간 검토 — 느리거나 가치가 낮은 게이트를 찾아 제거.

P2 — 한계효용 감소 7. SLSA L3 — L2에서 상향하려는 경우 밀폐형/재현 가능한 빌더(GitHub SLSA generator) 도입. 8. 커밋된 CodeQL 설정 + 버전이 고정된 semgrep — 제어력/재현성 향상. 9. PR별 DAST 스모크 테스트 — 위험도가 가장 높은 엔드포인트에서 schemathesis/promptfoo의 빠른 하위 집합 실행(야간 실행에만 국한하지 않음). 10. 불안정성 대시보드 + DORA 지표 — 게이트가 속도를 저해하지 않는지 확인.


파트 6 — 구체적인 릴리스 교훈(Phase 9에 추가할 게이트)

섹션 제목: “파트 6 — 구체적인 릴리스 교훈(Phase 9에 추가할 게이트)”

이 섹션은 릴리스 마감 과정에서 게이트가 누락되었던 실제 사고를 구체적인 증거 및 제안된 게이트와 함께 기록합니다. 각 항목은 파트 5의 후보입니다.

교훈 v3.8.27 (2026-06-17) — “빠른 게이트의 허점”으로 인해 결정론적 회귀가 릴리스 당일까지 유입됨

섹션 제목: “교훈 v3.8.27 (2026-06-17) — “빠른 게이트의 허점”으로 인해 결정론적 회귀가 릴리스 당일까지 유입됨”

발생한 일. v3.8.27 /generate-release 과정에서 릴리스 PR(release/v3.8.27 → main)은 통합 사이클에서 전체 ci.yml 매트릭스가 실행된 첫 번째 시점이었습니다. 그 결과 한꺼번에 12건이 실패했습니다. 즉, 결정론적 테스트 3건 + 불안정성/환경 문제 약 9건이었습니다. 실제 제품 회귀는 없었지만, 사이클 PR은 전체 단위 테스트 스위트, pr-test-policy(테스트 마스킹), 전체 통합 테스트 스위트, 스키마 동등성 검사를 실행하지 않는 **Fast QG (quality.yml)**를 통해 release/**에 들어가기 때문에 모두 감지되지 않았습니다. 결정론적 실패 3건은 다음과 같습니다.

  1. UI 변경으로 인해 오래된 테스트 — permissions modal switch buttons declare button type: #4034에서 네 번째 스위치가 추가되었고(a11y type="button"은 유지됨), 테스트의 === 3 개수 조건이 오래된 상태가 되었습니다. 정적 분석이 #4034 PR에서 이를 감지했어야 합니다.
  2. 패키징 변경으로 인해 오래된 테스트 — findMissingArtifactPaths ... root runtime files: dist/http-method-guard.cjs가 적법한 필수 경로가 되면서 테스트의 예상 목록이 오래된 상태가 되었습니다.
  3. 손실을 유발한 모듈화 불일치(가장 심각) — settings schemas accept ... unprefixed toggle: 모듈화된 updateSettingsSchema(#3988에서 생성된 schemas/settings.ts)가 정본(settingsSchemas.ts)과 달라졌습니다. **45개 필드 대 85개 필드 — 40개 누락 + 6개 불일치(qdrant*)**였습니다. 이는 데드 코드였으므로(런타임은 정본을 사용함) 실제 영향은 없었지만, 직접 작성한 동등성 테스트만이 이를 감지했습니다. #4030은 #3988/#3993에서 발생한 유사한 누락 16건을 복원했지만, 이 사례는 빠져나갔습니다.

제안된 게이트(Phase 9):

  • G1 — 빠른 게이트의 허점을 실제로 해소(P0 #2 확장). quality.yml(PR→release/**)에서 typecheck + 영향받는 테스트뿐 아니라 pr-test-policy(테스트 마스킹) + 전체 결정론적 단위 테스트 스위트도 실행합니다(또는 최소한 빠르고 불안정하지 않은 정적/동등성 파일 실행). 이렇게 하면 오래된 테스트와 assert 제거를 릴리스 당일이 아니라 해당 변경을 도입한 PR에서 감지할 수 있습니다. integration/e2e는 느리고 불안정하므로 제외하되, 결정론적 계층을 PR→main에만 두어서는 안 됩니다.
  • G2 — 모듈화 동등성 게이트(신규, 현재는 미지원). 모듈화된 배럴(src/shared/validation/schemas/*, providerRegistry 모듈 등)이 다시 내보내는 각 심볼에 대해 형태(z.object 키, 레지스트리 항목)를 정본 소스와 비교하고 불일치(누락/추가 필드)가 있으면 실패시키는 검사입니다. #3988에서 발생한 40개 필드 누락을 바로 그 PR에서 감지할 수 있었을 것입니다. 이는 직접 작성한 동등성 테스트(누군가 작성해야 한다고 기억한 곳에만 존재함)를 일반화합니다. 비용도 낮습니다. 양쪽을 import한 뒤 Object.keys(shape)를 비교하면 됩니다.
  • G3 — 결정론적 불안정성 분류(지원). LiveWS-startup 및 integration-combo/breaker 테스트는 로직이 아니라 CI의 서버 타임아웃/연쇄 실패(환경)로 인해 실패합니다. 이를 known-flaky(이슈와 함께 격리됨)로 표시하여 릴리스 PR의 실패 상태가 결정론적 회귀를 중간에서 가리는 노이즈가 아니라 실제 신호만 나타내도록 합니다.

원칙: 게이트는 병합이 일어나는 위치에서 실행되어야 합니다(Cross-cutting principles에 이미 명시됨). v3.8.27 사고는 이 원칙이 lint/typecheck뿐 아니라 결정론적 테스트 계층에도 적용됨을 보여줍니다. 그렇지 않으면 오래된 테스트 + 손실을 유발한 모듈화로 인한 부채가 최악의 순간인 PR→main에서 한꺼번에 드러납니다.



OmniRoute 소스 코드 (a58000c7685f)

HagiCode

HagiCode는 구조화된 워크플로, 다중 에이전트 실행, Hero Dungeon 뷰를 갖춘 에이전트 코딩 작업 공간입니다.

더 스마트하고 빠르며 즐거운 에이전트 워크플로로 유용한 소프트웨어를 만드세요.

HagiCode 라이트 테마 메인 화면
  • Smart구조화된 워크플로는 의도를 아이디어부터 배포까지 실행 가능한 경로로 바꿉니다.
  • Efficient다중 에이전트 워크플로로 조사, 구현, 검토를 병렬로 진행합니다.
  • FunHero Dungeon은 긴 코딩 세션을 시각적이고 협업적인 경험으로 만듭니다.
HagiCode 방문