CI/CD

이 문서는 source-repo의 CI/CD 기준을 설명합니다. 목표는 pull request에서 빠르게 깨진 boundary를 찾고, release에서는 package, image, firmware artifact가 어떤 source revision에서 만들어졌는지 추적 가능하게 남기는 것입니다.

쉽게 말하면 이 문서는 "PR에서는 무엇을 막고, main에 merge되면 무엇을 만들고, release tag에서는 무엇을 publish하는가"를 설명합니다. 낯선 용어는 Glossary에서 함께 확인할 수 있습니다.

source-repo는 polyglot monorepo입니다. TypeScript, Python, Rust, Go, C/Firmware, container image, demo smoke가 한 repo에 있지만, 모든 검증을 하나의 거대한 workflow로 묶지 않습니다. 언어별 package와 app runtime을 나누고, runtime 비용이 큰 검증은 별도 gate로 분리합니다.

1. 이 문서의 역할

1-1. Source repo CI의 책임

Source repo CI는 deployable artifact를 만들기 전까지의 code, package, app, image 검증을 담당합니다.

책임 설명
Contract 검증 JSON Schema, fixture, RiskMonitor manifest/status, firmware release manifest가 서로 맞는지 확인합니다.
Package 검증 언어별 reusable package가 독립적으로 test/build/publish 가능한지 확인합니다.
App 검증 package를 실제 app entrypoint에서 조립할 수 있는지 확인합니다.
Image 검증 deployable container image가 build되고 runtime dependency가 맞는지 확인합니다.
Release artifact 생성 package, image, firmware artifact와 evidence를 publish 가능한 형태로 만듭니다.

Source repo CI는 Kubernetes environment promotion을 담당하지 않습니다. Kubernetes manifest, environment overlay, rollback, production secret, cluster policy는 CD repo와 infra gate 책임입니다.

1-2. CI, release, CD의 경계

이 문서에서 CI/CD라고 부르는 범위는 source repo 관점의 검증과 artifact handoff까지입니다. 실제 환경 배포와 promotion은 source repo가 직접 소유하지 않습니다.

구분 Source repo 책임 Source repo 밖 책임
CI 코드, contract, package, app, image가 깨지지 않았는지 검증합니다. 없음
Release package, image, firmware artifact를 만들고 source revision, version, evidence를 남깁니다. Release 승인 기록과 운영 배포 일정은 별도 process와 연결됩니다.
CD CD repo가 소비할 image tag/digest, package version, migration 여부를 넘깁니다. Kubernetes manifest, environment overlay, secret, promotion, rollback은 CD repo와 infra gate가 소유합니다.

따라서 이 문서의 핵심은 "어떤 branch와 tag에서 무엇을 검증하고, 무엇을 publish할 수 있는가"입니다.

1-3. 한눈에 보는 흐름

처음 보는 개발자는 아래 흐름만 먼저 잡아도 됩니다.

Pull request
  -> 빠른 test/build로 merge해도 되는지 확인합니다.

main merge
  -> trunk 기준으로 package, app, image를 다시 조립합니다.
  -> dev container image를 자동 push해 통합 검증에 사용합니다.

vX.Y.Z release tag
  -> approval 뒤 package, release image, firmware artifact를 publish합니다.
  -> CD repo가 사용할 image tag/digest와 version 정보를 넘깁니다.

중요한 기준은 하나입니다. PR과 main은 검증과 통합을 빠르게 만들고, release tag는 외부에서 소비할 수 있는 안정 artifact를 고정합니다.

1-4. 이 문서를 읽는 순서

처음 보는 경우에는 아래 순서로 읽는 편이 가장 쉽습니다.

  1. ## 2. 기본 원칙에서 branch 전략, workflow 분리, publish 정책을 봅니다.
  2. ## 3. CI Gate 계층에서 pull request, integration, nightly/manual, release gate 차이를 봅니다.
  3. ## 5. Package, App, Image Lifecycle에서 package가 image보다 먼저 검증되는 이유를 봅니다.
  4. ## 6. Artifact Platform 기준에서 multi-platform package와 image 기준을 봅니다.
  5. ## 8. Registry와 인증 기준에서 Nexus와 lockfile 정책을 봅니다.

Release 운영 기준은 Release Policy, container image 상세 기준은 Containers, registry 주소와 credential 기준은 Nexus, supply-chain permission 기준은 Security를 함께 봅니다.

2. 기본 원칙

2-1. Workflow는 언어와 책임으로 나눈다

언어별 workflow를 분리합니다. TypeScript package가 깨졌을 때 Python image job까지 기다리지 않아도 되고, Rust PyO3 binding이 깨졌을 때 web app owner가 원인을 추적하지 않아도 됩니다.

