본문으로 건너뛰기

전자기록과 전자서명: 오픈소스로 구현하는 Part 11 / Annex 11

📍 현재 위치: 제5부 "신뢰(Trust)"입니다. 이전 장에서는 코드로 우리 데이터를 변조-증거가-남도록(tamper-evident) 만들었습니다. 이제 더 어려운 질문을 던집니다 — 전부 오픈소스(open source)로 구성한 스택이, 전자기록이 서명된 종이 문서를 대신할 수 있다고 말하는 규정을 정말로 충족할 수 있을까요? 우리는 실제로 작동하는 통제(control)를 구축하고, 작동하지 않는 통제는 정직하게 기록으로 남깁니다.

쉽게 말하면

종이 배치 기록(batch record)을 떠올려 보세요. 모든 기재 항목에는 이니셜과 날짜가 붙고, 절대로 지워지지 않습니다 — 실수는 한 줄을 그어 표시해서 이전 값이 여전히 보이게 하고, 맨 아래의 서명은 "특정한 이 사람이 이를 검토했고, 그 내용에 책임진다"는 뜻입니다. 21 CFR Part 11과 EU Annex 11은 단지 이 모든 일을 종이가 아니라 컴퓨터에서 하기 위한 규칙일 뿐입니다. 누가, 무엇을, 언제, 왜 바꾸었는지가 자동으로 기록되어야 하고, 서명은 그것이 서명하는 바로 그 기록에 끊어지지 않게 용접되어 있어야 합니다. 오픈소스는 이 대부분을 가능하게 해 줍니다 — 자동 감사 추적(audit trail), 암호 서명(cryptographic signature), 신뢰할 수 있는 시계로부터의 타임스탬프(time-stamp). 마지막 한 구간, 즉 시스템이 버튼을 누르는 사람이 맞는 사람임을 증명해야 하는 지점에서부터, 절차서를 쓰기 시작하고 상용 부품에 손을 뻗게 됩니다.

이 장에서 다루는 내용

이 장은 플랫폼이 규제 당국과 마주하는 장입니다. 우리는 새 센서나 새 대시보드를 추가하는 것이 아니라, 이미 구축한 관계형(relational) 골격을 가져다가, 그것이 담고 있는 기록이 법적 의미에서 신뢰할 수 있는지를 묻습니다. 흐름은 다음과 같습니다.

  • 21 CFR Part 11 [1]과 EU Annex 11 [2]이 조항별로 실제로 무엇을 요구하는지, 그리고 PIC/S PI 041-1 [3]이 그 조항들을 조사관(inspector)이 무엇을 보는지로 어떻게 풀어내는지.
  • 두 가지 방식으로 구축한 작동하는 감사 추적(audit trail): 데이터베이스 세션 수준의 pgAudit [4], 그리고 동반 저장소(companion repo)에서 가져온, 이전 값/새 값/누가/언제/왜를 포착하는 트리거(trigger) 기반 해시-체인(hash-chained) audit.change_log 테이블.
  • eLabFTW와 RFC 3161 신뢰 타임스탬프 [5][6]를 활용한 전자서명(electronic signature), 그리고 Keycloak [7]으로 뒷받침되는 변경-사유 서명 서비스.
  • 감사 추적 검토 쿼리(audit-trail review query) — 여러분의 품질 부서(quality unit)가 배치 출하(batch release) 전에 실제로 실행하는 산출물.
  • 냉정하리만치 정직한 격차 등록부(gap register): 오픈소스만으로는 부족한 Part 11 조항들, 그리고 각 격차를 무엇이 메우는지.

파일 경로가 붙어 있는 모든 것은 examples/에 있는, 우리 지속적 통합(continuous-integration) 파이프라인에서 실제로 실행된, 검증된 코드입니다. *예시용(illustrative)*이라고 표시된 모든 것은 노트북에서 돌릴 수 없는 서비스를 위한 현실적인 코드 조각입니다 — 정직하게 보여 줄 뿐, 실행된다고 주장하지 않습니다.

규정이 실제로 말하는 바

