본문으로 건너뛰기

오픈소스 스택 밸리데이션: GAMP 5와 CSA

📍 현재 위치: 5부 · 신뢰 — 우리는 감사 추적(audit trail)과 전자서명(e-signature)을 갖춘, 돌아가는 스택을 손에 넣었습니다. 이제 그것이 GxP 용도에 적합함을 증명해야 하는데(GxP는 보건 당국이 규제 대상 의약품 작업에 강제하는 "Good x Practice" 규칙들 — GMP(제조, Manufacturing), GLP(시험, Laboratory), GCP(임상, Clinical) Practice — 을 아우르는 우산입니다), 뒤에 숨을 벤더(vendor)는 없습니다.

쉽게 말하면

밸리데이션된(validated) 소프트웨어를 사는 것은, 두툼한 서류철을 건네주고 지붕이 새면 책임을 지는 시공업자를 고용하는 것과 같습니다. 오픈소스(open source)는 데크를 직접 짓는 것과 같습니다. 아무도 서류철을 건네주지 않고, 아무도 대신 결재해 주지 않습니다. 그렇다고 그것이 치명적인 결격 사유는 아닙니다 — 잘 지은 데크라면 주택 검사관도 통과시킵니다 — 다만 여러분이 영수증과 사진, 그리고 사용한 모든 목재의 목록을 직접 챙겨야 합니다. 이 장은 검사관이 받아들일 방식으로 그 영수증을 챙기는 일에 관한 것이며, 그것을 다시 실행할 수 있는 코드로 해내는 일에 관한 것입니다.

지난 장들에서 여러분은 코어 스택을 띄우고, 감사 트리거(audit trigger)를 로드하고, 전자서명 서비스가 어떻게 끼워질지를 스케치했습니다. 모든 것이 돌아갑니다. 하지만 "내 노트북에서는 돌아간다"는 "밸리데이션되었다"가 아닙니다. 이 스택을 돌려 단일클론항체(monoclonal antibody, mAb — 살아 있는 세포에서 키워 내는 치료용 단백질로, 그 물리적 공정은 Book 1의 주제입니다)를 만드는 규제 제조사라면, 자신이 그것을 신뢰하는지를 검사관에게 문서로 설명할 수 있어야 합니다 — 그리고 오픈소스에는 기댈 수 있는 공급자가 없습니다.

이 장이야말로 정직한 하이브리드(honest-hybrid) 명제가 가장 날카롭게 파고드는 곳입니다. 순수 오픈소스는 방어 가능한 밸리데이션 패키지로 가는 길의 약 80%까지 여러분을 데려다줄 수 있습니다. 마지막 한 구간 — 절차, 변경 관리(change control), 사람의 결재, 감사된 환경 — 은 여러분의 몫이며, 그 부담은 아래에 깔린 소프트웨어가 0달러였든 100만 달러였든 똑같습니다.

이 장에서 다루는 내용

  • "밸리데이션된 소프트웨어"가 왜 신화이며 밸리데이션이 시스템 + 절차의 속성인지를, 21 CFR Part 11 §11.10(a), 21 CFR 820.70(i), 그리고 FDA의 소프트웨어 밸리데이션 일반 원칙(General Principles of Software Validation)의 틀로 살펴봅니다.
  • 각 스택 구성 요소의 GAMP 5 범주화(categorization) — 인프라로서의 PostgreSQL, 카테고리 5로서의 우리 배치 모델 — 와, 문서에서 위험으로 무게중심을 옮기는 CSA(Computer Software Assurance, 컴퓨터 소프트웨어 보증)의 전환을 다룹니다.
  • 공급자 품질 시스템이 없는 커뮤니티 프로젝트에 대한 공급자 및 출처(provenance) 평가를, 이미지 다이제스트(image digest)에 고정된 SBOM으로 뒷받침합니다.
  • URS → IQ/OQ/PQ 추적성 매트릭스(traceability matrix)와, 이미지 다이제스트에 잠긴 IQ 매니페스트(manifest)를 살펴봅니다.
  • pytest로 작성한 자동화 OQexamples/tests/의 테스트들이 밸리데이션 증거이며, 변경이 있을 때마다 CI(continuous integration, 지속적 통합 — 코드가 변경될 때마다 테스트 스위트를 다시 실행하는 자동화)가 다시 실행합니다.

"밸리데이션된 소프트웨어"는 존재하지 않는다

먼저 여러분의 어휘에서 한 표현을 지우는 것으로 시작합시다. "밸리데이션된 소프트웨어" 말입니다. 어떤 다운로드도 컴플라이언트(compliant)하지 않습니다. 법은 이 점에 대해 정확합니다. 21 CFR Part 11 — 미국 연방규정집(Code of Federal Regulations, CFR) 24장으로, 전자기록과 전자서명에 관한 FDA의 구속력 있는 규칙 — §11.10(a)는 "정확성, 신뢰성, 일관되게 의도된 성능을 보장하기 위한 시스템의 밸리데이션"을 요구합니다 [5]. 의료기기와 품질 시스템에 대해서는 21 CFR 820.70(i)가 한층 더 직접적입니다. 생산 또는 품질 시스템의 일부로 사용되는 컴퓨터 소프트웨어는 "확립된 프로토콜에 따라 그 의도된 용도에 대해 밸리데이션되어야 한다"고 못 박습니다 [6]. 둘 다 의무를 벤더의 박스가 아니라 여러분이 사용하는 그대로의 시스템에 지웁니다.

FDA의 소프트웨어 밸리데이션 일반 원칙은 우리에게 동사들 — 설치, 운영, 성능 적격성 평가(IQ/OQ/PQ) — 을 제공하며, 밸리데이션을 일회성 시험이 아니라 라이프사이클 활동으로 규정합니다 [4]. 이 장의 나머지가 현금화하는 희소식은, 코드형 인프라(infrastructure-as-code) 스택 — 모든 구성 요소가 손으로 설치되는 것이 아니라 버전 관리되는 파일(여기서는 compose.yaml)에 선언되는 스택 — 에서는 그 동사들 각각이 여러분이 실행할 수 있는 무언가에 깔끔하게 매핑된다는 것입니다.

GAMP 5 제2판은 이를 비례적으로 해내기 위한 업계의 플레이북이며, 결정적으로 오픈소스를 금단의 열매로 취급하지 않습니다. 위험 기반의 비판적 사고 접근법과 함께, 소프트웨어 범주, 공급자, 심지어 오픈소스 소프트웨어에 대한 명시적 지침을 제공합니다 [1]. GAMP 실천 공동체(Community of Practice)가 펴낸 짝이 되는 Pharmaceutical Engineering 기사는 OSS 측면을 구체적으로 풀어냅니다. 오픈소스 구성 요소의 카탈로그를 유지하고, 프로젝트의 거버넌스(governance)와 지속 가능성을 평가하며, 설치된 사본이 평판 있는 출처의 의도된 버전과 일치하는지 검증하라는 것입니다 [7].

밸리데이션하기 전에 범주화하라

첫 번째 GAMP 5 수순은 각 구성 요소를 범주화하는 것인데, 범주가 노력의 크기를 좌우하기 때문입니다. 여러분은 PostgreSQL의 B-트리(B-tree) 구현을 밸리데이션하지 않습니다. 그 위에 여러분이 쌓아 올린 애플리케이션을 밸리데이션합니다. 우리 스택이 어떻게 나뉘는지 보겠습니다.