원칙 이유
언어별 workflow를 분리합니다. dependency cache, toolchain, test runner, publish 방식이 다릅니다.
package 검증과 app/container 검증을 분리합니다. package artifact 문제와 runtime composition 문제를 구분합니다.
외부 system 검증은 목적별 gate로 분리합니다. PostgreSQL, Redis, Kubernetes API server, browser runtime은 비용과 flake 가능성이 다릅니다.
publish 정책은 container와 package를 나눠 둡니다. container는 dev 통합 검증을 빠르게 만들고, package는 consumer 영향이 커서 release 중심으로 다룹니다.
registry 인증은 generated config나 composite action으로 주입합니다. credential을 source file, image layer, build log에 남기지 않습니다.

2-2. 빠른 feedback path를 먼저 둔다

Pull request의 첫 목적은 모든 운영 조합을 증명하는 것이 아니라, 변경자가 빠르게 깨진 boundary를 찾게 하는 것입니다. 따라서 PR required gate는 빠르고 결정적인 검증을 우선합니다.

PR에서 확인해야 하는 것은 아래입니다.

  • typecheck, lint, unit test
  • contract/schema/fixture validation
  • pure domain/usecase test
  • fake 또는 in-memory port 기반 app route test
  • package build가 가능한지 확인하는 lightweight build

반대로 매 PR마다 full container demo, browser visual journey, storage-backed long scenario, firmware board build를 모두 필수로 두면 feedback이 느려지고 실패 원인도 흐려집니다. 이런 검증은 integration, nightly/manual, release gate로 분리합니다.

2-3. Branch 전략은 trunk-based를 기본으로 둔다

이 repo는 작은 팀이 빠르게 통합하고 release tag에서 evidence를 고정하는 것을 우선합니다. 기본 branch 전략은 trunk-based workflow입니다.