Part 11은 짧고 오래되었습니다 — 1997년에 발효되었습니다 [1] — 그리고 그 묘미는 단 하나의 기술도 지명하지 않는다는 데 있습니다. Part 11은, 전자기록을 생성하는 시스템이 몇 가지 통제를 강제한다는 조건에서 전자기록이 종이를 대신할 수 있다고 말합니다. 우리에게 중요한 통제는 세 조항에 들어 있습니다. §11.10(e)는 기록을 생성·수정·삭제하는 작업자 행위를 기록하는 "안전하고, 컴퓨터가 생성하며, 타임스탬프가 찍힌 감사 추적"을 요구하며, 결정적으로 이전에 기록된 정보를 가려서는 안 된다고 못 박습니다. §11.70은 전자서명이 "서명이 잘려 나가거나, 복사되거나, 그 밖의 방식으로 옮겨져" 다른 기록을 위조하는 데 쓰일 수 없도록 "각각의 전자기록에 연결될" 것을 요구합니다. §11.200은 서명 자체를 규율합니다. 서명은 최소 두 개의 서로 다른 식별 요소(사용자 이름 + 비밀번호를 떠올리세요)를 사용해야 하고, 한 세션에서 최초 서명 이후 이어지는 모든 서명은 적어도 한 요소를 다시 실행해야 합니다.

2003년 적용 범위 및 적용에 관한 지침(Scope and Application guidance) [8]은 실무자의 정신 건강을 지켜 주는 문서입니다. FDA는 Part 11을 리스크 기반 자세로 좁혔고, 일부 통제에 대해서는 단속 재량(enforcement discretion)을 행사했지만, 감사 추적, 기록 보존, 전자서명 조항(§§11.10, 11.30, 11.50, 11.70, 11.100, 11.200, 11.300)에 대한 준수는 여전히 기대한다고 명시했습니다. 바로 그 문장이 우리 격차 등록부를 따라 그어지는 선입니다. FDA가 여전히 단속하는 조항이야말로, 우리가 오픈소스로 충족하거나 충족할 수 없음을 인정해야 하는 바로 그 조항들입니다.

EU Annex 11 [2]은 유럽 쪽 대응물이며, 군데군데 더 엄격합니다. 조항 9는 모든 GMP 관련 변경과 삭제에 대해 문서화된 사유를 포함한 감사 추적을 요구하고, 조항 12는 접근 통제(access control)를 요구하며, 조항 14는 전자서명이 손으로 쓴 서명과 동일한 효력을 갖고 그 기록에 영구적으로 연결될 것을 기대합니다. 조사관의 데이터 무결성(data integrity) 지침인 PIC/S PI 041 [3]은 여기에 운영상의 기대를 더합니다. 감사 추적은 단지 보관하는 것이 아니라 **검토(review)**해야 하며, 그 검토는 기록이 의존되기 전에 — 즉 배치 출하 전에 — 이루어져야 합니다. FDA 자체의 데이터 무결성 Q&A [9]도 더 평이한 말로 같은 이야기를 합니다. GMP 데이터의 생성과 수정을 포착하는 감사 추적은 데이터 자체와 똑같은 엄격함으로 검토되어야 한다는 것입니다. 이 모두를 염두에 두세요 — 이 장 끝의 산출물은 "우리는 감사 추적을 가지고 있다"가 아니라 "우리는 누군가가 검토하는 감사 추적을 가지고 있다"입니다.

Part 11과 Annex 11 조항을 그것들을 충족하는 오픈소스 통제에 대응시키고, 절차나 상용 도구가 필요한 격차를 세 번째 열에 표시한 계층 다이어그램. 왼쪽에서 오른쪽으로: 규제 요건, 그것을 충족하는 오픈소스 통제, 그리고 남아 있는 정직한 격차. 녹색 띠(감사 추적, 귀속 가능한 변경 포착, 신뢰 타임스탬프)는 OSS에서 진정으로 달성 가능합니다. 황색 띠(서명 시 재인증, WORM 보존, 고가용성)는 검증된(validated) 시스템, 절차, 또는 상용 도구가 짐을 지는 지점입니다. Original diagram by the authors, created with AI assistance.

두 가지 방식의 감사 추적

"누가, 무엇을, 언제, 왜 바꾸었는지"를 포착할 수 있는 상호 보완적인 두 지점이 있고, 제대로 된 시스템은 둘 다 사용합니다.