구성 요소GAMP 범주이유밸리데이션 초점
PostgreSQL / TimescaleDB1 (인프라)확립된 플랫폼 소프트웨어(SQL 데이터베이스와 그 시계열 확장)올바른 설치, 버전 고정, 명세에 따른 구성
Mosquitto, Grafana, Fuseki1 (인프라)수정 없이 사용하는 구성 가능한 플랫폼 도구(MQTT 메시지 브로커, 대시보드 서버, RDF 트리플스토어)설치 + 구성 검증
Grafana 대시보드(br101-batch-overlay.json), Grafana 프로비저닝, Mosquitto 구성(mosquitto.conf)4 (구성됨)우리는 동작을 코딩하는 것이 아니라 구성한다구성이 요구사항을 충족하는지 검증
bioproc_sim, ISA-88/95 모델, 감사 트리거, 소프트 센서(soft-sensor)5 (커스텀)이 의도된 용도를 위해 우리가 작성한 코드전체 라이프사이클: 요구사항 → 설계 → 코드 리뷰 → 테스트

카테고리 1 대 5의 구분이 밸리데이션의 경제학 전부입니다. 스택의 대부분은 인프라이거나 구성이며, 카테고리 5로서 중량급 처리가 필요한 것은 우리가 직접 작성한 코드뿐입니다. 바로 거기에 우리는 OQ 테스트를 겨냥합니다.

흐름도: URS가 위험 및 GAMP 범주 판단 다이아몬드로 들어가 범주에 따라 분기한다 — 카테고리 1 인프라는 IQ 설치 및 구성 검증으로, 카테고리 4 구성됨과 카테고리 5 커스텀은 모두 OQ pytest 동작 테스트로 이어지고, 그다음 IQ와 OQ가 실제 공정에 대한 엔드투엔드 PQ로 합류하며, 이는 서명된 증거와 추적성 매트릭스로 흘러간다.

GAMP 범주 다이얼: 왜 1 대 5가 경제학 전부인가

가격표가 아니라 범주가 청구서를 정하는 이유를 잠시 곱씹어 볼 가치가 있습니다. GAMP 5의 범주는 품질의 위계가 아니라 위험 다이얼입니다 [1]. 카테고리 1은 확립된 인프라 — 운영체제, 데이터베이스 엔진 — 로서, 방대한 사용자 기반이 날마다 가동하기에 그 올바른 작동이 전제됩니다. 여러분의 의무는 그 내부를 재시험하는 것이 아니라 올바른 버전이 명세대로 설치·구성되었음을 증명하는 것입니다. 카테고리 4는 구성된 제품입니다. 공급된 빌딩 블록(Grafana 대시보드, Mosquitto 브로커 구성)으로 동작을 조립하므로, 구성이 요구사항을 충족하는지 검증합니다. 카테고리 5는 단 하나의 의도된 용도를 위해 작성된 커스텀 코드 — 우리 시뮬레이터, ISA-88/95 모델, 감사 트리거 — 이며, 다른 누구도 정확히 이것을 돌려 본 적이 없어 다른 누구의 사용도 증거가 되지 못하므로 전체 라이프사이클을 받습니다.

경제학적 요점은 노력이 균일하지 않다는 것입니다. PostgreSQL을 카테고리 5로 취급하여 — B-트리에 대해 요구사항과 코드 리뷰를 작성하는 것 — 은 파산을 부를 만큼 비쌀 뿐 아니라, 이미 그것을 보증하는 수백만 건의 프로덕션 배포보다 설득력 있습니다. 우리 감사 트리거를 카테고리 1로 취급하는 것은 태만한데, 그것을 돌려 본 사람은 우리뿐이기 때문입니다. 다이얼은 밸리데이션 예산을 잔여 위험이 실제로 깃든 곳에 쓰게 해 주며, 이 스택에서 그곳은 한 줌의 카테고리 5 구성 요소입니다. 아래의 CSA는 같은 직관을 날카롭게 합니다. 카테고리 5 안에서도 각 기능의 위험이 그것을 얼마나 강하게 테스트할지를 결정합니다.

CSA: 쓰기를 멈추고, 생각하기를 시작하라

수년간 CSV(Computer System Validation, 컴퓨터 시스템 밸리데이션)는 문서화 군비 경쟁으로 흘러갔습니다 — 버튼이 파란색이라는 것을 증명하는 스크린샷 수십 페이지 말입니다. FDA의 컴퓨터 소프트웨어 보증(Computer Software Assurance, CSA) 지침은 그 교정입니다. 2022년에 초안으로 발표되어 [2] 2025년에 확정되었고(2026년 초 행정적 업데이트와 함께) [3], CSA는 위험에 비례하여 노력을 쓰라고 말합니다. 환자에게 영향을 주는 고위험 기능은 엄격한 스크립트 테스트(각 단계를 미리 글로 적어 두고 증거를 남기는 방식)를 받고, 저위험 기능은 더 가벼운 비스크립트(탐색적) 또는 자동화 점검을 받습니다. 기록 관리는 그 반대가 아니라 위험에 따라 확장됩니다.

CSA는 OSS 스택에 주어진 선물입니다. 모든 것을 손으로 다시 도출하는 대신, 기존 증거 — 로그, 감사 추적, 자동화된 테스트 결과 — 를 활용하는 것을 명시적으로 축복합니다. 그것이 바로 pytest 실행과 CI 로그로 가득 찬 Git 저장소가 여러분에게 주는 것입니다. 보증 활동은 이렇게 됩니다. 의도된 용도를 식별하고, 위험을 판단하고, 확신을 세우는 가장 가벼운 테스트를 고르고, 자동화가 기록을 만들어 내게 둡니다.

그리고 이것은 이론이 아닙니다. IMPALA 컨소시엄 — 로슈(Roche), MSD, 베링거 인겔하임(Boehringer Ingelheim) — 은 커뮤니티가 개발한 오픈소스 R 패키지를 GxP/GCP 표준으로 독자적으로 밸리데이션했고, 그들이 정확히 어떻게 했는지를 공개했습니다 [8]. 규제 제약은 벤더 없는 소프트웨어를 방어 가능하게 밸리데이션할 수 있습니다. 그들이 사용한 패턴이 곧 여기서의 패턴입니다. 출처를 평가하고, 의도된 용도를 정의하고, 요구사항에 대해 테스트하고, 증거를 보관하는 것입니다.

공급자가 없을 때의 공급자 평가

GAMP 5는 공급자 평가(supplier assessment) — 보통 벤더 품질 시스템에 대한 설문이나 감사 — 를 기대합니다. 오픈소스에는 감사할 벤더가 없으므로, 대신 프로젝트아티팩트(artifact)를 평가합니다. 두 가지 질문입니다. 프로젝트가 신뢰할 만한 "공급자"인가, 그리고 여러분이 돌리고 있는 비트 단위로 똑같은 사본이 여러분이 생각하는 그것이 맞는가?

공급자 대리 지표로서 프로젝트 건전성 점수 매기기

요청할 품질 매뉴얼이 없을 때, GAMP CoP 오픈소스 지침은 공급자의 대리 지표로 프로젝트 자체를 평가하라고 말합니다 [7]. 그 기준은 구체적이며, 다행히 대부분 공개 기록에서 관찰 가능합니다.

공급자 품질 차원벤더에게 물을 것오픈소스 대리 지표(어디를 볼지)
거버넌스 & 소유권"이 제품에 대해 누가 책임지는가?"명명된 재단 또는 메인테이너 팀, 문서화된 거버넌스 모델, 공개된 보안 정책(SECURITY.md).
릴리스 & 결함 처리"패치 SLA는 어떻게 되는가?"(service-level agreement, 서비스 수준 협약 — 약속된 처리 시간)릴리스 주기, 태그된 시맨틱 버전, 변경 이력, 과거 CVE(Common Vulnerabilities and Exposures — 공개적으로 추적되는 보안 결함)까지의 수정 시간.
지속 가능성"5년 뒤에도 존재할 것인가?"기여자 수와 버스 팩터(bus factor — 프로젝트가 멈추기까지 몇 명의 메인테이너가 떠나야 하는가. 버스 팩터가 1이면 단일 장애점), 자금/후원, 다운스트림 채택자.
라이선스 명확성"우리가 무엇을 해도 되는가?"SPDX로 식별 가능한 라이선스, 모호한 이중 라이선스 함정 없음(아래 TimescaleDB 함정 참조).