main은 protected trunk입니다. 모든 변경은 feature/*, fix/*, docs/*, task/* 같은 short-lived branch에서 pull request로 들어오고, required CI와 self-review를 통과한 뒤 squash merge합니다. main은 개발자가 직접 push하는 branch가 아니라 검증된 change set이 모이는 branch입니다.

이 방식에서 history는 모든 WIP commit을 보존하는 로그가 아니라, 검증된 change set을 PR 단위로 남기는 로그입니다. 문서 수정, dependency update, code 변경이 모두 main에 들어오더라도 squash merge와 PR title 정리를 지키면 main history는 훨씬 읽기 쉬워집니다.

Ref 역할 기본 정책
feature/*, fix/*, docs/*, task/* 작업 branch PR 검증만 수행하고 publish하지 않습니다.
main protected trunk release-ready 상태를 유지하고, merge 뒤 dev image를 자동 push합니다. Package는 build/test만 수행합니다.
release/x.y 선택적 안정화 branch QA와 release hardening이 며칠 이상 걸리거나 여러 release line을 유지해야 할 때만 사용합니다. Publish 기준은 여전히 tag입니다.
vX.Y.Z release anchor package, image, firmware release publish와 evidence 생성의 기준입니다.

상시 dev 또는 develop branch는 기본 정책에 포함하지 않습니다. 별도 integration branch는 main 흐름이 반복적으로 막히거나, 동시에 유지해야 하는 release line이 생겼을 때 도입합니다.

2-4. PR 통과 기준은 CI와 영향 기반 review로 둔다

이 repo에서 PR은 기본적으로 change set을 묶고, CI를 실행하고, squash merge할 단위입니다. 모든 PR이 사람이 깊게 review한 단위라는 뜻은 아닙니다.

Issue, branch, commit message, PR description 기준은 Contribution Flow를 따릅니다. 이 문서에서는 PR이 CI/CD gate에서 어떻게 다뤄지는지만 설명합니다.

PR 단계의 impact check는 정식 SaMD risk analysis가 아닙니다. 작성자는 clinical behavior, public contract/API, migration/data, release/security 영향이 있는지 표시하고, 실제 risk analysis와 safety review는 release, design, clinical review 같은 별도 gate에서 다룹니다.

일반 PR의 통과 기준은 아래입니다.

  • required CI가 통과해야 합니다.
  • 작성자가 diff를 self-review하고, 의도하지 않은 파일과 secret이 없는지 확인해야 합니다.
  • PR title과 description은 squash commit으로 남아도 이해할 수 있어야 합니다.
  • package, image, release publish는 수행하지 않습니다.

Human review는 모든 PR에 강제하지 않고 impact에 따라 요구합니다. 아래 변경은 사람이 별도로 확인해야 합니다.

Human review가 필요한 변경 이유
clinical 또는 risk logic 변경 SaMD behavior와 patient safety에 직접 영향을 줄 수 있습니다.
public API, contract, fixture 변경 다른 package, app, 문서 예제가 함께 깨질 수 있습니다.
database migration, storage schema 변경 rollback과 data compatibility 영향이 있습니다.
CI/CD, release, versioning, Nexus, security 변경 artifact publish와 supply-chain trust boundary에 영향을 줍니다.
container publish policy 또는 runtime base image 변경 배포 artifact와 runtime security에 영향을 줍니다.
large refactor 또는 boundary 변경 작성자 혼자 전체 영향 범위를 보기 어렵습니다.

AI review는 붙일 수 있으면 PR feedback path에 추가합니다. 다만 AI review가 붙기 전까지도 PR 정책은 성립해야 하므로, AI review를 required gate로 가정하지 않습니다.

main은 "모든 변경이 human-reviewed 되었다"는 뜻이 아닙니다. main은 required CI와 self-review를 통과한 change set이 모이는 protected trunk이고, release tag 전 review와 approval이 최종 통제 지점입니다.

2-5. Release gate는 artifact handoff를 검증한다

Release gate는 단순히 test를 한 번 더 돌리는 단계가 아닙니다. 어떤 source revision이 어떤 package, image, firmware artifact로 나갔는지 설명할 수 있어야 합니다.

Release gate에서는 아래를 확인합니다.

  • package artifact build와 publish dry-run
  • platform-specific artifact matrix 결과
  • container image build, SBOM, provenance
  • migration과 storage-backed smoke
  • firmware artifact manifest
  • CD repo로 넘길 image repository, tag 또는 digest, runtime contract
  • release 대상 change set의 impact check와 human review 필요 항목

2-6. Container와 package publish의 De Facto 패턴

회사와 오픈소스 커뮤니티에서 흔히 쓰는 기준은 container와 package를 같은 publish 정책으로 묶지 않는 것입니다. Container는 실행 가능한 배포 단위이고, package는 다른 코드가 의존하는 재료입니다.

단계 목적 Container Package
PR 검증 build/test만 수행 build/test만 수행
main 통합 테스트 dev image 자동 push 보통 publish하지 않음
release 배포 release image push release package publish

Container는 dev moving tag와 dev-<count>-g<sha> trace tag를 함께 사용합니다. dev는 항상 최신 main image를 가리키고, dev-<count>-g<sha>는 특정 source revision을 추적합니다. 그래서 main merge 뒤 dev image를 자동 publish해 preview, smoke, CD repo 연동 검증에 사용할 수 있습니다. Dev image는 빠른 통합 검증 artifact이므로 현재 workflow에서는 linux/amd64만 자동 publish하고, release image에서 linux/amd64, linux/arm64 multi-platform publish를 수행합니다.

Dev image는 계속 쌓이지 않도록 GHCR retention workflow로 정리합니다. Release tag는 보존하고, dev/sha/untagged version은 최신 N개만 남기는 방식입니다. 자세한 기준은 Containers의 Dev image retention 섹션을 따릅니다.

Package는 다른 app, service, repo가 dependency resolver로 가져가는 artifact입니다. 같은 version을 다시 publish할 수 없거나, pre-release가 dependency resolution에 섞이거나, binary package platform이 맞지 않아 consumer build를 깨뜨릴 수 있습니다. 따라서 package publish는 release를 기본으로 두고, dev package가 정말 필요한 경우에만 별도 dev channel 또는 dev repository를 운영합니다.

이 repo의 기본 정책은 아래처럼 둡니다.

PR:
  test/build only
  publish 없음

main:
  container dev image 자동 push
  package는 build/test만

manual publish:
  필요한 경우 명시적으로 dev image 또는 dev package를 publish

release tag:
  approval 후 package publish
  approval 후 release container image push

2-7. Local command와 CI command는 같은 entrypoint를 공유한다

Local 개발자는 언어별 command를 직접 실행할 수 있지만, repo 전체 기준은 Makefile target으로 맞춥니다.

make docs/check
make demo/check
make package/test/unit
make package/build
make container/build

CI도 같은 target 이름을 사용합니다. 이렇게 하면 local에서 재현 가능한 실패와 CI 전용 실패를 구분하기 쉽습니다.

3. CI Gate 계층

3-1. Gate 개요

CI는 테스트 피라미드와 외부 의존성 비용을 기준으로 gate를 나눕니다.

먼저 이름의 의미를 간단히 보면 아래와 같습니다.

  • PR required는 merge를 막을 수 있는 필수 검사입니다.
  • PR integration은 실제 database, Redis, Kubernetes API처럼 fake만으로 부족한 경계를 좁게 확인하는 검사입니다.
  • Main integration은 main에 들어온 change set이 실제 app/image로 다시 조립되는지 보는 검사입니다.
  • Release gate는 외부에서 소비할 package, image, firmware artifact를 publish하기 전 마지막 검사입니다.
Gate Trigger 목적 포함할 검증
PR required pull request 빠르고 결정적인 기본 회귀 방지 lint/typecheck, unit test, contract validation, domain/usecase test, fake/in-memory app route test
PR integration pull request, 변경 path 기반 mock으로 대체하기 어려운 adapter semantics 확인 Alembic migration smoke, PostgreSQL repository integration, Redis stream/idempotency test, Kubernetes envtest
Main integration main push merge된 trunk가 실제 통합 artifact를 만들 수 있는지 확인 package build, app validation, image build, dev image push, 필요한 경우 storage-backed smoke
Nightly/manual schedule, workflow_dispatch 느리거나 flake 가능성이 있는 runtime 조합 확인 full device-to-dashboard demo, storage-backed smoke, browser accessibility/visual smoke, longer failure-mode test
Release gate tag, release workflow, approval 배포 전 artifact와 runtime handoff 검증 package build/publish dry-run, image build/provenance/SBOM, migration, full storage E2E smoke, firmware artifact manifest

PR required job은 실패 원인을 빠르게 좁힐 수 있어야 합니다. 외부 container를 쓰는 job은 PR에서 허용하더라도 adapter integration처럼 목적이 좁아야 하며, product demo 전체를 매 PR의 필수 경로로 두지 않습니다.

main push에서 실행되는 job은 PR이 이미 통과한 change set을 trunk 기준으로 다시 조립하는 단계입니다. 이 단계에서 dev image를 자동 push하지만, stable package와 release image publish는 tag와 approval을 기준으로 합니다.

3-2. PR required 기준

PR required gate는 대부분의 변경에서 항상 실행됩니다. 이 gate가 실패하면 merge하지 않습니다.

기본 포함 대상은 아래입니다.

Area Required check
Docs make docs/check
Demo make demo/check
TypeScript package/app typecheck, unit/integration, build
Python package/app test, Rust Python binding test
Rust crate unit/integration, package build
Go module test, operator envtest, build
C C core package, host simulation

make demo/check는 device-to-dashboard 흐름을 빠르게 확인하는 lightweight smoke입니다. PostgreSQL/Redis를 포함한 storage-backed demo는 PR required가 아니라 별도 gate로 둡니다.

3-3. PR integration 기준

PR integration gate는 fake로 검증하면 의미가 약해지는 adapter semantics를 확인합니다.

예를 들어 PostgreSQL repository는 단순히 method가 호출되는지만 보면 부족합니다. transaction boundary, migration, index, SQL dialect가 실제로 맞아야 합니다. Redis도 key TTL, stream group, idempotency, lease behavior는 fake만으로 충분하지 않습니다.

PR integration gate는 아래처럼 작게 유지합니다.

  • PostgreSQL container에서 Alembic upgrade head와 repository test
  • Redis container에서 stream, TTL, idempotency, projection command test
  • Kubernetes envtest로 CRD/status subresource semantics test
  • 필요한 경우 headless browser role/accessibility smoke

3-4. Nightly/manual 기준

Nightly/manual gate는 느리거나 환경 의존성이 큰 검증을 담당합니다.

아래 job은 PR required 경로에서 제외하고 main push, tag, schedule, 또는 workflow_dispatch에서 실행합니다.

  • make demo/check/storage: PostgreSQL/Redis를 포함한 storage-backed demo
  • browser contract: Playwright, Storybook, browser audio, accessibility smoke
  • image build: Docker/BuildKit 기반 image 검증
  • firmware bare-metal, Zephyr native_sim: runner/toolchain 의존성이 큰 firmware 검증

이 구조의 목적은 검증을 줄이는 것이 아니라 feedback path를 분리하는 것입니다. PR은 빠르게 깨진 boundary를 알려주고, main/manual/release gate가 runtime 조합과 artifact handoff를 검증합니다.

4. Workflow Map

4-1. Workflow 파일 기준

Workflow는 pull request와 main push를 기본 trigger로 사용합니다. Feature branch push는 같은 commit에 대해 PR run과 push run이 중복으로 생기므로 기본 trigger에서 제외합니다. Release artifact가 필요한 언어 workflow는 v* tag push도 허용합니다.

Workflow Scope
.github/workflows/source-repo-docs.yml documentation, contract example, fixture validation
.github/workflows/source-repo-demo.yml device-to-dashboard smoke
.github/workflows/source-repo-typescript.yml TypeScript packages, simulator, web app, web image
.github/workflows/source-repo-python.yml Python package, API app, Rust Python binding, API image
.github/workflows/source-repo-rust.yml Rust preprocessing crate
.github/workflows/source-repo-go.yml Go module, RiskMonitor operator
.github/workflows/source-repo-c.yml C package, bare-metal firmware, Zephyr firmware

실제 workflow가 없는 template repo나 초기 repo에서는 이 표를 target architecture로 봅니다. CI를 추가할 때는 이 이름과 책임을 기준으로 workflow를 나눕니다.

4-2. Composite action 기준

반복되는 Nexus/Docker 설정은 .github/actions/ composite action으로 분리합니다. Composite action은 반복 제거가 목적이지 business flow를 숨기는 도구가 아닙니다.

Area Composite action
Version computation .github/actions/compute-version
npm auth .github/actions/setup-nexus-npm
uv auth .github/actions/setup-uv-auth
Cargo registry config .github/actions/setup-cargo-nexus
Conan remote config .github/actions/setup-conan-nexus
Docker build/publish .github/actions/docker-image

Workflow에는 package, app, image, publish job이 그대로 보여야 합니다. 인증 파일 생성, Docker BuildKit option, registry login처럼 반복되는 구현만 action으로 뺍니다.

4-3. Local Make target과 CI job 이름

CI job 이름과 local Make target은 가능한 한 같은 언어를 씁니다.

CI intent Local target
docs and contract validation make docs/check
lightweight demo smoke make demo/check
storage-backed demo smoke make demo/check/storage
package unit test make package/test/unit
package integration test make package/test/integration
package artifact build make package/build
container image build make container/build

CI에서 실패한 job은 local에서 같은 target 또는 같은 target family로 재현할 수 있어야 합니다.

4-4. Runner pool 선택 기준

Runner label은 job이 필요한 실행 환경을 명확히 표현해야 합니다. 기본값은 tirosh-ubuntu-lite이고, browser runtime이나 큰 host toolchain이 필요한 job만 별도 pool로 올립니다.

Runner label 용도 대표 job
tirosh-ubuntu-lite lint, unit test, typecheck, package build, 일반 Docker build/push, 가벼운 infra smoke docs, TypeScript, pure Python, Go, C package, Docker image build/push
tirosh-ubuntu-browser Playwright/Cypress 같은 browser/E2E job을 container image 안에서 실행 browser-contract
tirosh-ubuntu-heavy 무거운 host toolchain이나 긴 integration 실행이 필요한 job Rust crate, Rust Python binding, demo smoke, firmware, Zephyr/west, ARM GCC

선택 순서는 아래처럼 판단합니다.

  1. 먼저 tirosh-ubuntu-lite로 가능한지 봅니다.
  2. Browser가 필요하고 browser runtime을 workflow container 안에서 실행하면 tirosh-ubuntu-browser를 씁니다.
  3. Rust, firmware, Zephyr/west, ARM GCC, 긴 integration test처럼 host toolchain이 무거우면 tirosh-ubuntu-heavy를 씁니다.
  4. ncloud-ci, aws-ci 같은 provider-specific label은 workflow에 넣지 않습니다.

현재 workflow별 예외는 아래와 같습니다.

Workflow lite browser heavy
source-repo-docs.yml 전체 job 없음 없음
source-repo-typescript.yml package, app, image, publish job browser-contract 없음
source-repo-python.yml pure Python package/app, API image, Python package publish 없음 Rust Python binding build/publish
source-repo-rust.yml version 없음 Rust crate build/publish
source-repo-go.yml 전체 job 없음 없음
source-repo-c.yml version 없음 C package build/publish, firmware host simulation, bare-metal, Zephyr, firmware artifact publish
source-repo-demo.yml 없음 없음 demo smoke, storage-backed integration

Browser pool은 host에 browser를 직접 설치하기 위한 pool이 아닙니다. Browser runtime은 Playwright/Cypress container image가 제공하고, runner는 container job을 안정적으로 실행하는 역할만 맡습니다.

5. Package, App, Image Lifecycle

5-1. Lifecycle 순서

Package도 app과 별도의 CI lifecycle을 가집니다. Package CI는 container를 만들기 전에 library artifact가 독립적으로 검증되고, 필요할 때 Nexus에 publish할 수 있는 형태인지 확인합니다.

unit test -> integration test -> package build -> app validation -> image build -> publish

이 분리는 polyglot repo에서 특히 중요합니다. Package가 깨졌는데 container build까지 기다리면 피드백이 늦고 원인도 흐려집니다. 반대로 package는 정상인데 app bootstrap이나 Dockerfile만 깨진 경우에는 package maintainer가 아니라 app owner가 고쳐야 합니다.

5-2. Test level 기준

테스트는 실행 비용과 검증 대상에 따라 나눕니다.

Level 기준 예시
Unit process 밖 I/O가 없는 pure rule 또는 isolated usecase behavior risk score rule, alarm state transition, parser, mapper
Integration inbound/usecase/outbound adapter를 port contract를 통해 함께 실행 repository adapter, migration, Redis stream, envtest controller
App test package를 실제 app entrypoint에서 조립할 수 있는지 확인 FastAPI route smoke, web app render smoke, operator reconcile smoke
Image test runtime dependency와 container entrypoint가 맞는지 확인 Docker build, healthcheck, non-root user, BuildKit secret

Unit test가 app bootstrap을 검증하려고 하면 느려지고, image build가 package contract를 처음 발견하면 원인이 늦게 드러납니다. 각 level은 자기 책임을 작게 유지합니다.

5-3. Package command 기준

로컬 명령은 CI와 같은 이름을 사용합니다.

make package/test/unit
make package/test/integration
make package/build

Nexus upload도 같은 Makefile에서 실행합니다.

make package/upload PACKAGE_VERSION=0.1.0
make package/upload/python PACKAGE_VERSION=0.1.0
make package/upload/npm PACKAGE_VERSION=0.1.0
make package/upload/rust PACKAGE_VERSION=0.1.0
make package/upload/c PACKAGE_VERSION=0.1.0
make package/publish/go PACKAGE_VERSION=0.1.0
make firmware/upload PACKAGE_VERSION=0.1.0

Publish command는 release job이나 manual approval 뒤에서 실행합니다. 일반 PR job은 publish하지 않습니다.

6. Artifact Platform 기준

이 섹션은 모든 개발자가 처음부터 자세히 이해할 필요는 없습니다. Rust Python wheel, C binary package, multi-platform container image처럼 CPU architecture나 OS에 따라 artifact가 달라지는 package를 관리할 때 주로 참고합니다.

6-1. Platform-neutral과 platform-specific

Package artifact는 먼저 platform-neutral인지 platform-specific인지 분류합니다. 이 분류가 있어야 CI matrix, Nexus publish 대상, container image build가 서로 같은 artifact를 바라볼 수 있습니다.

Artifact class Examples CI 기준
Platform-neutral npm package, pure Python wheel/sdist, Go module, Rust crate source package 단일 package build와 package test를 우선합니다. Native dependency가 추가되면 별도 platform-specific 기준으로 승격합니다.
Platform-specific PyO3/maturin Python wheel, C/Conan binary package, firmware image, container image target platform 또는 hardware profile을 명시하고 matrix build로 검증합니다.

Platform-neutral artifact도 runtime compatibility 검증이 필요할 수 있습니다. 예를 들어 npm package는 package 자체가 platform-neutral이지만 browser compatibility나 Node version compatibility는 app smoke와 browser contract에서 확인합니다.

6-2. Docker platform을 기준 축으로 둔다

Docker로 배포되는 app의 기본 platform 축은 Docker 표기인 linux/amd64, linux/arm64를 사용합니다. 각 생태계의 artifact tag는 이 축에 맞춰 해석합니다.

Docker platform Python wheel C/Conan Container image
linux/amd64 manylinux_*_x86_64.whl arch=x86_64 profile linux/amd64 image variant
linux/arm64 manylinux_*_aarch64.whl arch=armv8 profile linux/arm64 image variant

macOS developer workstation에서 만든 macosx_* wheel은 local Python smoke에는 사용할 수 있지만 Linux container image에는 설치하지 않습니다. Container가 소비하는 Rust Python wheel은 Docker platform 기준으로 만든 Linux wheel이어야 합니다.

6-3. CI matrix 기준

CI workflow는 package별로 필요한 matrix만 둡니다. Pure Python package나 npm package는 matrix를 늘려도 얻는 신호가 작지만, Rust Python wheel과 C binary package는 architecture가 맞지 않으면 runtime에서 import 또는 link가 실패합니다.

Platform-specific artifact는 package build 단계에서 matrix로 만들고, app/container job은 해당 platform에 맞는 artifact를 소비해야 합니다.

package build matrix
  -> linux/amd64 artifact
  -> linux/arm64 artifact
  -> publish to Nexus
  -> image build consumes matching artifact

Docker-in-Docker는 multi-platform의 표준 표현이 아닙니다. CI 계약은 linux/amd64, linux/arm64 platform과 platform별 artifact 소비로 표현합니다. 구현은 BuildKit/buildx, QEMU emulation, native runner, remote builder 중 CI 환경에 맞는 방식을 선택합니다.

6-4. Firmware platform 기준

Firmware는 Docker platform을 따르지 않습니다. Firmware artifact는 board, MCU, RTOS/bare-metal 여부, compiler/toolchain version을 기준으로 분리합니다.

예를 들어 .elf, .hex, .bin, .map은 같은 source revision이라도 target board와 toolchain이 다르면 다른 release evidence입니다. Firmware release manifest에는 target과 artifact hash를 함께 남깁니다.

7. 외부 시스템 테스트 기준

7-1. 외부 시스템을 분리하는 이유

Mock이나 fake는 business rule과 usecase 흐름을 빠르게 검증하기 위한 도구입니다. Database schema, SQL dialect, Redis stream, Kubernetes status subresource처럼 도구 자체의 semantics가 중요한 곳에서는 fake만으로 충분하지 않습니다.

다만 그런 검증은 full app demo가 아니라 adapter별로 작고 독립적인 integration job으로 유지합니다.

7-2. 시스템별 gate 기준

외부 시스템은 아래 기준으로 다룹니다.

대상 PR required PR integration Nightly/manual 또는 release
PostgreSQL SQLite 또는 fake repository로 app/usecase contract 검증 PostgreSQL container에서 Alembic upgrade head, SQLAlchemy repository, transaction boundary 검증 storage-backed full demo, migration rollback/upgrade 시나리오
Redis fake key-value/stream port로 usecase contract 검증 Redis container에서 TTL, stream, idempotency, lease, projection command semantics 검증 reconnect, failover, stale projection, long-running relay style scenario
MQTT topic parser와 handler unit test, fake broker/adapter broker container가 꼭 필요한 QoS/reconnect behavior만 짧게 검증 gateway/device stream full path
Kubernetes fake client 기반 reconcile unit/integration envtest API server로 CRD/status subresource semantics 검증 real cluster smoke는 source repo CI가 아니라 CD/infra gate
Browser component/view model unit, SSR/render smoke 필요한 경우 headless browser role/accessibility smoke visual review, long browser journey, screenshot diff
Firmware/HIL host unit/integration, native simulation where cheap board/runtime boundary가 바뀐 경우 targeted simulation real PCB/HIL은 별도 runner와 manual/nightly policy

7-3. Demo workflow 기준

source-repo-demo.yml은 언어별 artifact 검증과 별도로 device-to-dashboard smoke를 실행합니다. PR required 경로에서는 make demo/check 수준의 lightweight smoke를 우선합니다.

PostgreSQL/Redis까지 포함하는 make demo/check/storage는 adapter integration, nightly/manual, release gate 중 하나로 둡니다. 이 gate는 synthetic device signal이 Python API를 거쳐 TypeScript dashboard view model과 React render까지 이어지는지 확인합니다.

8. Registry와 인증 기준

8-1. uv와 PyPI index 기준

source-repo/pyproject.tomltirosh-pypi Nexus index를 default로 선언합니다. 따라서 uv.lock도 Nexus 기준으로 유지합니다.

CI와 Makefile은 lockfile source와 다른 default index를 강제로 주입하지 않습니다.

  • workflow에서 UV_DEFAULT_INDEX=https://pypi.org/simple로 덮어쓰지 않습니다.
  • local helper인 DEMO_UV_RUN_ARGS, DOCS_UV_RUN_ARGS, RUST_PYTHON_UV_RUN_ARGS 기본값은 비워 둡니다.
  • Nexus credential은 .github/actions/setup-uv-authUV_INDEX_TIROSH_PYPI_USERNAMEUV_INDEX_TIROSH_PYPI_PASSWORD로 주입합니다.
  • 외부 PyPI로 의도적으로 검증해야 하는 경우에는 별도 job 또는 local override로 실행하고, 그 결과로 uv.lock을 갱신하지 않습니다.

이 기준을 지키지 않으면 uv sync --managed-python --lockeduv.lock needs to be updated로 실패하거나, uv run --managed-python이 작업 중 uv.lock의 registry URL을 PyPI 기준으로 바꾸는 문제가 생깁니다.

8-2. Generated registry config

CI는 scripts/configure-nexus.sh에 의존하지 않습니다. scripts/configure-nexus.sh는 local developer helper이고, CI는 workflow와 composite action만 보고 동작해야 합니다.

언어별 registry 인증은 workflow 안에서 generated config로 주입합니다.

Ecosystem Generated config 또는 env
npm .npmrc
Python/uv UV_INDEX_<NAME>_USERNAME, UV_INDEX_<NAME>_PASSWORD, UV_PUBLISH_*
Rust/Cargo Cargo registry config
C/Conan Conan remote config
Docker registry login, BuildKit secret

Credential은 source file, image layer, package metadata, release note에 남기지 않습니다.

8-3. BuildKit secret 기준

Container build가 private package registry에서 dependency를 받아야 할 때는 Dockerfile ARG나 image layer에 credential을 남기지 않습니다. BuildKit secret을 사용합니다.

RUN --mount=type=secret,id=uv_index_username ...
RUN --mount=type=secret,id=uv_index_password ...

Secret은 build 중에만 존재해야 하고 final image filesystem에 남으면 안 됩니다. 이 기준은 npm, uv, Cargo, Conan 모두 동일합니다.

9. Workflow 상세 기준

9-1. Docs workflow

source-repo-docs.yml은 Make interface인 make docs/check를 실행합니다. 이 target은 문서 navigation만 확인하지 않고, contract example, signal fixture, RiskMonitor manifest/status contract, firmware release manifest까지 함께 검증합니다.

문서 변경이 code contract를 깨뜨릴 수 있고, code contract 변경이 문서 예제를 깨뜨릴 수 있기 때문에 docs workflow는 documentation과 contract validation을 같이 봅니다.

9-2. TypeScript workflow

TypeScript workflow의 web app job은 typecheck, test, build를 PR required 경로에서 실행합니다.

Storybook과 Playwright 기반 browser contract는 별도 browser-contract job으로 분리하고, main push 또는 manual gate에서 실행합니다. Storybook 자체는 visual documentation이지만, browser contract job은 clinical UI 상태가 story fixture에서 사라지는 것을 잡는 runtime 회귀 방지선입니다.

browser-contract는 Playwright 공식 container image를 사용합니다. Self-hosted runner에 Playwright browser와 Linux system library를 직접 설치하는 방식은 기본 경로가 아닙니다. Runner는 Docker container를 실행할 수 있으면 되고, browser runtime은 workflow container가 제공합니다. Container job은 workspace에 root-owned file을 남길 수 있으므로 job 마지막에 workspace owner를 원래 runner user로 되돌립니다.

검증 예시는 아래와 같습니다.

  • scripts/check-clinical-storybook-contract.mjs
  • scripts/check-clinical-storybook-browser.mjs
  • browser DOM 기준 role, alarm action state, numeric status hook, axe smoke

9-3. Python과 Rust Python binding workflow

Python workflow는 pure Python package와 API app test를 실행합니다. Rust Python binding은 별도 package artifact 성격을 가지므로 pure Python package path와 분리해 검증합니다.

Rust Python wheel은 platform-specific artifact입니다. Local host에서 만든 wheel과 Linux container용 wheel이 다를 수 있으므로 release job에서는 linux/amd64, linux/arm64 matrix를 명시합니다.

9-4. Rust workflow

Rust workflow는 patient-signal-rust-core crate의 unit/integration test와 package build를 담당합니다. Rust core가 바뀌면 PyO3 binding workflow도 함께 검증되어야 합니다. Rust core와 Python binding의 fixture 해석이 달라지면 preprocessing contract가 흔들리기 때문입니다.

9-5. Go workflow

Go workflow는 Go module test와 RiskMonitor operator validation을 담당합니다. Go module은 VCS tag와 Nexus Go proxy를 통해 배포합니다. Operator runtime은 Kubernetes API semantics가 중요하므로 fake client test와 envtest를 구분합니다.

9-6. C/Firmware workflow

C/Firmware workflow는 host C package, bare-metal firmware, Zephyr firmware를 분리합니다.

C package는 Conan package로 배포하므로 profile-specific binary package 기준을 가집니다. Firmware artifact는 board/toolchain-specific release evidence이므로 .elf, .hex, .bin, .map, release manifest를 함께 남깁니다.

10. Source Repo와 CD Repo 경계

10-1. Source repo에 두지 않는 것

Source repo는 deployable artifact를 만들고 검증합니다. Kubernetes manifest와 실제 environment promotion은 CD repo 책임입니다.

Source repo에는 아래를 두지 않습니다.

  • Kubernetes Deployment, Service, Ingress, CronJob manifest
  • environment별 overlay
  • cluster secret
  • promotion policy

이 분리를 지키면 application code 변경, package release, cluster policy 변경이 한 PR에 섞이는 것을 줄일 수 있습니다.

10-2. CD repo로 넘기는 정보

Release가 끝나면 CD repo가 environment overlay를 갱신할 수 있도록 아래 정보를 넘깁니다.

  • image repository와 immutable tag 또는 digest
  • package version
  • runtime config 변경
  • health endpoint 또는 port 변경
  • migration 필요 여부
  • SaMD risk/control 영향 여부

이 기준은 Release Policy의 release handoff와 연결됩니다.

11. GitHub Actions Rerun 기준

11-1. Rerun은 과거 workflow definition을 사용한다

GitHub Actions에서 과거 run을 rerun하면 해당 run이 checkout했던 commit의 workflow YAML이 다시 실행됩니다. workflow 파일을 고친 뒤에는 이전 실패 run을 반복 실행하지 말고, 새 commit을 push해서 최신 workflow definition으로 새 run을 만들어야 합니다.

예를 들어 Zephyr native_sim build command를 고쳤다면, 이전 실패 run을 rerun해도 고친 command가 적용되지 않을 수 있습니다. 이 경우 새 commit을 push하고 최신 run을 확인합니다.

11-2. Rerun해도 되는 경우

Rerun은 외부 서비스 일시 장애나 runner 일시 실패처럼 workflow definition이 원인이 아닐 때 사용합니다.

상황 대응
Registry timeout, network flake 같은 commit rerun 가능
Runner capacity 문제 같은 commit rerun 가능
Workflow YAML 수정 새 commit push 후 새 run 확인
Toolchain install command 수정 새 commit push 후 새 run 확인
Test flake 원인 수정 새 commit push 후 새 run 확인