첫 번째는 pgAudit [4]으로, 세션이 실행하는 실제 SQL 구문을 데이터베이스 서버 로그에 기록하는 PostgreSQL 확장(extension)입니다. 이는 변조에 강한 시스템 수준 기록(transcript)에 가장 가까운 오픈소스 유사물입니다. 모든 UPDATE lab.result …가, 애플리케이션이 손대기 전에, 데이터베이스 사용자와 서버 타임스탬프와 함께 그대로 기록됩니다. postgresql.conf에서 (또는 ALTER SYSTEM을 통해) 예시용 설정으로 활성화합니다.

# illustrative configuration — platform/db/pgaudit.conf
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write, ddl, role' # capture INSERT/UPDATE/DELETE, schema and grant changes
pgaudit.log_relation = on # one log entry per affected table
pgaudit.log_parameter = on # record the bound values, not just the statement text

pgAudit은 한 가지 — 불변의, 추가만 가능한(append-only) 구문 로그 — 에 탁월하고, 자신의 한계에 정직합니다. 자체 문서가 분명히 밝히듯, 슈퍼유저(superuser)는 세션 도중에 로깅 설정을 바꿀 수 있기 때문에 슈퍼유저를 안정적으로 감사할 수 없습니다. 그 한 문장이 우리 격차 등록부의 첫 항목이며, 우리가 pgAudit에서 멈추지 않는 이유입니다.

두 번째 지점은 애플리케이션 차원에서 의미 있는 감사 추적입니다. 검토자가 이해하는 용어로 업무상의 변경을 기록하는 테이블 — 이전 행, 새 행, 그 일을 한 사람, 그리고 사유. 이는 이전 장의 주인공이며 실제로 검증된 코드입니다. examples/platform/db/50-alcoa.sql에서 가져온, 변경 로그 테이블과 그 해시 체인입니다.

-- examples/platform/db/50-alcoa.sql
CREATE TABLE audit.change_log (
seq bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ts timestamptz NOT NULL DEFAULT clock_timestamp(),
db_user text NOT NULL DEFAULT current_user,
app_user text, -- set via SET app.user = '...'
table_name text NOT NULL,
action text NOT NULL, -- INSERT | UPDATE | DELETE
row_key text,
old_row jsonb,
new_row jsonb,
reason text, -- set via SET app.reason = '...'
prev_hash text,
row_hash text NOT NULL
);

그 스키마는, 거의 한 줄 한 줄, DDL로 표현된 Part 11 §11.10(e) 감사 추적입니다. old_rownew_row는 이전에 기록된 정보가 결코 가려지지 않음을 의미합니다 — 지우지-말고-줄을-그어라(strike-through-not-erase) 규칙입니다. app_user는 (데이터베이스 계정이 아니라) 사람으로서, ALCOA+의 "귀속 가능(Attributable)"과 Annex 11 조항 12 [2]를 충족합니다. reason은 Annex 11 조항 9의 문서화된 변경 사유입니다. 그리고 prev_hash/row_hash는 각 항목을 직전 항목에 사슬로 잇기 때문에, 이력의 어디에서든 삭제나 수정이 일어나면 체인이 깨지고 탐지됩니다.

트리거는 포착을 자동으로 만들어 줍니다 — 작업자가 감사 행을 쓰는 일을 잊을 수 없는 이유는, PostgreSQL 트리거와 역할(role) [10]을 사용해 모든 변경마다 데이터베이스가 그들을 대신해 써 주기 때문입니다. 같은 파일이 트리거를 규제 대상 테이블에 붙이고 검증기(verifier)도 함께 제공합니다.

-- examples/platform/db/50-alcoa.sql
CREATE TRIGGER audit_result AFTER INSERT OR UPDATE OR DELETE ON lab.result
FOR EACH ROW EXECUTE FUNCTION audit.log_change();
CREATE TRIGGER audit_batch AFTER INSERT OR UPDATE OR DELETE ON s88.batch
FOR EACH ROW EXECUTE FUNCTION audit.log_change();
CREATE TRIGGER audit_recipe_p AFTER INSERT OR UPDATE OR DELETE ON s88.recipe_parameter
FOR EACH ROW EXECUTE FUNCTION audit.log_change();