이것은 벤더 감사보다 무른 기준이 아닙니다 — 인력이 빈약한 프로젝트라면 더 까다로운 기준일 수 있는데, 보안 정책 없는 1인 메인테이너 저장소는 평가에서 곧장 탈락할 수 있기 때문입니다. 그것은 정당한 발견 사항이며, 이 장 끝의 정직한 평결이 취약한 프로젝트를 평가하는 비용이 지원되는 제품을 사는 비용을 넘어설 수 있다고 짚는 이유입니다. OpenSSF Scorecard와 유사 도구는 이 작업의 상당 부분을 자동화하여, "이것이 신뢰할 만한 공급자인가?"를 직감이 아니라 반복 가능한, 증거 있는 점검으로 바꿉니다.

둘째 질문 — 돌아가는 사본이 여러분이 생각하는 그것이 맞는가 — 에 대해서는, 고정하고 검증합니다. 우리의 examples/platform/compose/compose.yaml(각 서비스를 컨테이너 이미지 — 프로그램과 그것이 실행에 필요로 하는 모든 것을 얼려 둔, 내려받을 수 있는 묶음 — 로 선언하는 Docker Compose 파일)은 모든 이미지를 사람이 읽을 수 있는 태그(tag)로 고정하고, 저장소의 examples/platform/versions.lock은 각 이미지에 대응하는 불변 매니페스트 다이제스트를 기록합니다(make lock으로 재생성).

# from examples/platform/compose/compose.yaml
postgres:
image: timescale/timescaledb:2.17.2-pg17
profiles: ["core"]

mosquitto:
image: eclipse-mosquitto:2.0.22
profiles: ["core"]

grafana:
image: grafana/grafana-oss:11.4.0
profiles: ["core"]

:2.17.2-pg17 같은 태그는 사람에게 친근한 라벨이라 다시 가리킬(re-point) 수 있습니다. 다이제스트(@sha256:…)는 콘텐츠 주소 지정(content-addressable) 방식이라 그럴 수 없습니다. 둘 다 고정하는 것이 "우리는 TimescaleDB 2.17을 돌렸다"와 "우리는 바로 이 TimescaleDB 2.17을 돌렸다"의 차이입니다. 그 구분이 설치를 재현 가능하게 만들며 — 재현성은 모든 IQ 주장의 토대입니다. 한 이미지는 자신의 빈틈에 대해 정직합니다. 커뮤니티 Fuseki 이미지는 다른 레지스트리(컨테이너 이미지를 호스팅하는 서버)로 옮겨 갔는데 — 다이제스트는 특정 레지스트리가 서빙하는 정확한 바이트로부터 계산되므로, 옛 고정값은 더 이상 해석되지 않습니다. 그래서 이제 여러분이 끌어오는 사본(미러)에서 다이제스트를 다시 가져와("해석") 둘 때까지 versions.lockVERIFY-BEFORE-USE 자리표시자를 싣고 있습니다 — docker buildx imagetools inspect apache/jena-fuseki:5.2.0이 그것을 출력합니다. 옮겨 간 커뮤니티 아티팩트의 출처야말로 공급자 평가가 드러내는 바로 그런 종류의 발견이라는 현실 세계의 환기입니다.

공급자 문서철로서의 SBOM: 무엇이, 어디서, 어떤 라이선스로

기계가 읽을 수 있는 공급자 문서철(dossier)이 소프트웨어 자재 명세서(Software Bill of Materials, SBOM)입니다. 우리는 컨테이너 이미지를 훑어 그 안의 모든 패키지를 나열하는 Apache-2.0 도구인 Syft로 하나를 생성합니다 [11]. 출력은 표준화된 포맷 — CycloneDX(이제 Ecma International ECMA-424) [9] 또는 SPDX(ISO/IEC 5962:2021로 비준) [10] — 이므로, 이 인벤토리는 손수 만든 스프레드시트가 아니라 형식을 갖춘, 검사에서 방어 가능한 기반을 가집니다. 다듬어진 CycloneDX 한 행은 다음과 같습니다.

// illustrative SBOM row (CycloneDX 1.6), produced by `make sbom` (Syft)
{
"type": "container",
"name": "timescale/timescaledb",
"version": "2.17.2-pg17",
"purl": "pkg:docker/timescale/timescaledb@2.17.2-pg17",
"hashes": [{ "alg": "SHA-256", "content": "sha256:3324f81c… (digest pinned in versions.lock)" }],
"licenses": [
{ "license": { "name": "PostgreSQL License" } },
{ "license": { "name": "Timescale License (TSL)" } }
]
}

그 행을 열별로 읽어 보세요. 각 열이 검사관의 질문 하나를 기계가 읽을 수 있는 형태로 답하고 있기 때문입니다. type: containername은 구성 요소가 무엇인지를, versionpurl(Package URL)은 어디서 왔는지를 말합니다 — pkg:docker/timescale/timescaledb@2.17.2-pg17은 스캐너가 레지스트리로 거슬러 해석할 수 있는, 전 지구적으로 모호하지 않은 좌표입니다. hashes 항목은 versions.lock에 기록된 것과 같은 SHA-256 매니페스트 다이제스트를 싣고 있어, 인벤토리는 다시 가리킬 수 있는 태그가 아니라 비트 단위로 똑같은 아티팩트에 고정됩니다. 그리고 licenses어떤 라이선스인지에 답하는데 — 이 열이 SBOM을 부품 목록에서 법적 문서철로 바꿉니다. 우리 저장소에서 SBOM은 손으로 쓰는 것이 아닙니다. make sbomcompose.yaml의 각 이미지에 대해 Syft를 돌려 이미지마다 하나의 CycloneDX 파일을 compliance/ 아래에 씁니다(예: compliance/sbom-timescale__timescaledb__2.17.2-pg17.cdx.json). 그래서 문서철은 돌아가는 이미지로부터 재생성되며, 결코 손으로 타이핑되지 않습니다.

그 하나의 아티팩트가 검사관이 물을 세 가지 질문에 답합니다. 여기에 무엇이 들어 있는가, 어디서 왔는가, 어떤 라이선스인가. 그리고 그것은 산문만으로는 감출 수 있는 정직함을 강제합니다. 고정된 timescale/timescaledb:2.17.2-pg17커뮤니티(Community) 번들이며, PostgreSQL License(Apache-2.0 코어)와 소스 공개(source-available) 방식의 Timescale License(TSL) — 읽고 실행하는 것은 무료이지만 OSI가 승인한 오픈소스는 아닌 — 로 이중 라이선스되어 있습니다. 그래서 SBOM 행은 깨끗한 Apache-2.0이 아니라 TSL을 싣고 있습니다(엄격하게 Apache 전용인 빌드는 -oss 태그입니다). 이 책은 이 함정에 대해 의도적입니다(히스토리언(historian) 장 참조). 무료 TSL 커뮤니티 자동화 — 연속 집계(continuous aggregate)와 add_retention_policy — 를 사용하되, 필요하지 않은 단 하나의 TSL 기능인 Hypercore 컬럼스토어/압축(columnstore/compression)에는 손대지 않습니다. InfluxDB v3 대신 VictoriaMetrics를 싣고, 재배포에 대해서는 Grafana의 AGPL을 표시합니다. 라이선스 열은 장식이 아닙니다 — 공급자 등록부와 SBOM이 하나의 소스(versions.lock)를 공유하므로, 기록된 라이선스는 고정된 이미지가 실제로 무엇이든 그것이며, 인벤토리는 돌아가는 스택에서 조용히 멀어질 수 없습니다.

