Skip to content

CI 실행 정책 (한국어)

🌐 CI_POLICY.md의 번역입니다. 영어 원문이 표준이며, CI가 영어 원문과의 구조 일치를 검사합니다.

일상적인 CI는 버전/OS의 전체 조합 매트릭스 대신 대표 조합을 사용합니다.

트리거 런타임 검증
문서만 변경한 PR 문서 및 정책 검사만 수행. 런타임 스위트와 CUBRID 서버 생성 없음
일반 코드 PR 95% 커버리지 기준을 적용한 Ubuntu/Python 3.12 전체 오프라인 스위트 하나(#742)
고위험 PR 같은 전체 오프라인 스위트와 Python 3.14/CUBRID 11.4. 필요하면 대상 레인 추가
main으로의 코드 푸시 기존 95% 커버리지 기준을 적용한 Ubuntu 전체 오프라인 스위트를 지원하는 가장 오래된·최신 Python(3.11, 3.15)에서 실행(#734). 라이브 엔드포인트 Python 3.14/CUBRID 11.4와 Python 3.11/CUBRID 10.2. 릴리스 커밋의 푸시는 정적·문서·도구 검사만 실행하고, 런타임 레인은 같은 SHA의 릴리스에서 실행(#737)
월요일 03:00 UTC main 푸시와 같은 정책(Python 3.11/3.15 오프라인 셀 포함)으로 최근 7일의 변경을 비교. 변경이 없거나 문서만 바뀐 이력은 런타임 테스트를 선택하지 않음. 권고용 SQLAlchemy 프리릴리스 카나리와 가장 오래된 셀의 권고용 CUBRIDdb 컴플라이언스는 푸시가 아니라 여기서 실행하며, 컴플라이언스 단계는 최근 7일에 코드 변경이 있어야 실행(#737)
명시적 전체 실행 또는 릴리스 기존 전체 Python 3.11–3.15 × CUBRID 10.2/11.0/11.2/11.4 통합 워크플로와 필수 릴리스 레인, 그리고 Python 3.11/3.15 오프라인 셀, 타입 검사, alembic-compat, 패키징 스모크 테스트, 차단 SQLAlchemy 컴플라이언스 레인(#737)

PR은 푸시와 같은 전체 오프라인 선택(-m "not integration and not repo")과 95% 커버리지 기준을 실행합니다. 이전의 세 파일 PR 스모크는 로컬에서 약 1초, 전체 스위트는 약 24초로, 오프라인 회귀가 main에서 처음 실패하도록 둘 만큼의 절감이 아니었습니다(#742). PR의 라이브 통합 검사는 계속 대표 검사입니다. 기여자는 자신의 변경과 관련된 회귀 검사를 로컬에서 실행하고 명령과 결과를 PR에 기록해야 합니다. make test-fast는 이 오프라인 선택을 로컬에서 실행하고, make integration은 범위 변경 없이 그대로 사용할 수 있습니다. make test는 저장소 도구 테스트도 실행합니다(#1009).

변경 선택은 ci.yml의 detect-changes 잡에서 이루어집니다. 문서가 아닌 경로는 기본적으로 코드로 취급하므로, 새 소스/설정 파일이 조용히 문서로 분류되지 않습니다. 연결/프로토콜/커서/비동기/호환성 또는 다이얼렉트/컴파일러/리플렉션, 의존성, 빌드, 스크립트 변경과 ci.yml 자체의 변경은 병합 전 대표 통합 검사를 선택하며, 그 밖의 워크플로 변경은 대신 도구 레인과 오프라인 스위트를 선택합니다(워크플로 변경 영향 참고). 저장소 도구 테스트는 도구가 바뀔 때 Linux 한 레인에서 실행합니다. 오프라인 레인은 이 모듈을 실행하지 않으므로 도구에는 agent-evals/**와 모든 저장소 도구 테스트 모듈이 포함됩니다(#964). 풀 리퀘스트에서 release_tooling 필터에 나열된 릴리스 노트 스크립트, 그 테스트 모듈과 release-please.yml은 런타임·라이브 레인 대신 release-tests 레인(도구 레인과 정적 검사 포함)만 선택합니다. main 푸시, 주간 실행과 수동 실행에서는 계속 코드로 취급합니다. release-tests는 나열된 모듈을 실행하고 나열된 경로가 없으면 실패하므로, 모듈을 지우거나 이름을 바꾸려면 ci.yml을 고쳐야 하며 그 수정은 전체 검증을 선택합니다(#1154). 정적 린트 잡은 생성 문서 검사와 README 비동기 예제 테스트(test/test_readme_async_examples.py)를 포함해 모든 이벤트에서 계속 실행되므로, README만 바꾸는 변경도 이 테스트를 실행합니다.

집계 필수 검사의 이름은 그대로 유지되며 변경 감지를 포함합니다. 선택된 잡은 성공해야 합니다. 선택된 검사가 생략·실패·취소되면 게이트가 실패합니다. 의도적으로 선택하지 않은 잡만 생략될 수 있습니다. 브랜치 보호는 집계 게이트에 걸어 두고, 매트릭스를 줄인 뒤 예전 매트릭스 셀 이름을 하나하나 필수로 두지 마세요. 병합 전에 브랜치 보호 설정을 확인해야 합니다. 보호 설정 변경은 이 PR에 포함되지 않습니다.

전체 검증에는 자동 야간 일정이 없습니다. integration-full.yml은 수동 실행과, 변경 불가능한 후보 SHA를 넘기는 릴리스 workflow_call을 유지합니다. 정확한 후보 브랜치/커밋에서 워크플로를 실행하고, PR 증거로 쓰기 전에 실행의 head SHA를 확인하세요. 브랜치가 움직였다면 새 증거가 필요합니다. 일상 CI의 수동 실행은 경로에 따라 달라지므로, 조건 없는 호환성 실행이 필요하면 전체 워크플로를 사용하세요. 릴리스 게시자/생성기는 변경하지 않습니다.

동시 실행은 이벤트와 ref별로 분리되므로 main 푸시, 주간 일정, 수동 실행이 서로를 취소할 수 없습니다. 같은 PR의 대체된 실행은 그 PR 그룹 안에서 여전히 취소됩니다. main 푸시와 PR 병합 ref의 SHA가 같다고 가정하지 않습니다. 주간 변경 선택은 저장된 마지막 성공 캐시가 아니라 최근 7일을 사용합니다. 실패한 주간 실행은 성공한 증거로 취급하지 말고 재실행하거나 수동 검증으로 이어 가야 합니다.

GitHub 요금이 어느 워크플로나 러너 SKU에서 발생하는지는 확인되지 않았습니다. 잡 수를 줄인 것은 반복 작업이 줄었다는 뜻이지, 측정된 비용 절감이 아닙니다. 비용을 주장하기 전에 이후의 Actions 잡/러너 분과 실제 과금 범주를 비교하세요.

일상적인 타입 검사는 Python 3.13/SQLAlchemy 2.1.1을 사용하고, Alembic 검사는 최신 버전을, 패키징은 SQLAlchemy 2.1.1을 사용합니다. 라이브 엔드포인트 셀(Python 3.14/CUBRID 11.4와 Python 3.11/CUBRID 10.2)은 main/주간 실행에서 SQLAlchemy 2.0과 2.1 컴플라이언스 커버리지를 유지합니다. make integration은 PR에서 PR이 아닌 코드 검증으로 미뤄지며 예전 라이브 스모크의 단언도 담당합니다. 권고 수준의 SQLAlchemy 카나리는 주간 일정과 수동 실행에서 실행됩니다(#737).

이벤트 계층과 비용 근거

#737은 비용이 큰 워크플로 이벤트마다 목적을 하나씩 둡니다. PR은 빠르고 대표적인 검사를 유지하고, main 푸시는 엔드포인트 증거를 담당하고 주간 일정은 카나리도 담당하며, 릴리스는 전체 매트릭스를 실행하고 실패 시 닫힌 상태로 실패합니다(fail closed). 통합 정리에서는 같은 SHA의 다른 레인이 이미 같은 단언을 하는 레인, 또는 아무것도 차단하지 않는 권고 레인만 제거했습니다.

측정

이 변경 전인 2026-10-09에 GitHub REST API(actions/runs, runs/{id}/jobs)로 측정했습니다. 러너 분은 실행된 각 잡의 completed_at - started_at 합계이며, "올림"은 GitHub의 계량 단위대로 잡마다 분 단위로 올린 값입니다. 이 저장소는 공개 저장소이고 표준 GitHub 호스트 러너를 쓰므로 러너 분은 청구 금액이 아닙니다. 실패 수율은 실패한 실행을 셉니다. 취소된 실행은 따로 적었는데, 표본의 취소는 모두 같은 동시성 그룹의 더 새로운 실행에 의해 대체된 것이지 결함이 아니었기 때문입니다. 표본은 범주별로 가장 최근에 완료된 실행이며, 10월 초에 워크플로 구조가 자주 바뀌어 기간이 짧습니다.

범주 기간(실행 수) 잡 수 중앙값 러너 분 중앙값 올림 분 중앙값 경과 분 중앙값 실패 / 취소
문서만 변경한 PR 2026-10-03..09 (15) 7 1.0 7 0.6 0 / 0
일반 코드 PR 2026-10-03..06 (16) 9 1.9 9 0.8 0 / 0
고위험 PR 2026-10-08..09 (20) 13 10.2 19 6.2 1 (lint) / 5
main 코드 푸시 2026-10-05..09 (20) 17 19.3 29.5 6.6 0 / 4
주간 ci.yml 일정 2026-10-05 (1) 17 21.2 32 7.3 0 / 0
업스트림 pycubrid 카나리 2026-08-10..10-08 (19) 2 1.9 4 1.2 카나리 잡 3개(드리프트, 보고됨) / 0
릴리스(publish-pypi.yml과 integration-full.yml) 2026-10-04..09 (2) 35 82.4 103.5 14.2 1 (cookbook 검증) / 0
integration-full.yml 수동 실행 2026-09-27..10-08 (8) 24 73.4 85.5 8.0 0 / 0

2026-10-01부터 2026-10-09까지 main으로의 코드 푸시 72건("코드 푸시"는 offline-tests 또는 typecheck가 실행된 main 푸시 실행이며, 전체 푸시는 84건) 중 47건이 성공하고 9건이 실패하고 16건이 취소되었습니다. 실패(모두 2026-10-03)는 모두 같은 실행에서 offline-tests와 sqlalchemy-21-canary가 함께 실패한 것이며, live-smoke, make-integration, integration-tests는 푸시에서 한 번도 실패하지 않았습니다. 2026-09-25부터 2026-10-09까지 PR이 아닌 모든 ci.yml 실행(모든 시도)에서 sqlalchemy-21-canary의 실패 9건(모두 2026-10-03)은 모두 offline-tests (3.12) 실패와 겹쳤으므로, 이 카나리는 푸시에서 독립적인 신호를 더하지 않았습니다.

코드 푸시에서의 잡별 러너 분 중앙값: integration-tests 셀당 5.3, make integration 2.1, repo-tests 1.6, live-smoke 1.3, offline-tests 셀당 0.7–0.8, sqlalchemy-21-canary 0.6, 패키징 0.5, alembic-compat 0.5, 타입 검사 0.4, lint 0.4. 가장 오래된 integration-tests 셀의 권고용 CUBRIDdb 컴플라이언스 단계는 중앙값 34초가 걸립니다.

커버리지 소유

커버리지 소유(워크플로 / 잡) 이벤트 계층
오프라인 스위트, 95% 기준 ci.yml offline-tests, integration-full.yml offline-endpoints PR: Python 3.12. main 푸시, 주간, 수동 실행: 3.11과 3.15. 릴리스: 3.11과 3.15
타입 검사 ci.yml typecheck(Python 3.13, SQLAlchemy 2.1.1), integration-full.yml typecheck(같은 셀) 모든 코드 이벤트, 수동 실행과 릴리스
Alembic 호환성 ci.yml alembic-compat(최신 Alembic), integration-full.yml alembic-compat(같은 셀), offline-tests의 오프라인 Alembic 테스트, integration-tests·make-integration·integration-full의 라이브 Alembic 연산, integration-tests와 integration-full의 트랜잭션 DDL 고위험 PR과 PR이 아닌 코드 이벤트, 수동 실행과 릴리스
패키징 ci.yml packaging-smoke-test, integration-full.yml packaging-smoke-test(pycubrid extra와 Alembic 검사를 포함한 같은 단계), publish-pypi.yml build 고위험 PR과 PR이 아닌 코드 이벤트, 수동 실행과 릴리스
대표 라이브 스모크 live-smoke를 흡수한 ci.yml make-integration(pycubrid, CUBRID 11.4, Python 3.12) main 푸시, 주간, 수동 실행
make integration ci.yml make-integration, integration-full.yml make-integration(두 드라이버, CUBRID 10.2와 11.4) PR이 아닌 코드 이벤트, 수동 실행과 릴리스
CUBRID/Python 엔드포인트 ci.yml integration-tests(고위험 PR은 3.14/11.4, PR이 아닌 코드 이벤트는 3.11/10.2 추가), integration-full.yml integration-full(5 × 4) 고위험 PR, PR이 아닌 코드 이벤트, 수동 실행과 릴리스
SQLAlchemy 다이얼렉트 컴플라이언스(차단) ci.yml integration-tests: 3.14/11.4의 CUBRIDdb 레인, 두 셀의 릴리스된 pycubrid 레인, integration-full.yml sqlalchemy-compliance(같은 두 셀의 같은 레인) 고위험 PR(3.14/11.4), PR이 아닌 코드 이벤트(두 셀), 수동 실행과 릴리스(두 셀)
가장 오래된 셀의 CUBRIDdb SQLAlchemy 컴플라이언스(권고) ci.yml integration-tests(3.11/10.2) 최근 7일에 코드 변경이 있는 주간 실행, 코드 수동 실행
SQLAlchemy 프리릴리스 카나리(권고) ci.yml sqlalchemy-21-canary 주간 일정과 수동 실행
pycubrid 업스트림 카나리(권고, 보고) upstream-canary.yml 목요일 06:00 UTC와 수동 실행
드라이버 차등 비교 ci.yml integration-tests, integration-full.yml integration-full 고위험 PR, PR이 아닌 코드 이벤트, 수동 실행과 릴리스
CUBRID 버전 차등 비교 integration-full.yml version-differential 수동 실행과 릴리스
퍼즈와 메타모픽 make integration 안의 dev 프로필, integration-full.yml fuzz-bug-hunt의 nightly 프로필 PR이 아닌 코드 이벤트, 수동 실행과 릴리스
뮤테이션 테스트(비차단) integration-full.yml mutation-testing 수동 실행만, 릴리스 호출에서는 건너뜀(#747)

통합 정리

  • ci.yml에서 live-smoke를 제거했습니다. 이 레인은 Python 3.12, CUBRID 11.4, cubrid+pycubrid://로 test/test_integration.py와 test/test_regression.py를 실행했습니다. 두 파일 모두 integration 표시가 있으므로, 같은 SHA의 make-integration이 같은 Python, 서버 버전, URL로 그 테스트를 모두 실행합니다. 그 환경은 패키지의 상위 집합(.[pycubrid]와 Alembic을 포함하는 dev 의존성 그룹)을 설치하고 더 엄격한 check_not_all_skipped 가드를 씁니다. 잃는 단언은 없습니다. 제거된 잡은 개발 도구 없이 .[pycubrid,alembic] pytest만 설치했고 make-integration은 .[pycubrid]와 dev 그룹을 쓰지만, packaging-smoke-test가 최소 설치에서 패키지 임포트를 확인할 뿐 dev 전용 패키지 없이 스모크 테스트를 실행하는 것은 검증하지 않습니다. 이 절충은 수용합니다. integration-tests는 유지합니다. CUBRIDdb 레인, 컴플라이언스 스위트, 드라이버 차등 비교, 트랜잭션 DDL, 서버 재시작 단계는 이 잡에만 있습니다.
  • sqlalchemy-21-canary는 변경 내용과 관계없이 주간 일정과 수동 실행에서만 실행되며 continue-on-error를 유지합니다. 이 카나리는 이 저장소의 커밋과 무관하게 바뀌는 업스트림 SQLAlchemy 프리릴리스를 감시하며, 표본에서 offline-tests가 이미 보고하지 않은 실패를 더한 적이 없습니다.
  • 가장 오래된 integration-tests 셀(Python 3.11, CUBRID 10.2)의 권고용 CUBRIDdb 컴플라이언스 단계는 코드 변경이 필요한 integration-tests 안에서 실행되므로, 최근 7일에 코드 변경이 있는 주간 실행과 코드 수동 실행에서만 실행됩니다. continue-on-error이므로 푸시를 차단한 적이 없고, 같은 셀의 차단용 릴리스된 pycubrid 컴플라이언스 레인은 여전히 모든 코드 푸시에서 실행됩니다.
  • upstream-canary.yml은 월요일 ci.yml 일정과 겹치지 않도록 월요일 06:00 UTC에서 목요일 06:00 UTC로 옮겼습니다. 이 잡은 pycubrid@main을 설치하므로 주간 ci.yml 스위트와 다르며, 둘 다 유지합니다.
  • PR은 바뀌지 않습니다. 일반 PR은 여전히 Python 3.12 오프라인 셀 하나만 실행하고 라이브 레인은 없으며, #734의 3.11/3.15 셀은 PR에서 실행되지 않습니다.

위의 잡별 중앙값으로 계산한 예상 러너 분:

범주 잡 수 전 → 후 러너 분 전 → 후 올림 분 전 → 후
일반 코드 PR 9 → 9 1.9 → 1.9 9 → 9
고위험 PR 13 → 13 10.2 → 10.2 19 → 19
main 코드 푸시 17 → 15 19.3 → 약 16.8 29.5 → 약 26
주간 ci.yml 일정 17 → 16 21.2 → 약 19.8 32 → 약 30
업스트림 pycubrid 카나리 2 → 2 1.9 → 1.9 4 → 4
릴리스 35 → 37 82.4 → 약 83.9 103.5 → 약 105.5

코드 변경이 없는 주간 실행도 이제 이전에는 실행하지 않던 SQLAlchemy 카나리에 약 0.8 러너 분을 씁니다. 이 값은 측정이 아닌 예상치이므로, 절감을 주장하기 전에 이후 몇 주의 Actions 데이터와 비교하세요.

릴리스 증거

릴리스 커밋은 main 커밋이며, 그 ci.yml 푸시 실행이 Python 3.11/3.14 오프라인 증거(#734)의 유일한 출처였습니다. 릴리스 경로는 그 실행의 성공을 요구하지 않았고, 이후 병합이 푸시 동시성 그룹을 통해 그 실행을 취소할 수 있었습니다. 이제 integration-full.yml이 정확한 릴리스 SHA에서 같은 두 셀을 offline-endpoints로 실행하고, full-matrix-result는 그 셀이 성공하지 않으면 실패합니다. 릴리스마다 약 1.5 러너 분이 반복됩니다. 대신 푸시 실행을 요구하려면 (적어도 그만큼 러너를 붙잡는) 폴링 잡, cubrid-lab 저장소 사이에서 동일하게 유지하는 릴리스 워크플로의 actions: read 권한, 그리고 취소되지 않는 푸시 실행이 필요했고, 마지막 것은 위에서 측정한 대체된 취소된 실행 16건을 끝까지 실행하게 했을 것입니다.

같은 이유가 이제 나머지 main 푸시 증거에도 적용됩니다(#737, 옵션 b). integration-full.yml은 ci.yml typecheck, alembic-compat, packaging-smoke-test(pycubrid extra 및 Alembic 검사 포함)의 사본과 sqlalchemy-compliance 잡도 실행합니다. 이 잡은 Python 3.14/CUBRID 11.4에서 차단 CUBRIDdb 레인을, 3.14/11.4와 3.11/10.2 두 셀에서 차단 릴리스된 pycubrid 레인을 실행하며, 각 레인은 test/known_failures.txt 기준선을 캡처한 SQLAlchemy 버전에 고정됩니다. 이 잡들은 릴리스 SHA를 체크아웃하고, 변경 경로로 선택되지 않으며, 모든 integration-full.yml 실행(수동 실행과 릴리스 호출)에서 실행됩니다. full-matrix-result는 이들을 needs에 두고 각각이 성공하지 않으면 실패하므로, 실패·취소·건너뜀 레인은 호출된 워크플로를 필요로 하는 publish-pypi.yml의 build와 publish 잡을 막습니다. 가장 오래된 셀의 권고용 CUBRIDdb 컴플라이언스 레인은 아무것도 차단하지 않으므로 반복하지 않습니다. publish-pypi.yml은 바뀌지 않습니다. 이 워크플로는 cubrid-lab 저장소 사이에서 동일하게 유지되며, 이 때문에 옵션 (a)(actions: read와 필수 ci.yml 푸시 실행)를 채택하지 않았습니다. 사본은 릴리스마다 짧은 잡 세 개(위의 중앙값으로 약 1.4 러너 분)와 라이브 컴플라이언스 셀 두 개를 추가하며, test/test_ci_policy.py가 그 단계를 ci.yml 원본과 같게 유지합니다.

릴리스 커밋 푸시

릴리스 커밋은 main에 한 번 푸시되고, 그 SHA에서 두 워크플로가 시작합니다. ci.yml의 푸시 실행과, matrix 잡이 integration-full.yml을 호출하는 publish-pypi.yml입니다. 릴리스 호출은 이미 main 푸시의 모든 런타임 레인을 실행합니다. Python 3.11/3.15 오프라인 셀, 타입 검사, alembic-compat, 패키징 스모크 테스트, make integration(두 드라이버, CUBRID 10.2와 11.4), 차단 컴플라이언스 레인, 20개 Python × CUBRID 셀 모두이며, fail-closed인 full-matrix-result 뒤에 있습니다. 표본의 릴리스 커밋 세 개(1.9.0, 1.10.0, 1.11.0)에서 푸시 실행은 이 작업을 반복하며 15.0~19.5 러너 분을 썼습니다(통합 정리 후 첫 릴리스인 1.11.0에서 잡 8개, 14.1 러너 분, 올림 18분).

그래서 ci.yml의 detect-changes는 main 푸시 이벤트에서 scripts/release_detect.py --mode push를 실행합니다. publish-pypi.yml의 detect 잡과 같은 스크립트, 같은 모드, 같은 SHA입니다. 릴리스를 선택하면(build=true) code, live, extended 출력이 false가 되어 typecheck, offline-tests, alembic-compat, packaging-smoke-test, integration-tests, make-integration은 의도적으로 선택되지 않고, matrix-result는 그 건너뜀을 받아들입니다. 린트, 문서 린트, 사이트 빌드, 저장소 도구 테스트는 각자의 선택을 그대로 따릅니다. 판정이 실패하면 detect-changes와 필수 게이트가 실패하므로, 릴리스 푸시가 실수로 녹색으로 보고되지 않습니다.

integration-full.yml이 실행하지 않던 integration-tests의 두 단계, "Run pycubrid sync integration tests"와 "Run regression tests"는 이제 그 워크플로의 Python 3.11/CUBRID 10.2와 Python 3.14/CUBRID 11.4 셀에서도 같은 명령과 URL로 실행합니다(3.11 행은 가장 오래된 ci.yml 셀처럼 이미 SQLAlchemy 2.0을 고정합니다). main 푸시에서 셀당 중앙값 42초와 1초가 걸렸으므로, 릴리스는 약 1.4 러너 분이 늘고 릴리스 커밋마다 푸시 실행은 약 14 러너 분이 줄어듭니다. test/test_ci_policy.py는 복사한 단계를 ci.yml 원본과, 그 셀을 푸시 매트릭스와 같게 유지합니다.

릴리스 검증은 fail-closed이고 완전한 상태로 남습니다. full-matrix-result가 성공하지 않으면 아무것도 게시하지 않으며, 실패하거나 게시되지 않은 릴리스는 그 버전을 미출시로 남깁니다. 릴리스 커밋은 버전, CHANGELOG.md, 릴리스 메타데이터만 바꾸고, 릴리스 PR은 병합 전에 PR 계층을 실행했습니다. 일반 푸시, 주간 스케줄, 수동 디스패치는 바뀌지 않습니다.

통합 정리 후 측정

2026-10-12에 같은 방법(actions/runs와 runs/{id}/jobs, 모든 시도, 건너뛴 잡 제외)으로 측정했습니다. "이전"은 2026-10-03부터 #760 병합(2026-10-09 03:19 UTC) 까지이고, 릴리스 행은 #797 병합(2026-10-10 23:56 UTC) 이전 전체입니다. "이후"는 2026-10-12까지입니다. 표본이 짧고, 같은 기간에 다른 CI 변경도 여럿 병합되어 표 아래에 적었습니다.

범주 이전: 기간(실행 수) 이전: 러너 / 올림 분 중앙값 이후: 기간(실행 수) 이후: 러너 / 올림 분 중앙값 이후: 실패 / 취소
문서만 변경한 PR 2026-10-03..09 (15) 1.0 / 7 2026-10-10..12 (29) 1.0 / 8 1 / 2
일반 코드 PR 2026-10-03..06 (17) 2.0 / 9 2026-10-09..12 (39) 3.8 / 11 2 / 2
위험 PR 2026-10-03..09 (66) 10.4 / 19 2026-10-09..12 (65) 9.4 / 18 0 / 15
main 푸시, 코드 2026-10-03..09 (48) 18.9 / 28.5 2026-10-09..12 (47) 17.0 / 27 0 / 4
주간 ci.yml 스케줄 2026-10-05 (1) 21.2 / 32 2026-10-12 (1) 18.8 / 29 0 / 0
업스트림 pycubrid 카나리 2026-09-21..10-08 (13) 2.0 / 4 아직 없음(첫 목요일 실행 2026-10-15) 측정 안 함 측정 안 함
릴리스(publish-pypi.yml 푸시) 2026-10-04, 10-09 (2) 93.8과 143.9 / 118과 185 2026-10-12 (1) 103.8 / 133 0 / 0
릴리스 리허설(publish-pypi.yml dry-run 디스패치) 없음 측정 안 함 2026-10-11 (5) 92.8 / 118 0 / 0
integration-full.yml 디스패치 2026-10-04..09 (8) 73.4 / 85.5 2026-10-10..12 (11) 82.1 / 102 0 / 1
  • main 푸시는 중앙값 약 1.9 러너 분 줄었습니다. live-smoke와 푸시 시점 SQLAlchemy 카나리가 없어졌고, #734의 두 번째 오프라인 셀이 그 일부를 되가져 갔습니다. 주간 실행은 2.4 러너 분 줄었습니다. main 푸시 실패는 9에서 0이 되었고, 취소된 실행은 이전처럼 더 새 실행에 대체된 것입니다.
  • 일반 코드 PR이 늘어난 것은 #742(2026-10-08)가 모든 PR에서 전체 오프라인 스위트를 실행하고 #786이 해당 PR에서 문서 사이트를 빌드하기 때문입니다. 둘 다 이번 통합 정리의 일부가 아니며, 실패를 main에서 PR로 옮깁니다.
  • integration-full.yml은 잡 24개에서 35개로 늘었습니다. #797이 릴리스 복사본 다섯 개(약 5.2 러너 분)를, #807(2026-10-11)이 Python 3.15 행(셀 4개)을 추가했습니다. 이전의 10-09 릴리스에는 재실행 시도가 포함됩니다.
  • 이는 공개 저장소의 GitHub 호스트 러너에서 쓴 러너 분이며 청구 금액이 아닙니다. 이번 변경으로 앞으로의 릴리스 커밋마다 위에서 측정한 푸시 실행의 런타임 레인(약 14 러너 분)을 아끼고, 릴리스에 약 1.4 러너 분이 더해집니다.

오프라인 Python 엔드포인트

offline-tests 매트릭스는 github.event_name으로 선택됩니다(#734). PR은 일반이든 고위험이든 대표 Python 3.12 셀 하나를 실행합니다. 그 밖의 모든 ci.yml 이벤트 (main 푸시, 월요일 일정, 수동 workflow_dispatch)는 3.12 대신 지원하는 가장 오래된 Python과 최신 Python인 3.11과 3.15를 실행합니다. 그 사이의 지원 범위는 이 엔드포인트와 PR의 3.12 증거로 다루며, 3.13과 3.14에는 일상 오프라인 셀이 없습니다(3.14는 모든 고위험 PR과 전체 매트릭스에서 계속 라이브로 실행됩니다). 두 종류의 실행 모두 같은 선택(-m "not integration and not repo"), 95% 커버리지 기준, detect-changes의 code 출력을 사용합니다. 문서만 바뀐 푸시나 코드 변경이 없는 주에는 오프라인 셀을 하나도 선택하지 않습니다. main에서의 workflow_dispatch는 비교할 이전 푸시가 없어 paths-filter가 마지막 커밋만으로 셀을 고르므로, 문서만 바뀐 병합 직후에 수동 실행하면 오프라인 셀이 실행되지 않습니다. 각 셀은 자체 coverage-report-py<version> 아티팩트를 올리므로 셀끼리 이름이 충돌하지 않습니다.

이벤트 offline-tests 셀
pull_request Python 3.12
main push, schedule, workflow_dispatch Python 3.11과 3.15

매트릭스는 fail-fast: false를 사용하므로 한 인터프리터가 실패해도 다른 쪽의 증거가 취소되지 않습니다. matrix-result는 잡의 집계 결과를 읽습니다. 실패하거나 취소된 셀이 있으면 결과가 성공이 아니게 되고, code가 선택됐는데 생략되면 예상치 못한 결과이므로 어느 경우든 필수 게이트가 실패합니다. 예전의 3.12 단일 셀과 비교하면 코드 푸시나 주간 실행마다 비슷한 길이의 오프라인 잡이 하나 늘고(잡 예산은 15분), PR은 바뀌지 않습니다. integration-full.yml은 릴리스 SHA에서 같은 두 셀을 offline-endpoints로 실행하고, full-matrix-result가 이를 요구합니다(#737, 릴리스 증거 참고). 저장소 도구 레인은 대표 Python 3.12 셀에 그대로 둡니다. test/test_ci_policy.py는 이벤트별로 매트릭스를 계산하고, 실패·취소·생략된 오프라인 결과로 게이트를 실행해 봅니다.

워크플로 변경 영향

변경 경로 선택은 워크플로별 영향 표를 따릅니다(#746). risk를 통해 라이브 PR 레인(integration-tests, alembic-compat, packaging)을 선택하는 것은 이를 정의하고 실행하는 ci.yml뿐입니다. .github/ 아래의 다른 워크플로는 문서가 아닌 변경이므로 전체 오프라인 스위트를 실행하고 저장소 도구 레인도 선택합니다. 둘이 합쳐 워크플로 파일을 읽는 모든 테스트를 다룹니다. ci.yml의 라이브 레인은 다른 워크플로의 잡을 실행하지 않으므로 이를 돌려도 검출력이 늘지 않습니다.

변경된 워크플로 PR 검증
ci.yml 정의한 모든 레인과 도구 레인, 오프라인 스위트
integration-full.yml, upstream-canary.yml, python-canary.yml 도구 레인과 오프라인 스위트, 그리고 PR head에서 해당 워크플로를 수동 workflow_dispatch로 실행하고 PR에 링크
publish-pypi.yml, release-please.yml 도구 레인과 오프라인 스위트(릴리스 워크플로 테스트)
그 밖의 워크플로 도구 레인과 오프라인 스위트. pr-title.yml, docs-sync.yml, codeql.yml, security.yml은 PR에서 스스로도 실행됨

test/test_workflow_path_impact.py가 모든 워크플로 파일과 대표 경로에 대해 필터를 평가하고, 워크플로 파일을 (리터럴 경로, 분할 경로, 상수로) 읽는 테스트 모듈에 integration 표시가 있어 그런 PR에서 실행되지 않으면 실패합니다.

병렬 라이브 레인

integration-tests와 make-integration은 detect-changes에만 의존하므로(#744), lint, 타입 검사, 오프라인 테스트가 끝난 뒤가 아니라 함께 시작합니다. matrix-result는 여전히 모든 잡을 요구하므로 라이브 레인이 통과해도 lint, 타입, 오프라인 실패가 있으면 필수 검사는 실패합니다. 대신 lint에 실패한 PR도 라이브 레인 러너 시간을 쓸 수 있습니다.

CUBRIDdb 드라이버 빌드 캐시

CUBRIDdb 레인은 cubrid-python v11.3.0.51을 wheel로 빌드해 캐시합니다(#745). wheel은 CCI를 정적으로 링크하고(libcascci.a) libc와 libstdc++만 필요하므로, 캐시 키는 러너 OS와 아키텍처, 러너 이미지(ImageOS), Python ABI(SOABI, 예: cpython-314-x86_64-linux-gnu), 그리고 태그가 아닌 해석된 소스 커밋입니다. CUBRID 서버 버전은 키에 포함되지 않으므로 같은 Python 버전의 서버 셀들이 wheel을 공유합니다. 적중하면 CCI 컴파일과 build-essential/cmake 설치를 건너뛰고, 적중하지 않으면 uv build로 wheel을 빌드해 잡이 성공하면 저장합니다. CPython은 마이너 버전 안에서 ABI를 안정적으로 유지하지만 러너 이미지마다 패치 버전이 다르므로, 키에는 패치 버전 대신 ABI를 씁니다. 빌드 절차나 툴체인이 바뀌면 cubriddb-wheel-v 접두사를 직접 올려야 하며, 세 곳의 빌드 절차는 동일하게 유지해야 합니다. 적중을 증거로 보지 않습니다. 매 실행이 wheel을 설치하고 CUBRIDdb를 import하며, 준비 확인 단계가 스위트 실행 전에 실제 서버에 연결합니다. test/test_workflow_cubriddb_cache.py가 키 구성 요소와 적중 시에만 빌드를 건너뛰는지를 검증합니다.

잡 타임아웃

실행되는 모든 잡은 정수 timeout-minutes를 지정합니다(GitHub 기본값은 360분). 따라서 멈춘 컨테이너, 소켓, 드라이버 빌드, 설치가 러너를 6시간 동안 붙잡지 않고 예산 안에서 실패합니다(#741). 예산은 Actions에서 관측된 최대 실행 시간의 약 3–5배이며 하한을 둡니다. 게이트와 작은 잡은 5분, lint/type/오프라인/Alembic/카나리 잡은 10–15분, make integration과 업스트림 카나리 통합 잡은 20분, 라이브 통합 매트릭스는 30분(CUBRIDdb 소스 빌드와 컴플라이언스 스위트를 포함한 관측 최대 7.1분), 뮤테이션 테스트는 60분(관측 최대 18.1분)입니다. 180분 상한을 넘는 예외가 필요한 잡은 없습니다. 집계 게이트(matrix-result, full-matrix-result)는 if: always()와 짧은 타임아웃으로 실행됩니다. 타임아웃된 의존 잡은 성공이 아닌 결과 (cancelled/failure)로 보고되며 게이트는 이를 실패로 처리합니다.

재사용 워크플로를 호출하는 잡에는 timeout-minutes를 지정할 수 없습니다. 저장소 내부 피호출 워크플로(publish-pypi.yml → integration-full.yml)는 그 워크플로의 잡에서 검증합니다. 외부 소유 피호출 워크플로는 test/test_workflow_timeouts.py의 명시적 허용 목록입니다. cubrid-lab/.github의 공유 doc-lint, live-smoke(integration-full.yml의 fuzz 버그 탐색에서 사용), codeql 워크플로와 cookbook 스모크 테스트가 여기에 해당합니다. 이 테스트는 모든 워크플로를 파싱하여 실행 잡에 제한된 타임아웃이 없거나, 새 외부 호출자가 허용 목록에 없거나, 릴리스 게이트(full-matrix-result)가 성공이 아닌 의존 결과에서 실패하지 않으면 실패합니다. matrix-result는 test/test_ci_policy.py에서 검증합니다.

문서 도구, 스캔 동시성, pycubrid 하한

  • 문서 도구 고정 (#761). docs.yml과 ci.yml의 docs-build 작업(아래 참고)이 mkdocs build --strict를 실행하며, docs.yml은 mkdocs, mkdocs-material, pymdown-extensions를 정확한 버전이 고정된 .github/docs-requirements/requirements.txt에서 설치합니다. /.github/docs-requirements용 Dependabot pip 항목이 업데이트를 제안하므로, 업스트림 릴리스가 저장소 변경 없이 엄격 빌드를 깨뜨릴 수 없습니다.
  • 풀 리퀘스트의 문서 사이트 빌드 (#786). ci.yml이 병합 전에 사이트를 빌드하므로, mkdocs build --strict를 깨뜨리는 문서 수정이나 문서 도구 버전 갱신은 나중에 main이 아니라 풀 리퀘스트에서 실패합니다. docs-build 작업은 site 경로 필터로 선택됩니다: docs/**(콘텐츠, 번역, 자산), mkdocs.yml, scripts/generate_llms_full.py, MkDocs 훅 scripts/docs_provenance_hook.py, .github/docs-requirements/**, .github/workflows/docs.yml, .github/workflows/ci.yml. 루트 *.md 파일은 사이트 입력이 아니므로 이 작업을 선택하지 않으며, workflow_dispatch는 다른 레인처럼 이 작업을 강제로 선택합니다. 이 작업은 docs.yml의 빌드 단계(고정된 uv로 .github/docs-requirements/requirements.txt 설치, scripts/generate_llms_full.py, mkdocs build --strict)를 읽기 전용 권한, 고정된 액션, 10분 타임아웃, persist-credentials: false로 실행하며 Pages 아티팩트를 올리거나 배포하지 않습니다. matrix-result 게이트는 site가 선택된 경우에만 docs-build의 성공을 요구하고 그 외에는 건너뜀을 허용합니다. test/test_ci_policy.py, test/test_workflow_path_impact.py, test/test_workflow_hygiene.py가 이를 검증합니다.
  • 결정적 문서 게이트 (#908). docs-check 작업은 추적되는 모든 Markdown 파일에 scripts/check_docs_links.py와 scripts/check_agents_docs.py를 실행합니다. docs 필터(**/*.md, docs/**) 또는 tooling 필터(scripts/**, Makefile 등)가 선택될 때 실행되는데, 스크립트나 make 타깃을 제거하면 그 이름을 쓰는 문서가 깨지기 때문입니다. 다음 경우에 file:line과, 비슷한 후보가 있으면 수정 제안을 출력하며 실패합니다.
  • 대상이 없거나, 절대 경로이거나, 저장소 밖을 가리키는 상대 Markdown 또는 HTML (<img src>, <a href>, <source src>) 링크
  • 대상 파일의 제목(GitHub 또는 사이트 슬러그, {#id})이나 <a id>/<a name>이 아닌 #앵커
  • 정의가 없는 참조 링크 [text][label], 닫히지 않은 코드 펜스
  • docs/ 아래에서 #과 숫자로 시작하는 문단 줄(줄바꿈된 이슈 번호 등, 목록 항목이나 인용 블록 안 포함). MkDocs 사이트가 제목으로 렌더링하므로(#920) \#750처럼 씁니다.
  • 인라인 코드나 펜스 블록에 쓴 make 목표 중 Makefile에 없는 것, 존재하지 않는 scripts/ 경로. 릴리스 기록(CHANGELOG.md, RELEASE_CHANGELOG.md)은 당시의 명령을 그대로 두므로 링크만 확인합니다.

Markdown은 CommonMark로 파싱하므로(.github/docs-requirements/requirements.txt에 고정된 markdown-it-py) 코드 안의 링크는 예시로, URL·제목·HTML 주석 안의 대괄호는 참조가 아닌 것으로, 따옴표 안의 텍스트와 리디렉션은 make 목표가 아닌 것으로 취급합니다. 검토한 오탐은 보고된 줄이나 그 앞 줄에 <!-- docs-check: ignore -->로 표시합니다. 이 작업은 네트워크 연결을 열지 않으며 검사는 약 1초 걸립니다. matrix-result 게이트는 두 필터 중 하나가 선택된 경우에만 docs-check의 성공을 요구하고 그 외에는 건너뜀을 허용하므로, 문서만 바꾼 풀 리퀘스트는 여전히 CUBRID 런타임 레인을 건너뜁니다. 지원 문구의 기준은 계속 scripts/check_support_claims.py이며 docs-check는 지원 주장을 하지 않습니다. 공유 doc-lint 워크플로는 권고용으로 남습니다(fail-on-issue: false). #918 헤드 (2026-10-15)의 결과가 그 이유입니다. markdownlint는 스타일 결과 2,207건(그중 1,530건이 표 스타일 MD060)을 보고했고, Mermaid 검사는 0건, lychee는 --exclude-mail 인수에서 종료되어 링크를 하나도 확인하지 않았습니다. fail-on-issue: true로 바꾸면 스타일 결과가 게이트가 되면서도 링크는 여전히 확인하지 않습니다. test/test_check_docs_links.py에 통과·실패 픽스처가 있습니다. - 스캔 동시성 (#762). codeql.yml과 security.yml은 그룹 ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}, cancel-in-progress: ${{ github.event_name == 'pull_request' }}로 concurrency를 설정합니다. 풀 리퀘스트에서는 새 푸시가 이전 스캔을 취소하지만, 그 외 이벤트는 실행마다 고유한 그룹을 받습니다(같은 그룹에서는 cancel-in-progress가 없어도 대기 중인 실행이 대체됨). 따라서 main 푸시와 주간 예약 실행은 취소되거나 누락되지 않습니다. 스캔 범위는 바뀌지 않습니다. - 선언된 pycubrid 하한 (#764). pyproject.toml은 pycubrid>=1.8.0,<2.0을 선언하고, ci.yml의 게이트 컴플라이언스 레인은 pycubrid==1.8.0을 설치합니다(통합 잡은 최신 릴리스를 설치하므로 하한을 검증하는 유일한 레인). test/test_workflow_hygiene.py는 고정 버전과 선언된 하한이 다르면 실패하므로, 추가 CI 잡 없이 한쪽만 올리는 변경을 오프라인에서 잡아냅니다. 그 뒤 잡의 마지막에 실행되는 cub_server 재시작 단계는 최신 릴리스로 다시 올리므로(--upgrade-package pycubrid, 일반 재설치는 1.8.0을 유지함) 다른 통합 단계와 같은 pycubrid를 테스트합니다. test/test_ci_policy.py가 이를 고정합니다(#479). - 표현 (#747). integration-full.yml에는 일정이 없습니다. fuzz-bug-hunt와 mutation-testing 잡 이름에서 "nightly"를 뺐습니다(nightly는 Hypothesis 프로파일 이름으로 유지). 게이트 동작은 그대로입니다. 퍼즈는 릴리스를 게이트하고 뮤테이션 테스트는 비차단입니다. 필수 상태 검사는 이 잡 이름을 참조하지 않습니다. - 릴리스 경로에서 뮤테이션 테스트 제외 (#747). publish-pypi.yml은 integration-full.yml을 호출하고 그 build 잡은 호출된 워크플로 전체를 기다리므로, 비차단 mutation-testing 잡은 릴리스를 막을 수 없으면서도 실행 시간만큼(과거 측정 중앙값 9.6분, 최대 18.1분) 모든 릴리스를 늦췄습니다. 이제 이 잡에는 if: ${{ !inputs.sha }}가 있습니다. 릴리스 호출은 필수 입력 sha를 항상 넘기므로 이 잡을 건너뛰고, integration-full.yml을 직접 수동 실행하면(입력 없음) 계속 실행되어 점수를 보고합니다. 경로는 이 명시적 입력으로 고르며 github.event_name으로 고르지 않습니다. 호출된 워크플로에서 이 값은 호출자의 이벤트(push 또는 workflow_dispatch)라서 릴리스 호출과 수동 실행을 구분하지 못합니다. Hypothesis 퍼즈 패스는 그대로 릴리스를 게이트합니다. full-matrix-result는 원래 뮤테이션 잡을 필요로 하지 않으므로, 건너뛴 실행은 게이트를 실패시키지도, 필수 잡의 통과로 간주되지도 않습니다. publish-pypi.yml은 바뀌지 않습니다(정본 사본과 동일하게 유지). test/test_ci_policy.py가 이 조건, 어떤 잡도 뮤테이션 테스트를 기다리지 않는다는 점, 릴리스 호출에서도 퍼즈가 실행된다는 점을 고정합니다.

의존성 설치

ci.yml, integration-full.yml, upstream-canary.yml은 uv로 의존성을 설치합니다 (#743). 패키지를 설치하는 각 잡은 actions/setup-python 다음에 커밋 SHA로 고정한 astral-sh/setup-uv를 실행하고 uv 자체도 고정합니다(version: "0.12.17"). 그다음 uv pip install --system으로 그 인터프리터에 설치하고, 결과를 uv pip freeze --system으로 기록합니다. setup-uv에는 setup-python과 같은 python-version 지정(예: "3.12" 또는 매트릭스 값)을 넘기므로, 캐시 키에는 러너 이미지마다 달라 키 불일치를 일으키는 uv python find의 패치 버전 대신 잡의 Python 마이너 버전이 들어갑니다. pyproject.toml 해시와 잡별 cache-suffix를 함께 써서 잡과 Python 버전마다 별도 캐시를 유지합니다. 같은 입력이 UV_PYTHON을 내보내며, uv pip install --system은 PATH에서 처음 일치하는 인터프리터, 즉 setup-python의 인터프리터를 고릅니다. 일상 CI와 카나리는 항상 캐시하고(enable-cache: true), 릴리스 게이트인 integration-full.yml은 auto를 사용하며, 이는 태그 푸시, release, pull_request_target, workflow_run 이벤트에서만 캐시를 끕니다. 따라서 릴리스 경로 (main 푸시에서 실행되는 publish-pypi.yml 또는 복구용 수동 실행)는 여전히 캐시를 복원합니다. uv 캐시에는 내려받거나 빌드한 wheel만 들어 있고 매 실행이 같은 제약에서 다시 해석하므로, 캐시는 설치를 빠르게 할 뿐 해석된 버전을 바꿀 수 없어 안전합니다. 바뀌는 것은 설치 도구뿐입니다. 전환 전에 Python 3.12에서 .[dev], .[dev,alembic], .[dev,pycubrid]를 pip와 uv로 해석한 결과는 같은 패키지 집합이었습니다(PEP 503 이름 정규화 후 각각 73, 73, 74개). 고정된 SQLAlchemy와 pycubrid 컴플라이언스 설치는 정확한 핀을 유지하고, SQLAlchemy 프리릴리스 카나리는 --upgrade-package SQLAlchemy --prerelease=if-necessary-or-explicit로 SQLAlchemy만 업그레이드하도록 요청합니다. 이 옵션은 최신 SQLAlchemy 프리릴리스 설치를 보장하지 않으며, 안정 버전 2.1.x가 2.2 베타보다 우선 선택될 수 있습니다. #778에서 실제 공개된 2.2 프리릴리스를 대상으로 설치 버전을 검증할 예정이며, 검증 전까지 카나리 성공을 2.2 호환성 증거로 해석하지 않습니다. 가장 오래된 셀의 SQLAlchemy 2.0 고정 단계는 고정된 버전을 기록합니다. 일반 pip는 그것이 목적인 곳에 남습니다. 패키징 스모크 가상 환경은 빌드된 wheel과 sdist가 최종 사용자 도구로 설치되는지 증명하고, 외부 live-smoke 재사용 워크플로는 자체 설치 명령을 받습니다. test/test_workflow_installs.py가 이를 검증합니다.

Dependabot 그룹

이전에는 Dependabot이 pip(/), pip(/.github/docs-requirements), GitHub Actions의 패키지마다 PR을 하나씩 열었고, PR마다 PR 계층 검사가 실행되었습니다. 이제 .github/dependabot.yml은 minor와 patch 버전 업데이트를 생태계와 디렉터리별로 PR 하나에 묶으므로(dev-tools, docs-tools, github-actions), 주간 업데이트 묶음은 그룹당 CI 실행 한 번으로 끝납니다(#737). 다음은 여전히 별도 PR로 열립니다.

  • Major 업데이트는 모든 그룹에서 제외되며(update-types는 minor와 patch뿐), dependabot-auto-merge.yml은 여전히 사람의 리뷰를 위해 보류합니다.
  • 보안 업데이트는 그룹이 applies-to: version-updates로 설정되어 있어 분리됩니다.
  • SQLAlchemy, pycubrid, Alembic, CUBRID-Python은 dev-tools에서 제외됩니다. 이들은 도구 업데이트와 호환성 위험이 다르므로 업데이트마다 따로 리뷰할 수 있어야 합니다.

그룹은 아무것도 우회하지 않습니다. 그룹 PR도 같은 필수 체크를 거치며, 체크가 통과해야 자동 병합이 완료됩니다. dependabot/fetch-metadata는 그룹 PR의 update-type으로 가장 높은 semver 변경을 보고하므로, 그룹이 major 업데이트를 자동 병합으로 가져갈 수 없습니다. 패키지 하나가 실패하면 그룹 전체가 막힙니다. 그룹 PR에서 고치거나, 별도 작업이 필요하면 리뷰된 PR로 해당 패키지를 그룹의 exclude-patterns에 추가해 Dependabot이 단독 PR로 열게 하세요. test/test_workflow_hygiene.py가 그룹 설정을 검증합니다.

2026-10-10의 첫 그룹 주간 실행은 네 워크플로의 actions/upload-artifact와 actions/download-artifact에 대해 PR 하나, #793("bump the github-actions group with 2 updates")을 열었습니다. 2026-09-21에는 같은 두 액션에 PR 두 개(#451, #452)가 필요했습니다. 필수 검사를 통과한 뒤 병합되었습니다. 고정된 cookbook smoke-test 워크플로의 SHA 갱신(#794, #809)은 마이너·패치 버전 갱신이 아니라 커밋 갱신이므로 그룹에 맞지 않아 여전히 각자 PR을 엽니다.

Python 3.15 검증 준비

python-canary.yml은 수동 전용입니다. 전체 SHA를 전달하고 그 커밋의 브랜치에서 실행하세요. Ubuntu/표준 GIL 레인 하나가 Python 3.15를 선택하며 (선택기는 프리릴리스도 허용), 실제 인터프리터/의존성 버전을 출력하며, 전체 오프라인 회귀 검사를 실행하고 새로 설치한 wheel/sdist를 검증합니다. 설정/설치/테스트 실패는 평소처럼 실행을 실패시킵니다. 이 레인은 필수 PR 검사나 릴리스 게이트와 분리되어 있으며, 새 일정, PR 매트릭스 셀, CUBRID 서버 생성이 없습니다. 이 레인만으로는 공식 지원, 라이브 데이터베이스 호환성, free-threaded 호환성이 성립하지 않습니다.

GitHub는 수동 워크플로의 파일이 기본 브랜치에 있어야만 실행하므로, 이 레인은 병합되기 전에는 실행할 수 없습니다. 첫 실행은 병합된 커밋에서 이루어지고 추적 이슈에 기록됩니다. 일반 PR CI는 Python 3.15의 증거가 아닙니다.

이제 Python 3.15를 지원합니다(#666, 지원 매트릭스 참고). 지원 근거는 전체 호환성 매트릭스(CUBRIDdb와 pycubrid 레인을 포함한 Python 3.15 × CUBRID 4개 버전), 3.15 오프라인 엔드포인트 셀, 이 레인의 wheel/sdist 새 환경 설치 검사입니다. 이 레인은 수동 패키징 검사로 유지합니다. 대표 라이브 셀은 Python 3.14/CUBRID 11.4와 Python 3.11/CUBRID 10.2를 유지하며, SQLAlchemy 컴플라이언스 레인도 이 두 셀에 그대로 둡니다.