-- Verify the chain is intact: returns rows where the recomputed hash breaks.
CREATE OR REPLACE FUNCTION audit.verify_chain()
RETURNS TABLE(seq bigint, ok boolean) AS $$
WITH chained AS (
SELECT c.seq, c.row_hash, c.prev_hash,
lag(c.row_hash) OVER (ORDER BY c.seq) AS expected_prev
FROM audit.change_log c
)
SELECT seq, (prev_hash IS NOT DISTINCT FROM expected_prev) AS ok
FROM chained
WHERE prev_hash IS DISTINCT FROM expected_prev;
$$ LANGUAGE sql;

애플리케이션은 변경 전에 사람의 신원과 사유를 세션 변수로 설정하고, 트리거가 그것을 집어 옵니다. examples/tests/test_db.py에 있는 우리 테스트 스위트는 바로 그 왕복을 입증합니다 — UPDATEold + new + who + why(이전 값 + 새 값 + 누가 + 왜)를 기록하고 체인을 온전하게 유지함을.

# 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

정직한 단서를, 핵심이기에 여기서 다시 반복합니다. 트리거를 비활성화하거나 테이블을 다시 쓰는 슈퍼유저는 여전히 이를 우회할 수 있습니다. 해시 체인은 변조를 불가능하게 만드는 것이 아니라 탐지 가능하게 만듭니다. 그것이 OSS 스택에 맞는 올바른 설계입니다 — 다만 그 말은, 권한 오용을 막는 통제(직무 분리(separation of duties), 제한된 슈퍼유저 계정, 변경 불가능한 외부 로그 전송)가 여러분의 절차와 인프라에 존재한다는 뜻이며, 조사관은 그것을 보여 달라고 요청할 것입니다.

감사 추적 검토하기

추적을 포착하는 일은 쉬운 절반입니다. PI 041 [3]과 FDA [9] 모두 더 어려운 절반 — 검토 — 을 고집합니다. 동반 저장소는 무결성 점검을 명령 인터페이스에 곧장 연결해 둡니다. 저장소의 Makefile에서 가져왔습니다.

# examples/Makefile
alcoa: ## verify the ALCOA+ audit hash chain is intact (0 = good)
docker exec -e PGPASSWORD=bioproc sensor-to-submission-postgres-1 psql -U bioproc -d bioproc \
-c "select count(*) as broken_links from audit.verify_chain();"

시드 데이터가 들어간 스택에 대해 make alcoa를 실행하면, 검토할 수 있는 단 하나의 숫자가 반환됩니다.

broken_links
--------------
0
(1 row)

깨진 링크가 0이라는 것은, 이력의 어떤 항목도 기록된 이후 변경되지 않았다는 뜻입니다. 그러나 검토자에게는 "체인이 온전하다" 이상이 필요합니다 — 특정 기록에 대한, 사람에게 의미 있는 변경을 직접 봐야 합니다. 50-alcoa.sql의 트리거는 각 행의 row_keycoalesce(batch_id, sample_id)로 계산하므로, batch_id 없이 sample_id만 가진 lab.result 변경(examples/platform/db/30-lab-events.sql 참조)은 그 샘플로 키가 매겨집니다. 하나의 수확(harvest) 샘플에 대한 검토 쿼리(같은 실제 테이블을 대상으로 한 예시용 SQL)가 바로 여러분의 품질 부서가 출하 전에 실행하는 것입니다.

-- illustrative review query over examples/platform/db/50-alcoa.sql
SELECT ts, app_user, table_name, action, reason,
old_row ->> 'value' AS old_value,
new_row ->> 'value' AS new_value
FROM audit.change_log
WHERE row_key = 'BATCH-2026-001-OFF-007' -- a lab.result is keyed by its sample_id
ORDER BY seq;
ts | app_user | table_name | action | reason | old_value | new_value
-----------------------+----------+------------+--------+-----------------+-----------+-----------
2026-05-12 09:14:02+00 | aoh | result | INSERT | | | 4.81
2026-05-12 14:32:51+00 | mlee | result | UPDATE | transcription | 4.81 | 4.18
| | | | error corrected | |

그 행들을 읽는 검토자는 전체 이야기를 봅니다. 분석자 aoh가 샘플 BATCH-2026-001-OFF-007에 대해 4.81 g/L의 역가(titer)를 기록했고, 분석자 mlee가 나중에 전사(transcription) 오류를 사유로 들어 4.18 g/L로 정정했으며, 둘 다 타임스탬프가 찍히고 귀속 가능하며, 원래 값은 결코 파기되지 않았습니다. 샘플의 이력을 그 배치까지 끌어올리려면, 검토자는 lab.sample(그 batch_idBATCH-2026-001)을 거쳐 다시 조인합니다. 이것이 제 역할을 하고 있는 Part 11 감사 추적입니다 — 그리고 전적으로 오픈소스입니다.