추적성 매트릭스: URS → IQ/OQ/PQ

밸리데이션은 근본적으로 "우리가 필요하다고 말한 것을 우리가 만들었는가?"에 답하는 일입니다. 그것을 증명하는 아티팩트가 추적성 매트릭스(traceability matrix)입니다. 모든 사용자 요구사항(URS)은 앞으로 하나의 테스트에 매핑되고, 모든 테스트는 뒤로 하나의 요구사항에 매핑됩니다. 테스트되지 않은 것 없고, 추적되지 않은 것 없습니다.

다음은 우리 스택의 요구사항-대-테스트 매트릭스의 한 조각으로, 저장소가 compliance/ 아래에 실어 보낼 CSV로 표현한 것입니다(여기서 말하는 "골든 배치(golden batch)"는 책이 모든 것을 추세 비교하는 기준이 되는 표준 참조 실행입니다).

# illustrative compliance/traceability.csv (URS -> test, generated from test IDs)
urs_id,requirement,gamp_cat,risk,verifies,test_id
URS-001,"Historian stores all bioreactor tags for a batch",1,High,test_historian_loaded,tests/test_db.py::test_historian_loaded
URS-002,"Readings within a batch resolve to their ISA-88 phase (all four golden-batch phases present)",5,High,test_contextualization_joins_phase,tests/test_db.py::test_contextualization_joins_phase
URS-003,"Record changes are attributable, reasoned, tamper-evident",5,Critical,test_audit_captures_update,tests/test_db.py::test_audit_captures_update
URS-004,"The audit hash chain has no broken links",5,Critical,test_alcoa_chain_intact,tests/test_db.py::test_alcoa_chain_intact
URS-005,"Generated process data is deterministic & reproducible",5,Medium,test_determinism_two_runs_identical,tests/test_simulator.py::test_determinism_two_runs_identical

test_id 열이 핵심입니다. 각 요구사항은 산문 한 단락이 아니라 실행되는 함수를 가리킵니다. 그것이 실전에서의 CSA입니다 — 증거는 옮겨 적는 것이 아니라 생성되는 것입니다.

추적성 매트릭스 한 행의 해부

세 번째 행을 열별로 분해해 봅시다. 모든 열이 저마다 다른 일을 하고 있으며, 마지막 열이야말로 이 매트릭스를 서술적인 것이 아니라 실행 가능한 것으로 만들기 때문입니다. URS-003 — "기록 변경은 귀속 가능하고, 사유가 있으며, 변조 감지 가능하다" — 는 표에 있는 두 개의 Critical 위험 요구사항 중 하나이며, 검사관이 가장 강하게 압박할 바로 그것입니다.

URS-003에 대한 traceability.csv 한 행의 신원 카드 해부: urs_id, requirement, gamp_cat 5, Critical 위험 알약, verifies 열, 그리고 pytest 노드 tests/test_db.py 콜론콜론 test_audit_captures_update로 해석되는 강조된 초록색 test_id 블록, 그리고 그 노드가 수행하는 두 가지 단언 — action이 UPDATE와 같다는 것과 verify_chain이 깨진 링크 0을 반환한다는 것 — 을 나열한 보라색 패널. 추적성 행의 각 열은 한 가지 역할을 맡습니다. test_id는 실행 가능한 핵심입니다 — CI가 모든 커밋마다 다시 실행하는 pytest 노드를 가리키므로, 증거는 옮겨 적는 것이 아니라 생성됩니다.

저자가 AI의 도움을 받아 직접 제작한 그림입니다.

처음 다섯 열은 어떤 서류철 기반 매트릭스라도 실었을 평범한 장부 기록입니다. 안정적인 urs_id, 평이한 언어의 requirement, 밸리데이션 등급을 정하는 gamp_cat(5, 커스텀 코드), 테스트 엄격도를 정하는 risk(Critical), 그리고 점검을 명명하는 verifies 라벨입니다. 이 문서의 종류를 바꾸는 것은 여섯 번째 열입니다. test_id는 페이지 참조나 스크린샷 폴더로 해석되지 않습니다. tests/test_db.py::test_audit_captures_update — CI가 실행할 수 있는 pytest 노드 주소 — 로 해석됩니다. 아래 OQ에서 해부할 그 노드는 사용자와 사유를 곁들여 실제 UPDATE를 수행한 뒤, 변경이 포착되었다는 것(action == "UPDATE")과 변조 감지 해시 체인이 깨지지 않았다는 것(verify_chain()이 0을 반환) 모두를 단언합니다. 전통적 CSV 매트릭스는 verifies 열을 사람이 한 번 옮겨 적은 증거에 겨눕니다. 이것은 기계가 재생성하는 증거에 겨눕니다 — 그것이 CSV와 CSA의 모든 차이입니다. (정직한 틀: 저장소는 아직 문자 그대로의 compliance/traceability.csv를 싣고 있지 않습니다 — 매트릭스는 테스트 ID에서 생성되며, 그것이 해부하는 실제 아티팩트는 그 ID가 명명하는 살아 있는 pytest 노드입니다.)

그래프로서의 매트릭스: 요구사항은 트리플, 테스트는 셰이프

추적성 CSV는 조용히 시맨틱한 형태를 띠고 있으며, 그것을 명명하는 일은 이 장을 Fuseki를 돌리는 지식 그래프 장 — 위 공급자 등록부에 있는 바로 그 카테고리 1 트리플스토어 — 과 잇습니다. 각 행은 한 요구사항에 관한 사실을 단언하며, 사실은 트리플(triple)(subject-predicate-object, 관계를 일급 데이터로 저장하는 그래프 데이터 모델인 RDF의 원자)입니다. 트리플로 모델링한 URS-003은 그저 그래프 형태의 그 행입니다 — bp:URS-003 bp:risk "Critical", bp:URS-003 bp:gampCat 5, bp:URS-003 bp:verifiedBy bp:test_audit_captures_update — 그리고 매트릭스가 강제하려고 존재하는 "모든 테스트는 뒤로 한 요구사항에 매핑되고, 추적되지 않은 것 없다"는 규칙은 바로 SHACL(Shapes Constraint Language) 셰이프입니다. verifiedBy 에지를 갖지 않은 요구사항 노드를 실패시키는 폐쇄 세계(closed-world) 게이트로, Book 4가 릴리스 명세를 실행 가능한 게이트로 만드는 데 쓰는 것과 같은 메커니즘입니다.

# illustrative: the "nothing untraced" rule as a SHACL shape (closed-world).
bp:TraceableRequirementShape a sh:NodeShape ;
sh:targetClass bp:Requirement ;
sh:property [ sh:path bp:verifiedBy ; sh:minCount 1 ;
sh:message "Requirement has no verifying test — untraced." ] ;
sh:property [ sh:path bp:gampCat ; sh:datatype xsd:integer ; sh:maxCount 1 ] .

sh:minCount 1은 매트릭스가 산문으로 말하는 바를 그래프가 말하는 것입니다 — 누락된 에지는 지금 실패이지 열린 질문이 아닙니다 — 그래서 OWL의 개방 세계 추론이 아니라 SHACL의 폐쇄 세계 입장이 완전성 점검에 맞는 도구입니다(커버리지 빈틈은 있어야 하는데 없는 것입니다). 추적성 질문 자체는 SPARQL 역량 질문(competency question)으로 읽힙니다 — 모델이 답할 수 있어야 하는 질의로, Book 4가 23개 역량 질문을 PASS/FAIL 점검으로 돌리는 방식 그대로 인수 테스트로 쓰입니다.

