종속성 고정 매니페스트
Python 런타임 범위, 개발 도구, 릴리스 도구 잠금 파일, 각 빌드 표면에 대해 비준된 버전 하한을 다루는 종속성 선언 정책.
Apothem이 종속성을 선언하고, 고정하고, 잠그는 방식에 대해 비준된 태세. 대상: 재현성 모델을 이해해야 하는 기여자 및 다운스트림 소비자.
태세
Apothem은 작은 런타임 종속성 집합과 더 큰 개발·감사·릴리스·문서 도구 체인을 갖춘
Python CLI입니다. 런타임 종속성은 pyproject.toml에서 보수적인 하한을 사용합니다.
웹사이트는 site/package-lock.json을 커밋합니다. GitHub Actions는 버전 주석과 함께
불변 SHA로 고정되며 Dependabot이 버전을 올립니다.
| 구분 | 선언 | 잠금 정책 |
|---|---|---|
| 런타임 Python 종속성 | pyproject.toml의 [project.dependencies] | 사용자용은 하한 범위; 패키지에는 Python 런타임 잠금이 포함되지 않음. CI는 이 종속성들의 해시 잠금 클로저를 설치(CI 종속성 잠금 참조) |
| 개발 / 보안 도구 | [project.optional-dependencies] extras | 하한 범위. 단, ruff와 mypy는 예외로, dev extra가 CI에서 실행하는 버전으로 정확히 고정함(ruff는 CI 도구 잠금 참조); CI는 dev extra를 해시 잠금 클로저에서 설치(CI 종속성 잠금 참조) |
| 릴리스 도구 | 릴리스 워크플로용 scripts/release/requirements-build-linux.txt; 로컬 빌드용 [project.optional-dependencies]의 release extra | 릴리스 워크플로용은 해시 잠금이며 릴리스 도구 클로저에서 생성됨. release extra는 하한 범위임 |
| CI pip | scripts/ci/requirements-ci-pip.txt | 해시 잠금: wheel과 sdist의 해시가 붙은 pip==X.Y.Z 고정 하나로, pip를 업그레이드하는 모든 워크플로가 --require-hashes -r로 설치함. Dependabot이 업그레이드를 제안하고 해시를 다시 씀; scripts/dev/check_pip_pin_drift.py가 고정을 pip 26.2 이상으로 유지함 |
| CI 종속성 잠금 | scripts/ci/requirements-ci-dev.txt, -runtime.txt, -test.txt, -prereqs.txt | 해시 잠금된 유니버설 클로저. uv pip compile로 pyproject.toml에서(-prereqs.txt는 엔진의 호스트 전제 조건에서) 컴파일함. 기준 시점은 잠금 날짜에서 7일 쿨다운을 뺀 시점이며, 둘 다 각 파일에 기록됨. --require-hashes -r로 설치함(패키지 자체는 --no-deps -e .로 설치). CI는 scripts/dev/refresh_ci_locks.py가 다시 생성할 때까지 잠긴 버전을 테스트함; pip-audit.yml은 허용되는 최신 버전과 잠금을 매주 감사함. 잠금이 더 이상 입력을 만족하지 않으면 tests/scripts/test_ci_locks_fresh.py가 실패함 |
| CI 도구 잠금 | scripts/ci/requirements-ci-ruff.txt, -bandit.txt, -pip-audit.txt, -pip-licenses.txt, -pre-commit.txt | 워크플로가 실행하는 도구마다 해시 잠금 클로저 하나. 같은 스크립트가 scripts/dev/ci_lock_table.py의 정확한 사양으로 컴파일하며, 잠금 날짜와 쿨다운이 같고, --require-hashes -r로 설치함. 도구 버전을 적는 워크플로는 없음. pre-commit은 Python 3.11 이상이 필요하므로 그 잠금도 마찬가지이고, pip-audit 잠금도 3.11용으로 컴파일함. 3.10을 지원하는 최신 filelock에 수정되지 않은 보안 권고가 있기 때문임. pip를 고정하는 잠금은 CI pip뿐임 |
| 문서 사이트 | site/package.json | site/package-lock.json이 커밋되고 npm ci가 사용 |
| VS Code 패키저 | tools/vsce/package.json | tools/vsce/package-lock.json이 커밋됨; 릴리스 및 게시 작업은 npm ci --ignore-scripts를 실행 |
| channel-smoke 하니스 CLI | tools/channel-smoke/package.json | tools/channel-smoke/package-lock.json이 커밋되고 npm ci가 사용 |
| GitHub Actions | 버전 주석이 있는 SHA 고정 uses: 참조 | Dependabot이 주간 업데이트를 제안; 예외는 action-pinning-exempt: 마커를 지님 |
릴리스 비준
| 표면 | 비준된 상태 | 근거 |
|---|---|---|
| 런타임 종속성 | 현재 하한은 pyproject.toml의 [project.dependencies]에 있음 | 릴리스 전에 비준된 현재 안정 하한. 알려진 런타임 CVE 차단 요소 없음 |
| 개발 도구 | 현재 하한은 pyproject.toml의 dev extra에 있음. ruff와 mypy는 그곳에서 정확히 고정됨 | 릴리스 전에 비준된 최신 안정 하한. ruff와 mypy는 훅, CI, 기여자를 위해 각각 한 버전으로 고정되고, mypy 훅은 나열한 라이브러리를 dev 잠금의 버전으로 고정함. 버전이 어긋나면 tests/scripts/test_ci_tool_locks.py가 실패함 |
| 보안 도구 | 현재 하한은 pyproject.toml의 security extra에 있음 | 릴리스 전에 비준된 현재 스캐너 하한 |
| 릴리스 도구 | 해시 잠금 클로저는 scripts/release/requirements-build-linux.txt에 있음 | 릴리스 워크플로는 --require-hashes로 설치합니다. 로컬 스크립트는 변동하는 종속성을 조용히 설치하는 대신 build가 이미 존재하기를 요구합니다 |
| CI pip | 해시 잠금 고정은 scripts/ci/requirements-ci-pip.txt에 있음 | pip 26.2 이상에서 CVE-2026-13346이 해결됩니다. 워크플로는 --require-hashes로 설치하므로, 다운로드는 게시된 해시와 대조됩니다 |
| CI 종속성 잠금 | 해시 잠금 클로저는 scripts/ci/requirements-ci-{dev,runtime,test,prereqs}.txt에 있음 | 워크플로는 --require-hashes로 설치합니다. 각 파일은 잠금 날짜, 쿨다운, 컷오프를 기록하고, 최신성 테스트가 이들이 pyproject.toml과 보조를 맞추도록 지킵니다 |
| CI 도구 잠금 | 해시 잠금 클로저는 scripts/ci/requirements-ci-{ruff,bandit,pip-audit,pip-licenses,pre-commit}.txt에 있음 | 워크플로는 --require-hashes로 설치하며 도구 버전을 적지 않습니다. 사양은 scripts/dev/ci_lock_table.py에 있고, 잠금 날짜, 쿨다운, 컷오프는 종속성 잠금과 같습니다 |
| 문서 도구 | 현재 범위는 site/package.json에 있고, 정확한 해석 결과는 site/package-lock.json에 있음 | 잠금 파일을 새로 고친 후 npm outdated가 오래된 문서 종속성을 반환하지 않음 |
| GitHub Actions | 현재 릴리스에는 기존 SHA 고정을 유지함. SLSA 재사용 가능 워크플로는 문서화된 태그 고정 예외로 남으며, release.yml의 action-pinning-exempt: 주석으로 표시됨 | 공개 CI 재실행 결과가 녹색입니다. 릴리스 표면의 변동을 피하려고 광범위한 action 메이저 업그레이드는 의도적으로 종속성 자동화 PR에 맡깁니다 |
| npm 패키징 | publish-npm.yml 워크플로는 v*.*.* 태그 푸시에서 실행되며, workflow_dispatch가 대체 수단임 | 유일한 공개 npm 패키지는 @ahmed-g-gad/apothem입니다. 게시는 운영자가 release 환경을 승인할 때까지 기다리며, 승인은 scripts/release/check-release-ready.sh가 통과한 뒤에 주어집니다 |
잠금
기여자가 직접 쓰는 Python 잠금 파일은 필요할 때 생성하며 커밋하지 않습니다. Apothem의
런타임 클로저가 작고 CLI가 여러 Python 마이너 버전을 지원하기 때문입니다. CI는
예외입니다. CI는 scripts/ci/의 해시 잠금 파일을 설치합니다. 종속성 잠금 네 개
(requirements-ci-dev.txt, -runtime.txt, -test.txt, -prereqs.txt)와
워크플로가 실행하는 도구별 잠금 하나씩(-ruff.txt, -bandit.txt, -pip-audit.txt,
-pip-licenses.txt, -pre-commit.txt)입니다. python scripts/dev/refresh_ci_locks.py는
아홉 개 모두를 잠금 날짜에서 Dependabot과 같은 7일 쿨다운을 뺀 시점 기준으로
컴파일합니다. 따라서 CI는 잠금 날짜에 공개된 지 최소 일주일이 지난 릴리스를 테스트하며,
파일을 새로 고치면 테스트하는 릴리스도 새로워집니다. 각 파일은 잠금 날짜, 쿨다운, 그
결과인 컷오프를 기록하고, --check는 그 컷오프를 재현합니다. 쿨다운보다 최신인 긴급
보안 수정에는 --cooldown-days 0으로 새로 고침을 실행하거나,
scripts/dev/ci_lock_table.py에 해당 패키지의 쿨다운 예외를 두어야 합니다. 이 예외는
그 패키지에 대해서만 쿨다운을 해제합니다. 매주 실행되는 pip-audit.yml은
pyproject.toml이 허용하는 최신 버전과 아홉 개의 잠금을 감사하며, OSV 서비스에 보안
권고를 조회합니다. 기여자는 계속 pip install -e ".[dev]"로 설치합니다.
# Astral uv:
uv pip install -e ".[dev,security,release]"
uv pip freeze > requirements.dev.lock
# pip-tools:
pip-compile --extra=dev --extra=security --extra=release -o requirements.dev.lock pyproject.toml
# Plain pip:
pip install -e ".[dev,security,release]"
pip freeze > requirements.dev.lock웹사이트는 다릅니다. site/package-lock.json이 커밋되고 CI가 npm ci를 사용하므로,
렌더링된 문서 사이트에는 재현 가능한 Node 클로저가 있습니다.
근거
- Python에 범위 고정을 쓰는 이유는? CLI는 광범위한 Python 매트릭스를 지원하므로,
pyproject.toml은 보수적인 하한을 유지합니다. CI는 이 범위의 해시 잠금 클로저를 설치하고, 메인테이너가scripts/dev/refresh_ci_locks.py로 이를 새로 고칩니다(매주가 의도한 주기입니다). 또한 매주 실행되는pip-audit.yml이 허용되는 최신 버전을 해석해 현재 도구와의 문제를 드러냅니다. - 커밋되는 Node 잠금을 쓰는 이유는? 웹사이트는 배포된 정적 산출물입니다. 잠금
파일은 그 공개 표면에 대해 결정론적
npm ci빌드를 제공합니다. - SHA 고정 Actions를 쓰는 이유는? 태그는 가변적이지만 SHA는 불변입니다. Dependabot은 SHA 상향을 제안하고 각 버전 주석을 다시 쓰며, 릴리스 준비 게이트는 의도적인 보류를 모두 기록합니다.
- 업데이트 봇이 건드리지 않는 것. Dependabot 버전 업데이트
(
.github/dependabot.yml)는 GitHub Actions 고정, 웹사이트 잠금 파일, vsce 패키저 잠금 파일(tools/vsce), channel smoke가 설치하는 하니스 CLI 잠금 파일 (tools/channel-smoke), CI가 설치하는 해시 잠금 pip (scripts/ci/requirements-ci-pip.txt), pre-commit 훅을 다룹니다.scripts/ci의 나머지 아홉 파일은scripts/dev/refresh_ci_locks.py로 통째로 다시 생성하는 클로저이며, 어느 것도 pip를 고정하지 않습니다. 이 폴더에 대한 Dependabot 버전 업데이트는 pip로 한정됩니다.pyproject.toml의 런타임 하한, 해시 잠금된 릴리스 요구 사항,src/apothem/_vendor/vendor.txt의 벤더링된 클로저,pyproject.toml과 보조를 맞춰 고정된 pre-commit 훅은 수동으로 갱신합니다. 고정 하나만 올리면 그것과 일치해야 하는 파일들이 깨지기 때문입니다. ruff와 mypy 훅이 그중 두 가지입니다.tests/scripts/test_ci_tool_locks.py는pyproject.toml의 정확한 ruff 고정, 훅 rev, ruff 잠금, dev 잠금이 같은 버전을 가질 때까지, 그리고 정확한 mypy 고정, mypy 훅 rev, 그 훅이 나열하는 라이브러리가 dev 잠금과 일치할 때까지 실패합니다. 따라서 두 도구 중 어느 쪽이든 Dependabot 보안 풀 리퀘스트는 수동으로 조정합니다. 웹사이트의@types/node메이저 버전은 Node 하한(engines와.nvmrc, Node 22)을 따르므로, Dependabot은 마이너 및 패치 업데이트만 제안합니다. 그 메이저 버전은 하한과 함께 올리세요. 버전 업데이트는 npmoverrides블록을 건드리지 않습니다. Dependabot 보안 업데이트는 여전히 모든 매니페스트를 다루므로, 보안 풀 리퀘스트가 이러한 고정 중 하나를 제안하거나 override를 옮길 수 있습니다. 수동으로 조정하거나 닫으세요. - Dependabot 풀 리퀘스트 병합. 저장소의 스쿼시 기본값은 PR 제목을 사용합니다.
일부 Dependabot 제목, 주로 디렉터리를 명시하는 그룹화된 제목은 72자 스쿼시 제목
한도를 넘깁니다. PR Title 검사가 이를 지적하며, 제목을 바꾸면 해결됩니다. 도구
잠금 파일을 변경하는 풀 리퀘스트는
ci.yml작업을 실행하지 않으므로, npm audit 워크플로가 이를 감사하고 설치하며, vsce 잠금 파일에 대해서는 VS Code 확장을 패키징하고 vsce 릴리스 테스트를 실행하고, channel-smoke 잠금 파일에 대해서는 channel smoke 워크플로가 실행됩니다. Markdown Lint 워크플로는 pre-commit 훅 풀 리퀘스트에서 markdownlint 훅을 실행합니다. 다른 훅은 CI에서 실행되지 않으므로, 병합하기 전에 해당 브랜치에서pre-commit run --all-files를 실행하세요.