전자서명과 신뢰할 수 있는 시간

감사 추적은 무엇이 바뀌었는지를 말합니다. 서명은 나는 이를 승인하며, 나는 바로 이 특정한 사람이다라고 말합니다. 바로 여기서 오픈소스가 진정으로 빛을 발하다가 벽에 부딪힙니다.

빛나는 부분: 우리 lab 프로필에 들어 있는 오픈소스 전자 실험 노트(electronic lab notebook)인 eLabFTW [6]은 실험을 잠그고(lock), 암호 서명하고, RFC 3161 신뢰 타임스탬프 [5]를 찍을 수 있습니다. RFC 3161은, 특정 바이트 시퀀스가 특정 시점에 존재했음을 증명하는 서명된 토큰을 독립적인 시간 인증 기관(Time-Stamp Authority, TSA)에 요청하는 표준입니다 — 소급 날짜를 붙일 수 없는 존재 증명(proof of existence)입니다. eLabFTW의 서명 흐름은 정확히 §11.70의 "영구적으로 연결됨" 속성을 만들어 냅니다. 서명과 타임스탬프 토큰이 기록 내용의 해시에 묶여 있어서, 서명을 잘라내어 다른 기록에 붙이면 깨지지 않고는 불가능합니다. 설정에서 임의의 RFC 3161 TSA를 가리키게 합니다(예시용).

# illustrative configuration — eLabFTW timestamping (config.php-equivalent settings)
ts_authority: custom # or a managed TSA such as FreeTSA / DigiCert
ts_url: https://freetsa.org/tsr
ts_hash: sha256 # algorithm for the proof-of-existence token
ts_login: "" # credentials if the TSA requires them

이는 실재하고 방어 가능한 §11.70 / Annex 11 조항 14 통제입니다. 변조 증거가 남고, 시간에 고정되며, 영구적으로 연결됩니다. 정직한 의존성은, 이제 신뢰가 공급자로서 적격성을 평가해야 할 외부 TSA에, 그리고 여러분이 검증해야 할 설정에 놓인다는 점입니다 — 기본 설치 상태의 eLabFTW은 즉시 사용 가능한(turnkey) Part 11 시스템이 아닙니다.

벽은 §11.200 — 재인증 규칙입니다. 준수하는 서명 표시(signing manifestation)는 서명의 의미(검토, 승인, 작성)를 포착해야 하고, 한 세션에서 최초 서명 이후 모든 서명에 대해 적어도 한 개의 식별 요소를 다시 실행해야 합니다. 우리 trust 프로필에 들어 있는 오픈소스 신원 공급자(identity provider)인 Keycloak [7]은 고유 사용자 ID, 역할 기반 접근 통제(role-based access control), 다중 인증(multi-factor authentication)을 제공하며, 이는 §11.10(d)/(g)와 Annex 11 접근 통제를 깔끔하게 충족합니다. 기본 설치 상태로 Keycloak이 하지 않는 일은, 서명하는 바로 그 순간에 상향(step-up) 재인증을 강제하는 것입니다. 만들 수는 있습니다 — 저장소의 signing-service 설계는 변경 사유를 포착하고 서명 전에 Keycloak 재인증을 강제합니다 — 그러나 그것은 여러분이 소유하고 검증해야 할 맞춤 코드(GAMP 5 카테고리 5 [11])이지, 켜기만 하면 되는 기능이 아닙니다. 의도한 계약(contract)을 예시용 API 형태로 보면 다음과 같습니다.

# illustrative — proposed examples/services/signing-service contract (not yet in repo)
POST /sign
Authorization: Bearer <fresh Keycloak token from step-up re-auth>
Content-Type: application/json

{ "record": "lab.result:91823", "meaning": "approved", "reason": "release review" }
→ 201 Created
{ "signed_hash": "sha256:9f2c…", "signer": "qa_reviewer_02",
"ts_token": "rfc3161:MIIE…", "linked_record_hash": "sha256:1b07…" }