# competency question: which Critical-risk requirements have no verifying test? (must return zero)
PREFIX bp: <https://example.org/bioproc#>
SELECT ?req WHERE {
?req a bp:Requirement ; bp:risk "Critical" .
FILTER NOT EXISTS { ?req bp:verifiedBy ?t . }
}

정직한 경계는 SHACL이 늘 짊어지는 그것과 같습니다. 셰이프는 매트릭스가 완전하고 잘 형성되었음 — 모든 요구사항이 추적되고, 모든 테스트가 명명됨 — 을 증명하는 것이지, 그것이 명명하는 테스트가 올바름을 증명하지는 않습니다. 완전성은 기계가 보장하는 그래프 속성이지만, 올바름은 여전히 게이트가 기록하되 대체하지 않는 자격자(qualified-person) 판단에 달려 있습니다. 그리고 디지털 스레드 그래프와 마찬가지로, 이 추적성 그래프는 테스트 ID의 파생(derived) 뷰가 될 것이므로, GxP 관련 사실을 담는 어떤 그래프에든 시맨틱 장이 요구하는 것과 같은 밸리데이션되고 변경 관리되는 로드를 받습니다.

사용자 요구사항이 GAMP 범주화와 위험 평가를 거쳐 IQ, OQ, PQ 단계로 흘러가는 파이프라인. IQ는 고정된 이미지 다이제스트를 검증하고, OQ는 살아 있는 스택에 대해 pytest를 실행하며, PQ는 엔드투엔드 배치를 가동하고, 이 모두가 검사 증거로서 서명된 추적성 매트릭스와 SBOM으로 모여든다.

작성된 요구사항에서 실행 가능한 테스트로: OSS 밸리데이션 라이프사이클은 GAMP 범주화와 위험을 각 구성 요소가 얼마만큼의 적격성 평가를 받을지 정하는 다이얼로 다루고, 그런 다음 IQ를 다이제스트로 고정된 매니페스트로, OQ를 자동화된 pytest로, PQ를 엔드투엔드 배치로 포착합니다 — 모든 단계가 URS로 거슬러 추적됩니다.

저자가 AI의 도움을 받아 직접 제작한 그림입니다.

IQ: 다이제스트에 고정된 설치 매니페스트

설치 적격성 평가(Installation Qualification)는 "올바른 것이, 올바르게 구성되어, 올바른 자리에 설치되었는가?"에 답합니다. 코드형 인프라에서는 설치 자체가 하나의 파일이므로 이것은 거의 공짜입니다. IQ 매니페스트는 돌아가는 스택의 구성 요소와 그 다이제스트의 스냅샷입니다 — 배포된 것이 명세된 것과 일치한다는 증거입니다.

// illustrative compliance/iq_manifest.json (captured from the running stack)
{
"captured_utc": "2026-06-14T09:00:00Z",
"compose_file_sha256": "…",
"components": [
{ "service": "postgres", "image": "timescale/timescaledb:2.17.2-pg17",
"digest": "sha256:3324f81c…", "gamp_category": 1, "profile": "core" },
{ "service": "grafana", "image": "grafana/grafana-oss:11.4.0",
"digest": "sha256:d8ea3779…", "gamp_category": 1, "profile": "core" }
]
}

매니페스트가 SBOM과 공급자 등록부가 쓰는 것과 같은 versions.lock 다이제스트에서 생성되므로, 통과하는 IQ는 세 가지를 한꺼번에 증명합니다. 올바른 버전이 설치되어 있고, 그것이 라이선스 인벤토리와 일치하며, CI가 테스트한 것과 일치한다는 것입니다. 다이제스트가 어긋나면, 다음 절의 OQ 스위트는 요란하게 실패하도록 설계되어 있습니다 — 그것이 바로 핵심입니다.

IQ 매니페스트 한 항목의 해부

추적성 행을 분해했던 방식 그대로 구성 요소 하나를 분해해 봅시다. IQ 매니페스트의 힘은 단 하나의 필드에 집중되어 있기 때문입니다. 저장소에 실제로 존재하는 아티팩트는 platform/versions.lock이며 — 위의 매니페스트 JSON은 같은 데이터의 표현입니다 — 그래서 우리는 실제 versions.lock 한 줄을 해부합니다. postgres 서비스, timescale/timescaledb:2.17.2-pg17, 다이제스트 sha256:3324f81c…에 고정된 것입니다.

postgres 서비스에 대한 platform/versions.lock 한 항목의 신원 카드 해부: service, image timescale/timescaledb 2.17.2-pg17, gamp_category 1, profile core 행, 비트 단위 IQ 주장으로 표시된 강조된 초록색 sha256 다이제스트 블록 3324f81c…, 그리고 같은 다이제스트가 세 아티팩트 — versions.lock, Syft SBOM, IQ 매니페스트 — 에 의해 읽혀 설치 = 라이선스 = CI-테스트됨임을 보여주는 보라색 패널. versions.lock 한 줄을 열별로: 태그는 무엇을 돌리려 했는지를 기록하고, 불변의 sha256 다이제스트는 비트 단위 IQ 주장입니다. 그리고 세 아티팩트가 같은 다이제스트를 읽으므로 그것들은 서로 어긋날 수 없습니다.

저자가 AI의 도움을 받아 직접 제작한 그림입니다.

service, image:tag, gamp_category, profile 필드는 맥락입니다. 이것이 어떤 compose 서비스인지, 어떤 사람이 읽을 수 있는 이미지가 요청되었는지, 카테고리 1 인프라(설치를 검증하되 다시 빌드하지 않는다)인지, 어떤 프로파일이 그것을 띄우는지를 말합니다. 하중을 짊어지는 필드는 digest입니다. :2.17.2-pg17 같은 태그는 가변적입니다 — 레지스트리가 내일 그것을 새로 빌드된 이미지로 다시 가리킬 수 있으므로 — 태그만으로는 "우리는 TimescaleDB 2.17을 돌리려 했다"는 뒷받침할 뿐, "우리는 바로 이 이미지를 돌렸다"는 아닙니다. sha256 매니페스트 다이제스트는 콘텐츠 주소 지정 방식입니다. 이미지 바이트로부터 계산되므로 조용히 다시 가리킬 수 없으며, 따라서 IQ가 의지하는 비트 단위 주장입니다. 조용한 초능력은 같은 다이제스트가 세 개의 다른 아티팩트에 의해 읽힌다는 것입니다 — versions.lock 공급자 등록부, Syft SBOM의 hashes 필드, 그리고 IQ 매니페스트의 digest 필드 — 모두 하나의 소스에서 재생성됩니다. 그래서 단 한 번의 통과하는 IQ가 동시에 설치(올바른 이미지가 배포됨), 라이선스(SBOM에 기록된 라이선스와 일치), CI-테스트됨(make test가 가동한 것과 일치)을 증명합니다. 지문 하나, 보증 셋, 옮겨 적기 없음.

OQ: 저장소의 테스트가 증거다

운영 적격성 평가(Operational Qualification)는 "운영 범위 전반에서, 마땅히 해야 할 일을 해내는가?"에 답합니다. 이 책의 실습 지향적 성격이 보답하는 지점이 여기입니다. 우리는 이미 OQ를 작성해 두었습니다. examples/tests/pytest 스위트 [12]pytest는 표준 파이썬 테스트 도구로, 작은 test_… 함수들을 찾아 실행하고 통과/실패를 보고합니다 — 는 나중에 덧붙인 개발자 편의 도구가 아니라 운영 증거이며, 살아 있는 compose 스택에 대해 실행됩니다.