이 흐름은 정직한 오픈소스입니다 — 신원에 Keycloak, 연결에 해시 체인, 시간에 RFC 3161 토큰 — 그러나 토큰이 신선함(fresh)을 강제하는 것, 그리고 누가 qa_reviewer 역할을 가질 수 있는지를 규정하는 SOP는 여러분이 직접 쓰고 방어해야 합니다.

정직한 Part 11 격차 등록부

이 절이 이 책의 제목을 정당화하는 부분입니다. GAMP 5 제2판 [11]은 비판적 사고와 공급자 증거에 대한 명료한 평가를 검증의 핵심으로 삼았습니다. 우리가 할 수 있는 가장 전문가다운 일은, 순수 OSS가 멈추는 지점이 정확히 어디인지를 표로 정리하는 것입니다.

Part 11 / Annex 11 통제OSS 상태정직한 격차
§11.10(e) / Annex 11 조항 9 — 감사 추적(이전/새/누가/언제/왜)충족audit.change_log + 트리거 + pgAudit기술적으로는 없음. 다만 여전히 검토해야 함(PI 041)
§11.70 — 기록에 영구적으로 연결된 서명충족 — 해시 연결 + RFC 3161 토큰신뢰가 적격성 평가해야 할 외부 TSA에 의존
§11.10(d)/(g) — 접근 통제, 고유 ID, MFA충족 — Keycloak RBAC + MFA역할 배정은 절차적임. 직무 분리는 여러분 몫
§11.200 — 매 서명 시 재인증부분 충족 — 맞춤 상향 인증 필요Keycloak 기본값 아님. 검증 대상 카테고리-5 맞춤 코드
슈퍼유저/권한 행위 감사격차pgAudit은 슈퍼유저를 안정적으로 감사 불가절차 + 제한된 계정 + 외부 변경 불가 로그
WORM(write-once-read-many)으로서의 기록 보존격차 — Postgres는 WORM이 아님객체 잠금(object-lock)을 갖춘 객체 저장소(SeaweedFS object-lock / 상용)
진실의 기록(record-of-truth)을 위한 고가용성격차 — 이 스택의 단일 노드 PostgresTimescaleDB HA는 TSL/상용 기능. 복제 설계 필요

표를 정직하게 읽으세요. 녹색 행은 오늘날 오픈소스로 진정으로 달성 가능하며, 동반 저장소가 이를 실행합니다. 황색과 적색 행은 오픈소스의 실패라기보다, 준수란 검증된 시스템과 그 절차의 속성이지, 결코 내려받은 도구의 속성이 아니라는 점을 일깨우는 것입니다 — 책 전체가 처음에 제시한 바로 그 틀입니다.

왜 중요한가

규제 당국은 여러분의 소프트웨어를 조사하지 않기 때문입니다. 그들은 여러분의 기록과 그것을 생성한 시스템을 조사합니다. 단일클론항체(monoclonal antibody, mAb) 한 배치가 출하되고 1년 뒤, 일탈(deviation) 조사에서 어떤 역가 결과가 수정되었는지, 누가, 왜 수정했는지를 알아야 한다면, 그 답은 "개발자가 데이터베이스는 괜찮다고 우리에게 보증했다"일 수 없습니다. 그것은 품질 검토자가, 시스템이 자동으로 쓴 감사 추적에 대해 실행한 쿼리여야 하고, 떼어내어 다시 붙일 수 없는 서명에 연결되어 있어야 합니다. 우리가 구축한 오픈소스 스택은 그 지점까지 놀라울 만큼 멀리 데려다줍니다 — 그리고 마지막 몇 걸음에 대해 정밀하게 구는 것이, 시연(demo)과 방어 가능한 시스템을 가르는 차이입니다.

실제 현장에서는

산업 현장에서 실제로 출하되는 패턴은 이 장이 모델로 삼은 하이브리드입니다. 실제 CHO + Protein A mAb 시설은 GxP 진실의 기록을 위해 검증된 상용 MES와 히스토리안(historian)을 운영하고, 그 곁에서 점점 더 많이 맥락화(contextualization), 분석, 엔지니어링을 위한 오픈소스 계층을 함께 운영합니다 — 정확히 OSS 계층이 더 빨리 진화하고 소유 비용이 더 낮기 때문입니다. 핵심 규율은 둘 사이의 선을 명시적으로 유지하는 것입니다. 검증된 시스템은 서명된 기록을 보유하고, OSS 계층은 읽고, 모델링하고, 시각화합니다. NIIMBL — 미국의 민관 합동 바이오 의약품 제조 혁신 연구소(Institute for Innovation in Biopharmaceutical Manufacturing) — 은 바로 이러한 데이터 통합과 디지털 성숙도 문제에 자금을 지원하며, 그곳의 SABRE 시설(델라웨어 대학교에 있는 파일럿 규모의 현행 우수 제조 관리 기준(current Good Manufacturing Practice, cGMP) 시설로, 2024년 4월에 착공)은 이러한 통제가 이론이 아니라 실제여야 하는 종류의 현장입니다. 주의해서 보세요. SABRE는 데이터 프로그램이 아니라 건설 중인 시설이며, 2026년 중반 기준으로 어떤 오픈소스 도구도 — eLabFTW도, Keycloak도, PostgreSQL도 — "Part 11 준수" 제품으로 출하되지 않습니다. 그들은 메커니즘을 출하하고, 준수는 여러분이 구축하고, 검증하고, 절차로 둘러쌉니다. 이 책이 교육용 시스템으로 다루는 OSS LIMS인 SENAITE는 경계의 사례입니다. 발표된 유일한 Part 11 격차 분석은 2019년 것이고, 전자서명, 보존, 비밀번호 통제에서 실제 격차를 나열합니다. 마케팅상의 성숙도는 검증된 성숙도가 아닙니다.

핵심 용어

  • 21 CFR Part 11 — 전자기록과 전자서명이 종이 및 자필 서명의 신뢰할 수 있는 등가물이 되는 기준을 정한 미국 FDA 규정.
  • EU Annex 11 — 전산화 시스템에 대한 유럽 GMP 지침. Part 11의 유럽 쪽 대응물로, 군데군데 더 엄격함(예: 모든 변경에 대한 문서화된 사유).
  • PIC/S PI 041 — 감사 추적의 검토(단순 보존이 아니라)를 명시적 기대로 만든 조사관용 데이터 무결성 지침.
  • 감사 추적(audit trail) — 누가 데이터를 생성·수정·삭제했고 왜 그랬는지를, 이전 값을 가리지 않으면서 기록하는, 안전하고 타임스탬프가 찍힌 컴퓨터 생성 기록.
  • pgAudit — 세션/객체 수준 감사를 위해 실행된 SQL 구문을 기록하는 PostgreSQL 확장.
  • 해시 체인(hash chain) — 각 감사 항목을 직전 항목의 암호 해시에 연결하여, 이후의 어떤 변경도 탐지 가능하게 만드는 것.
  • RFC 3161 / TSA — 신뢰 타임스탬프 프로토콜과, 서명된 존재 증명 토큰을 발급하는 시간 인증 기관(Time-Stamp Authority).
  • 재인증(re-authentication, §11.200) — 한 세션에서 최초 서명 이후의 모든 서명이 적어도 한 개의 신원 요소를 다시 실행해야 한다는 요건.
  • WORM — 확정된 기록의 변경을 물리적으로 막는, 한 번 쓰면 여러 번 읽는(write-once-read-many) 저장소.

다음 이야기

이제 우리는 규제 당국이 인정할 만한 기록을 가지고 있습니다 — 자동으로 감사되고, 귀속 가능하게 변경되며, 암호 서명된 — 그리고 오픈소스가 일을 마무리하려면 어디에서 절차나 상용 부품이 필요한지에 대한 정직한 지도도 가지고 있습니다. 그러나 방어 가능한 시스템은 그 통제 이상의 것입니다. 누군가는 의존할 벤더 품질 시스템 없이, 스택 전체가 설치되고, 설정되고, 의도한 대로 동작함을 증명해야 합니다. 그것이 검증(validation)입니다. 다음 장 오픈소스 스택 검증하기: GAMP 5와 CSA는, 이미 보았던 이 같은 산출물들을 IQ/OQ/PQ 증거로 바꿉니다 — 여러분이 이미 본 테스트 스위트를, 플랫폼이 우리가 말한 대로 작동함을 보이는 조사 대비(inspection-ready) 증거로서 실행하면서 말입니다.