examples/tests/test_db.py를 보세요. 이 테스트들은 돌아가는 PostgreSQL+TimescaleDB에 연결하여 시스템이 요구사항대로 동작함을 단언합니다. 히스토리언 및 맥락화(contextualization) 점검은 URS-001과 URS-002를 검증합니다.

# from examples/tests/test_db.py
def test_historian_loaded(conn):
n = _scalar(conn, "select count(*) from ts.sensor_reading where batch_id='BATCH-2026-001'")
assert n > 300_000 # 16 tags x ~20160 minutes


def test_contextualization_joins_phase(conn):
# the golden batch should surface all four named ISA-88 phases (LEFT JOIN allows NULL outside a window)
rows = _scalar(conn, "select count(distinct phase_name) from s88.v_batch_sensor "
"where batch_id='BATCH-2026-001' and phase_name is not null")
assert rows >= 4 # Inoculate, Growth, Production, Harvest

그 네 개의 페이즈는 유가식(fed-batch)의 전형적인 흐름입니다 — 세포로 배양을 시작해 가동하면서 영양분을 공급하는 과정(Book 1의 주제인 물리적 공정)으로, 바이오리액터(세포가 자라는 용기)를 접종하고, 생존 세포를 키우고, 항체 생산으로 전환한 뒤, 수확·청징(액체에서 세포를 걸러 냄)하는 것 말입니다. 히스토리언 점검이 세는 16개 태그는 바이오리액터의 계측입니다 — 온도, pH, 용존 산소, 교반(각각 측정된 공정값과 그 설정값으로 운반되므로, 네 개의 양이 8개 태그를 이룸), 두 개의 영양 피드(누적 피드 질량, 단위 kg), 용기 압력과 작업 부피, 두 개의 오프가스 채널(CO2와 O2), 그리고 온라인 글루코스와 역가(리터당 항체 그램)입니다 — 이 여덟 개의 단일 공정값 채널이 8개를 더하니, 네 개의 PV-플러스-설정값 쌍과 여덟 개의 단일 채널이 어떻게 도합 16개 태그를 이루는지 알 수 있습니다. 14일짜리 배치 전반에서 각 태그는 분당 한 번 샘플링되므로(14일은 약 20,160분), 16개 태그 곱하기 대략 20,160분은 30만 건이 넘는 판독값입니다 — 테스트가 단언하는 그 개수입니다.

두 개의 데이터 무결성 테스트가 가장 중요한데, Critical 위험인 URS-003과 URS-004 — 검사관이 가장 신경 쓰는 ALCOA+ 통제(Attributable, Legible, Contemporaneous, Original, Accurate에 더해 Complete, Consistent, Enduring, Available — 규제 당국이 GMP 기록에 요구하는 데이터 무결성 속성) — 를 검증하기 때문입니다. test_audit_captures_update는 사용자와 사유를 제공하며 실제 UPDATE를 수행한 뒤, 변경이 포착되었는지 그리고 변조 감지 해시 체인(tamper-evident hash chain)이 여전히 온전한지를 단언합니다. 해시 체인(hash chain)은 모든 감사 행을 그 앞 행에 묶는데, 새 행과 앞 행의 지문(fingerprint) 양쪽으로부터 계산된 지문(해시)을 저장함으로써 그렇게 합니다. 따라서 어떤 항목이라도 삭제하거나 재배열하면 그 뒤 모든 링크의 계산이 깨지며 — 바로 그것이 조용한 변조를 감지 가능하게 만듭니다. verify_chain()은 체인을 훑어 깨진 링크들을 반환합니다(0이면 온전함).

# from examples/tests/test_db.py
def test_audit_captures_update(conn):
# an UPDATE must record old + new + who + why and keep the chain intact
with conn.cursor() as cur:
cur.execute("select set_config('app.user','pytest',false), "
"set_config('app.reason','test correction',false)")
cur.execute("update lab.result set value = value where result_id = "
"(select result_id from lab.result limit 1)")
conn.commit()
last = _scalar(conn, "select action from audit.change_log "
"where app_user='pytest' order by seq desc limit 1")
assert last == "UPDATE"
assert _scalar(conn, "select count(*) from audit.verify_chain()") == 0

실행되고 로그로 남은 그 하나의 테스트가, 감사 추적을 처음부터 끝까지 가동하는 OQ 스크립트입니다. 스크린샷보다 더 나은 증거인데, 누구든 — 검사관까지 포함해 — 그것을 다시 실행하여 통과하는 것을 지켜볼 수 있기 때문입니다.

examples/tests/test_simulator.py의 시뮬레이터 테스트는 재현성 요구사항(URS-005)을 다룹니다. 결정론(determinism)은 그 자체로 하나의 밸리데이션 속성입니다. 같은 입력이 같은 출력을 내지 않는다면, 그 위에 쌓아 올린 어떤 것도 적격성을 평가할 수 없습니다.

# from examples/tests/test_simulator.py
def test_determinism_two_runs_identical():
a = fed_batch.simulate("BATCH-2026-001").tags["value"].to_numpy()
b = fed_batch.simulate("BATCH-2026-001").tags["value"].to_numpy()
assert np.array_equal(a, b)

그리고 전체 스위트는 examples/Makefile의 단 하나의 명령으로 가려져 있는데, 이것이 밸리데이션 엔지니어(또는 CI)가 운영 증거를 만들어 내기 위해 입력하는 바로 그 한 줄입니다.

# from examples/Makefile
test: ## run the test suite (determinism + db + analytics)
$(PY) -m pytest -q tests

make test가 OQ 실행입니다. 파일로 캡처된 콘솔 출력이 OQ 결과입니다. 깨끗한 머신에서 다시 실행하는 것 — CI가 모든 커밋마다 하는 일 — 은 여러분의 밸리데이션된 상태를 공짜로 회귀 테스트(regression-test)하는 것입니다.

카테고리 5 소프트 센서도 같은 처리를 받지만, 그 OQ는 예측 하한선 테스트입니다. 따로 떼어 둔(held-out) R-제곱 임계값(assert m["r2"] > 0.85)을 통과해야 하며, 그러지 못하면 CI가 실패합니다 — 모델에서 "올바르게 작동한다"는, 학습에 쓰이지 않은 데이터에 대해서도 여전히 명세대로 예측한다는 뜻입니다. 그 워크드 예제는 분석 및 캡스톤 장에 있습니다. 공정 분석: SPC, MVDA & 소프트 센서캡스톤: 한 배치, 처음부터 끝까지를 참조하세요.

카테고리 5 모델은 평범한 함수가 숨기지 않는 밸리데이션 함정을 숨기고 있으며, 그것을 명명하는 일이야말로 소프트 센서의 OQ를 자화자찬이 아니라 정직하게 유지하는 길입니다. 무작위 행 단위(row-wise) 학습/테스트 분할은 누수(leak)합니다. 한 셀 뱅크에서 나온 자매 배치들은 배지 로트와 스키드를 공유하므로, 거의 쌍둥이인 배치가 분할의 양쪽에 걸리면 모델이 답을 미리 보고 환상에 가까운 점수를 보고하게 됩니다. 방어 가능한 분할은 그룹화된(grouped) 분할 — 한 배치의 모든 판독값이 학습 또는 테스트 중 한쪽으로 통째로 가는 것 — 이며, 그래야 따로 떼어 둔 R-제곱이 본 적 없는 배치로의 일반화를 재는 것이지 본 배치의 암기를 재는 것이 아니게 됩니다. Book 5는 모델과 밸리데이션에서 이 GroupKFold / 리브원배치아웃(leave-one-batch-out) 폴드를 기본값으로 삼습니다. 예측 하한선과 나란히 가는, 분석 장에서 가동되더라도 밸리데이션 패키지에 속하는 두 가지 모델 전용 의무가 더 있습니다. 적용 범위(applicability domain)(새 배치가 학습 외피 밖에 놓이면 예측을 거절하는 게이트 — Hotelling T-제곱 / 잔차 점검 — 이므로, 센서가 제 깊이를 벗어나 추측하기보다 기권합니다)와, 공정 드리프트(process drift)(살아 있는 CHO 배양이 배치마다 진짜로 떠도는 것 — 히스토리언이 보존해야 할 실재 신호)와 모델 드리프트(model drift)(예측기가 그 움직이는 공정에 대해 낡아 가는 것 — 감지해야 할 결함) 사이의 깔끔한 구분이 그것이며, 이 구분을 중심으로 Book 5의 MLOps와 라이프사이클 장이 모니터링을 세웁니다. CSA 아래에서 모델 OQ의 깊이는 다른 모든 카테고리 5 기능과 똑같이 위험에 따라 확장됩니다. 출력이 릴리스나 피드 결정에 정보를 주는 소프트 센서는 스크립트화된 그룹화 CV 증거와 로그로 남긴 모델 계보(model lineage)(어떤 데이터셋 해시, 어떤 동결된 모델 버전, 어떤 CQA를 채점했는지 — IQ 매니페스트가 이미지 다이제스트를 포착하는 것과 똑같이 포착)를 얻는 반면, 읽기 전용 추세 뷰는 훨씬 덜 얻습니다. 모델은 카테고리 5 구성 요소이며, CSA의 위험 다이얼은 그것에도 변함없이 적용됩니다.

PQ: 배치가 곧 증거다

성능 적격성 평가(Performance Qualification)는 "실제 작업에, 실제 조건 하에서, 제대로 작동하는가?"에 답합니다. 우리에게 그것은 엔드투엔드 실행입니다. 완전한 시뮬레이션 유가식(fed-batch) CHO(Chinese Hamster Ovary, 중국 햄스터 난소 — 대부분의 치료용 항체를 키워 내는 주력 포유류 세포주. 그 물리적 공정은 Book 1의 주제입니다) 캠페인이 센서에서 출발해 히스토리언과 맥락화를 거쳐, 검토 가능하고 서명된 데이터셋과 감사 추적 검토 보고서까지 흘러가는 것 말입니다. PQ는 추상적인 요구사항이 실제 mAb 공정과 만나는 곳입니다. 구체적으로 PQ는 골든 배치 BATCH-2026-001 — 책이 모든 것을 추세 비교하는 기준이 되는 참조 실행으로, 역가(titer, 리터당 생산된 항체 그램)가 g/L로 쌓이고, 7일째에 의도적인 0.5 degC 온도 일탈(목표 설정값에서의 짧은 이탈)이 주입되는 14일짜리 유가식 — 을 재현하며, 그 일탈이 감사 추적이 설명할 수 있는 품질 플래그가 붙은 일탈로 드러남을 증명합니다. 그 경로는 make data && make loadtest_db/test_simulator 스위트(맥락화, 감사 체인, 결정론)로 가동되며, 서명된 검토 가능 데이터셋과 감사 추적 검토 보고서는 배포하는 현장이 그 위에 더하는 PQ 결과물입니다 — 책 전체에 대한 표제격 "구현은 가능하다" 증명입니다.

실제로 무엇이 지적되는가: 현장 실패 데이터포인트

검사관이 실제로 무엇을 적어 내는지 들여다보기 전까지는, 밸리데이션을 박스 체크 의식으로 취급하기 쉽습니다. 2010–2020년 제약사에 발부된 FDA 경고장(Warning Letter — 위반 사항을 시정해야 한다는 당국의 공식 서면 통지)을 회고적으로 분석한 연구는, 문서화 및 데이터 무결성 결함이 cGMP(current Good Manufacturing Practice, 현행 우수 제조 관리 기준 — "current"는 오늘날 시행 중인 기준을 뜻함) 경고장의 약 21%를 차지했고, 문서화는 평균적으로 경고장의 20–25% 정도에서 주요 결함으로 지적되었음을 발견했습니다 [13]. 이것들은 별난 발견이 아닙니다. 흔하디흔한 지적입니다 — 귀속되지 않는 기록, 부재하거나 비활성화된 감사 추적, 누가 무엇을 왜 바꿨는지로 거슬러 묶을 수 없는 데이터. 미국 의약품 GMP 관점에서 이것들은 21 CFR 211.68과 211.180, 그리고 21 CFR Part 11의 기록 관리 및 전자기록 기대치에 매핑됩니다 [5].

그 통계는 이 장의 접근법 전체를 비추는 정직한 거울입니다. 우리 추적성 매트릭스의 두 Critical 위험 요구사항 — URS-003(변경은 귀속 가능하고, 사유가 있으며, 변조 감지 가능하다)과 URS-004(해시 체인에 깨진 링크가 없다) — 은 바로, 누락되면 데이터 무결성 지적이 되는 통제들입니다. 우리가 가장 엄격하고 재실행 가능한 증거를 정확히 그 두 행에 겨누는 이유는, 현장 실패 기록이 규제 시설이 실제로 지적받는 곳이 거기라고 말하기 때문입니다. 귀속과 체인 무결성을 모든 커밋마다 다시 증명하는 자동화 OQ는 금박 입히기가 아닙니다. Form 483(FDA 조사관이 검사 종료 시 현장에 건네는 결함 목록)에 나타날 가능성이 가장 높은 항목을 방어하는 것입니다.

그리고 한계를 정직하게 명명할 가치가 있습니다. 통과하는 pytest 체인은 통제가 테스트되었을 때 작동했음을 증명하는 것이지, 작심한 내부자가 그것을 무력화할 수 없음을 증명하는 것이 아닙니다. 점검 자체에도 더 좁은 한계가 있습니다. verify_chain()은 어떤 링크도 삭제되거나 재배열되지 않았음을 증명하지만, 각 행 해시를 그 페이로드로부터 다시 계산하지는 않으므로, 이미 기록된 old_row/new_row에 대한 조용한 제자리 수정은 이 점검만으로는 잡히지 않습니다 — 거기서의 방어는 행 단위 해시입니다. 23장의 해시 체인이 보였듯, 감사 트리거를 비활성화하는 슈퍼유저는 여전히 그것을 우회할 수 있습니다 — 감지 가능성은 불가능성이 아닙니다. 위의 데이터포인트는 이 통제들을 강하게 테스트할 이유이지, 테스트가 그것들을 변조 불가능하게 만든다는 주장이 아닙니다.

왜 중요한가

이 장이 없다면, 앞선 스무 개 장은 취미 프로젝트일 뿐입니다. 규제 시설은 방어할 수 없는 소프트웨어를 돌릴 수 없습니다. 이것이 유독 오픈소스에 그토록 중요한 이유는 부재하는 벤더에 있습니다. 밸리데이션된 LIMS를 살 때는 공급자의 품질 시스템이 여러분 부담의 일부를 흡수합니다. OSS에서는 그 부담이 전적으로 여러분에게 떨어집니다 — 하지만 사라지지는 않으며, CSA와 자동화 테스트는 그것을 옛 스크린샷 주도의 CSV가 했던 것보다 더 싸게, 정직하게 떨치도록 만들어 줍니다. 저장소는 밸리데이션을 문서화 프로젝트에서 엔지니어링 프로젝트로 바꾸어, 증거가 소프트웨어를 빌드하는 바로 그 CI에 의해 생성되게 합니다.

실제 현장에서는

정직한 평결: 이 접근법은 실재하며, 실제로 행해지고 있습니다. IMPALA 컨소시엄이 공개한 커뮤니티 R 패키지 밸리데이션은, 거대 제약사가 GxP 맥락에서 독립적으로 평가된 OSS를 뒷받침할 것이라는 증거입니다 [8]. GAMP 5 제2판의 오픈소스 부록과 GAMP CoP 지침은, 그 프레임워크가 이를 명시적으로 지원한다는 것을 뜻합니다 [1] [7]. 그리고 2025년의 CSA 확정은, 이 장이 취하는 위험 기반·증거 활용 자세가 이제 영리한 우회책이 아니라 기대되는 검사 대비 접근법이라는 것을 뜻합니다 [3].

하지만 한계에 대해서는 잔인하리만치 분명히 합시다. 자동화된 pytest는 훌륭한 OQ 증거입니다. 그것이 밸리데이션 패키지의 전부는 아닙니다. 여러분에게는 여전히 빚이 있습니다. 자격 있는 사람이 서명한 밸리데이션 계획서와 보고서, 변경 관리 절차(모든 다이제스트 변경은 평가를 재촉발합니다), 주기적 검토, 일탈(deviation) 프로세스, 그리고 — 이 책이 거듭 강조하는 정직함 — 어떤 OSS 구성 요소도 박스에서 꺼낸 채로는 Part 11을 준수하지 않는다는 인식 말입니다. 23장의 해시 체인은 변조를 불가능하게 하는 것이 아니라 감지 가능하게 만듭니다. 트리거를 비활성화하는 슈퍼유저는 여전히 그것을 우회할 수 있습니다. 빈약하게 유지되는 프로젝트에 대한 공급자 평가 노력은 지원되는 제품을 사는 비용을 넘어설 수 있으며, 그 절충은 실패가 아니라 정당한 사업적 결정입니다. 이 장은 오픈소스로 거기에 도달할 수 있음을 보여줍니다. 결코 마지막 한 구간이 공짜인 척하지 않습니다.

핵심 용어

  • GxP — 규제 대상 제약 작업을 다스리는 "Good x Practice" 규정군(GMP, GLP, GCP). 이 스택이 만족해야 하는 규칙들이다.
  • GAMP 5 — GxP 전산화 시스템을 밸리데이션하기 위한 ISPE의 위험 기반 프레임워크. 그 범주(1 인프라 → 5 커스텀)가 밸리데이션 노력을 확장한다.
  • ALCOA+ — 규제 대상 기록이 지녀야 할 데이터 무결성 속성: Attributable, Legible, Contemporaneous, Original, Accurate에 더해 Complete, Consistent, Enduring, Available.
  • 코드형 인프라(Infrastructure-as-code) — 스택의 모든 구성 요소를 손으로 클릭하는 설치가 아니라 버전 관리되는 파일(여기서는 compose.yamlversions.lock)로 정의하는 것. 그래서 설치 자체가 다시 실행하고 감사할 수 있는 하나의 파일이 된다.
  • CSA(Computer Software Assurance, 컴퓨터 소프트웨어 보증) — 중량급 CSV에 대한 FDA의 위험 기반 후계자. 환자/데이터 위험에 비례하여 보증 노력을 쓰고, 기존 증거를 활용한다.
  • CSV(Computer System Validation, 컴퓨터 시스템 밸리데이션) — CSA가 개혁하는, 흔히 문서 중심적이던 전통적 접근법.
  • IQ / OQ / PQ — 설치, 운영, 성능 적격성 평가. 올바르게 설치되었는가, 올바르게 운영되는가, 실제 작업에 성능을 내는가.
  • URS(User Requirement Specification, 사용자 요구사항 명세) — 시스템이 해야 할 일에 대한 진술. 추적성 매트릭스의 뿌리.
  • 추적성 매트릭스(traceability matrix) — 각 URS에서 그것을 검증하는 테스트로(그리고 그 역으로)의 매핑.
  • 공급자 평가(supplier assessment) — 소프트웨어 공급자의 품질을 평가하는 것. OSS의 경우, 대신 프로젝트의 건전성을 평가하고 아티팩트를 검증한다.
  • SBOM — 소프트웨어 자재 명세서(Software Bill of Materials). 모든 구성 요소와 그 버전, 출처, 라이선스의 표준화된 인벤토리(CycloneDX/ECMA-424, SPDX/ISO 5962).
  • 이미지 다이제스트(image digest) — 컨테이너 이미지의 콘텐츠 주소 지정 SHA-256. 다이제스트로 고정하면 설치가 비트 단위로 재현 가능해진다.
  • purl(Package URL) — 소프트웨어 구성 요소에 대한 표준화된, 전 지구적으로 모호하지 않은 좌표(예: pkg:docker/timescale/timescaledb@2.17.2-pg17). SBOM에 실려 스캐너가 출처를 소스 레지스트리로 거슬러 해석할 수 있게 한다.
  • OpenSSF Scorecard — 오픈소스 프로젝트의 보안·유지보수 관행(보안 정책, 릴리스 서명, 브랜치 보호, 의존성 업데이트 위생)에 점수를 매기는 자동화 점검. 공급자 평가의 프로젝트 건전성 측면을 반복 가능한, 증거 있는 결과로 바꾼다.
  • 그룹화된 분할 / 리브원배치아웃(leave-one-batch-out) — 카테고리 5 모델을 위한 누수 없는 학습/테스트 분할로, 한 배치의 모든 판독값이 학습 또는 테스트 중 한쪽으로 통째로 간다. 그래야 따로 떼어 둔 점수가 거의 쌍둥이인 자매 배치의 암기가 아니라 본 적 없는 배치로의 일반화를 잰다.
  • 적용 범위(applicability domain) — 새 배치가 학습 외피 밖에 놓이면 예측을 거절하는 모델의 게이트(Hotelling T-제곱 / 잔차 점검). 그래서 소프트 센서가 제 깊이를 벗어나 추측하기보다 기권한다.
  • 공정 드리프트 대 모델 드리프트(process drift vs. model drift) — 공정 드리프트는 살아 있는 CHO 배양이 배치마다 진짜로 떠도는 것(보존해야 할 실재 신호), 모델 드리프트는 예측기가 그 움직이는 공정에 대해 낡아 가는 것(감지해야 할 결함). 둘을 뒤섞으면 모니터가 헛경보를 울리거나 진짜 변화를 놓친다.
  • 모델 계보(model lineage) — 배포된 모델이 어떤 데이터셋 해시, 동결된 모델 버전, CQA를 채점했는지를 IQ 매니페스트가 이미지 다이제스트를 포착하듯 로그로 남긴 기록. 그래서 감사가 릴리스된 로트에서 그것을 건드린 모델로 거슬러 갈 수 있다.
  • SHACL 추적성 셰이프 / 역량 질문 — "추적되지 않은 것 없다"는 규칙을 폐쇄 세계 SHACL 셰이프(verifiedBy 에지가 없는 요구사항 노드는 실패)와 SPARQL 질의(어떤 Critical 요구사항에 테스트가 없는가?)로 표현하여 PASS/FAIL 인수 테스트로 돌리는 것. 추적성 매트릭스의 그래프 형태.

다음 이야기

밸리데이션은 "이 시스템은 신뢰할 만한가?"에 답합니다 — 하지만 신뢰는 전 지구적이지 않습니다. FDA의 Part 11을 만족하는 기록이라도 EU, 한국, 중국, 일본에서는 다른 거주(residency), 보존(retention), 감사 추적 기대치에 직면할 수 있습니다. 다음 장 여러 관할권에 걸친 데이터: FDA, EU, PIC/S, NMPA, PMDA, MFDS는 밸리데이션된 스택을 국경 너머로 데려가, 그 서로 다른 규칙들을 분기된 배포가 아니라 데이터와 정책으로 인코딩하는 방법을 보여줍니다.