앞 장에서는 프로토콜 레코드를 메모리 안의 타입 구조 그래프로 설명하였다. 그러나 메모리에만 존재하는 레코드는 기관생물안전위원회의 심의 대상이 될 수 없고, 협력 기관에 보낼 수도 없으며, 2년 뒤 감사 요청에 대응해 제출할 수도 없다. 레코드는 서로 다른 두 프로그램이 동일하게 해석하는 형식으로 기록되어야 하고, 이후 모든 개정을 견디는 이름을 가져야 하며, 누군가 그것에 근거해 행동하기 전에 스키마로 검증되어야 한다. 이 장의 주제가 바로 그 세 가지다. 즉 전송 형식, 식별자, 그리고 제출된 문서와 레코드 저장소 사이에 서 있는 검증 게이트이다.
이 책에서 직렬화와 식별자는 이 장이 전담한다. 호출 시그니처는 4장, 생애주기 상태와 시각은 5장, 전송과 배치는 6장, 증거 패키징은 7장이 담당한다. 이 장이 그 주제들을 언급하는 경우에도, 디스크에 놓인 문서 자체의 성질에 해당하는 부분만 다룬다.
WIA-BIO-014 §8.3.1은 프로토콜을 하나의 JSON 객체로 제시한다. 이 레코드 부류에 JSON이 적합한 이유는 화려하지 않다. 짧고 안정된 문서(RFC 8259)로 규정되어 있고, 연구기관 소프트웨어 환경의 거의 모든 언어가 파싱할 수 있으며, 티켓에 붙여넣어도 형태가 유지되고, 무엇도 설치하지 않은 준법 담당자가 그대로 읽고 서명할 수 있다. 대가는 JSON 자체에 의미론이 거의 없다는 점이다. JSON은 필수 필드, 열거 값, 단위, 순서 규칙 중 어느 것도 표현하지 못한다. 따라서 문서를 신뢰할 수 있게 만드는 모든 요소는 문서 밖, 즉 이 장 후반에서 설명하는 스키마와 검증기에 존재한다.
정본 문서는 애그리게이트 루트의 식별 필드와, 자식 구조마다 하나씩 대응하는 멤버로 구성된 단일 객체이다. 표준의
예시는 그 멤버를 protocol_id, version, target, guide_rna,
cas_system, delivery, validation으로 표기한다. 반면 참조 SDK는 동일한
레코드를 카멜케이스 TypeScript 속성 id, version, target,
guideRNA, casSystem, delivery, strategy,
validation, safety, parameters로 기술한다. 두 어휘는 동일하지 않으며, 그
차이는 대소문자 표기에 그치지 않는다. JSON 예시에는 strategy와 safety 멤버가 아예 없고,
식별 필드 이름도 SDK의 id와 달리 protocol_id이다.
이 대응 관계를 암묵적으로 추측하는 구현은, 하나의 코드베이스 안에서는 왕복이 성립하지만 경계를 넘을 때마다 필드를 잃는 레코드를 만들어 낸다. 따라서 이 책이 채택하는 규칙은 명시적이다. 적합한 구현은 필드 매핑 표를 문서의 일부로 공개하며, 그 매핑은 손으로 관리하지 않고 스키마로부터 생성하거나 스키마에 대한 시험으로 검증한다. 아래 표는 이 책이 사용하는 매핑이다. 레코드를 확장하는 기관은 같은 커밋에서 이 표도 확장한다.
| JSON 멤버 | SDK 속성 | 문서상 타입 | 필수 | 비고 |
|---|---|---|---|---|
protocol_id | id | string | 예 | 버전이 바뀌어도 불변. §3.2 참조 |
version | version | string | 예 | 레코드 버전이며 표준 버전이 아니다 |
created | created | string (date-time) | 예 | SDK 타입은 Date. 직렬화는 §3.7 |
updated | updated | string (date-time) | 예 | 저장소가 기록하며 클라이언트가 쓰지 않는다 |
target | target | object | 예 | TargetInfo |
target.gene | target.gene | string | 예 | 제출자가 기재한 심볼 |
target.gene_id | target.geneId | string | 아니오 | 네임스페이스를 명시한 외부 식별자 |
target.coordinates | target.coordinates | object | 예 | GenomicCoordinates. 표준 예시는 평탄화되어 있다 |
target.coordinates.assembly | …assembly | string | 예 | 표준 예시는 이를 genome으로 표기한다 |
target.region_type | target.regionType | enum string | 예 | 통제 어휘. §3.3 |
target.purpose | target.purpose | enum string | 예 | 통제 어휘. §3.3 |
guide_rna | guideRNA | object | 예 | 레코드 필드로만 취급한다. 설계 지침은 이 책의 범위가 아니다 |
guide_rna.on_target_score | …onTargetScore | number | 예 | 산출 도구·버전의 출처 기록이 필요하다 |
cas_system | casSystem | object | 예 | type·variant·source가 열거형이다 |
delivery | delivery | object | 예 | 타입이 정해진 외피와 개방형 parameters |
delivery.cell_type | delivery.cellType | string | 예 | 기관이 어휘를 결속하지 않는 한 자유 문자열 |
strategy | strategy | object | 예(SDK) | 표준의 JSON 예시에는 없다 |
validation | validation | array of object | 예 | 표준 예시는 객체, SDK는 배열이다 |
safety | safety | object | 예(SDK) | 표준의 JSON 예시에는 없다 |
parameters | parameters? | object | 아니오 | 개방형 맵. 단위 규칙은 §2.4 참조 |
굵게 표시한 세 행이 이 표준을 도입할 때의 실질적인 작업이다. 공개된 예시는 SDK 타입의 부분집합이 아니라, 더 오래되고
느슨한 형태이다. 조정 방법은 두 가지이며, 기관은 그중 하나를 선택해 문서로 남겨야 한다. 첫째는 SDK 타입을 정본으로
보고 예시를 예시로만 취급하는 방법이며, 이 책이 취하는 입장이다. 즉 strategy와 safety는
필수 멤버이고, validation은 배열이므로 하나의 프로토콜이 여러 개의 검증 기록을 가질 수 있다. 둘째는
수신 시점에 표준 예시 형태를 받아들여 정규화하는 방법이다. 이때 정규화기 자체가 적합성과 관련된 소프트웨어가 되며,
서로 어긋나는 세 멤버에 대한 그 동작은 시험 벡터로 덮여야 한다(7장).
아래 주석이 달린 예시는 이 책이 사용하는 형태로 작성되었다. 공개 자료가 그럴듯한 생물학적 값이나 장비 값으로 채워 두었던 모든 항목은 꺾쇠괄호로 감싼 명백한 자리표시자로 대체하였다. 이는 의도된 것이며 이 절의 두 번째 규칙이다. 예시 문서는 권고 설정으로 오해될 수 있어서는 안 된다. 예시를 그대로 복사한 독자는 자신의 값을 채워 넣기 전까지 검증에 실패하는 문서를 얻게 되며, 그것이 올바른 결과이다.
{
"protocol_id": "<INST>-CRISPR-<YYYY>-<NNNN>",
"version": "3",
"created": "2026-01-15T09:20:00+09:00",
"updated": "2026-02-02T14:05:11+09:00",
"target": {
"gene": "<GENE_SYMBOL>",
"gene_id": "ensembl:<ENSG_ID>",
"coordinates": {
"chromosome": "<CHR>",
"start": 0,
"end": 0,
"strand": "+",
"assembly": "<ASSEMBLY_LABEL>"
},
"region_type": "exon",
"purpose": "knockout",
"description": "<한 줄 설명. 개인 식별 정보를 넣지 않는다>"
},
"guide_rna": {
"sequence": "<RECORDED_BY_SUBMITTER>",
"pam": "<PAM_LABEL>",
"on_target_score": null,
"specificity_score": null,
"provenance": {
"tool": "<도구 이름>",
"tool_version": "<semver 또는 빌드 식별자>",
"computed": "2026-01-15T09:18:42+09:00"
}
},
"cas_system": {
"type": "Custom",
"variant": "Custom",
"source": "recombinant"
},
"delivery": {
"method": "other",
"cell_type": "<세포주 또는 일차세포 라벨>",
"parameters": {
"<param_name>": { "value": null, "unit": "<단위>", "source": "<SOP 식별자 또는 장비 로그>" }
}
},
"strategy": {
"type": "NHEJ",
"expected_outcome": "<제출자가 기재한 자유 서술>",
"timeline": "<자유 서술>"
},
"validation": [
{
"method": "NGS",
"description": "<자유 서술>",
"timing": null,
"parameters": { "<param_name>": { "value": null, "unit": "<단위>" } }
}
],
"safety": {
"off_target_analysis": false,
"ethics_approval": false,
"approval_refs": [],
"biosafety": "BSL2",
"regulatory": [],
"risk_level": "low",
"mitigation": []
},
"schema": {
"standard": "WIA-BIO-014",
"standard_version": "1.0",
"profile": "<기관 프로파일 이름>@<프로파일 버전>"
}
}
이 문서에서 특히 짚어 둘 세 가지가 있다. 측정되지 않은 수치 필드는 0이 아니라 null이다.
점수 필드의 0은 하나의 주장이며, 그것도 거짓인 주장이다. guide_rna.provenance 객체는 공개된 타입이
가지고 있는 것이 아니라 이 책이 권고하는 추가 항목이다. 프로그램이 계산한 값을 저장할 때에는 그 값을 산출한 도구와
버전이 함께 있어야 하며, 그렇지 않으면 그 값은 재현할 수도 철회할 수도 없다. 그리고 schema 멤버는 이
문서가 어떤 표준 버전과 어떤 기관 프로파일에 맞추어 작성되었는지를 기록한다. 5년 뒤의 독자가 현재 스키마가 아니라
그때의 올바른 스키마로 문서를 검증할 수 있게 하는 것이 이 멤버의 역할이다.
식별자는 레코드에서 다른 시스템이 복사해 가는 부분이다. 시료 관리 목록, 시퀀싱 작업 티켓, 위원회 회의록, 구매 요청, 논문 보충자료가 모두 프로토콜 식별자를 문자열로 보관하게 되며, 그중 어느 것도 나중에 값을 갱신해 주지 않는다. 따라서 식별자에는 어떤 미적 선호보다 우선하는 하나의 강한 요구가 있다. 식별자는 변하지 않으며, 재사용되지 않는다.
공개 자료는 대체로 CRISPR-<연도>-<일련번호> 형태의 식별자를 보여 준다. 이 형태는 한 기관
내부에서는 쓸 만하지만, 두 기관이 레코드를 교환하는 순간 무너진다. 양쪽 모두 CRISPR-2026-001을 발급하기
때문이다. 이 책이 권고하는 프로파일은 기관을 접두로 붙이고, 레코드의 동일성과 레코드의 버전을 분리한다.
protocol-id = institution "-" domain "-" year "-" serial
institution = 2*16 ( ALPHA / DIGIT ) ; 등록된 기관 접두어
domain = "CRISPR" ; WIA-BIO-014 레코드에서 고정
year = 4DIGIT ; 레코드가 최초 생성된 연도
serial = 3*8DIGIT ; 0 패딩, 기관·연도별 단조 증가
version = 1*DIGIT ; 별도 필드. protocol-id 에 포함하지 않는다
reference = protocol-id [ "@" version ] ; 특정 버전을 인용하는 표기
위 문법은 WIA-BIO-014의 규범 요구가 아니라 예시 프로파일이다. 표준은 protocol_id를 문자열로만 제약한다.
중요한 것은 기관이 하나의 문법을 확정하고, 스키마의 패턴으로 검증하며, 그 문법을 프로파일 매니페스트에 기록해 두어
협력 기관이 "형식이 잘못된 식별자"와 "낯선 식별자"를 구별할 수 있게 하는 것이다.
고려할 만한 대안은 불투명 식별자, 즉 UUID(RFC 9562)나 그에 준하는 무작위 토큰을 주 식별자로 삼고 사람이 읽는 라벨은 별도의 표시 필드에 두는 방식이다. 불투명 식별자는 기관 간 충돌이 없고, 추측이 불가능하며, 나중에 틀린 것으로 판명될 정보를 이름 안에 은근히 담지 않는다. 대가는 소리 내어 읽을 수 없다는 점과, 잘못 입력했을 때 눈에 보이게 틀린 문자열이 아니라 단순한 조회 실패가 된다는 점이다. 연구행정 시스템이 대체로 도달하는 절충안은, 저장소 내부의 기본 키는 불투명 식별자로 두고 사람이 사용하는 표시 식별자는 스키마가 제약하는 구조화된 유일값으로 두는 것이다.
레코드 버전은 동일성의 나머지 절반이며, 대부분의 구현이 틀리는 지점이다. 흔한 유혹은 프로토콜 레코드를 편집 가능한
행으로 취급하는 것이다. 검토자가 어셈블리 라벨의 오류를 발견하고, 누군가 그 필드를 고치고, updated가
갱신되면, 그 레코드는 위원회가 승인한 것과 다른 내용을 서술하게 된다. 이 책이 명확히 정하는 규칙은 다음과 같다.
수정된 레코드는 새 버전이며, 이전 버전은 영구히 읽을 수 있는 상태로 남는다.
| 상황 | protocol_id | version | 결과 |
|---|---|---|---|
| 초안 상태에서 자유 서술의 오타를 고침 | 유지 | 유지 | 초안 편집. 감사 이벤트에 필드 변경을 기록 |
| 초안을 벗어난 뒤의 모든 변경 | 유지 | 증가 | 새 버전. 이전 버전은 보존되고 인용 가능 |
| 승인 이후 잘못된 값이 발견됨 | 유지 | 증가 | 정정 버전. 사유와 승인 참조를 함께 기록 |
| 표적 또는 목적 자체가 바뀜 | 신규 | 1에서 시작 | 새 프로토콜. 이전 레코드를 가리키는 supersedes 포인터 |
| 착오로 생성되어 실행되지 않음 | 유지 | 증가 | 철회 버전. 레코드를 삭제하지 않는다 |
| 두 기관의 레지스트리를 통합 | 유지 | 유지 | 접두어로 양쪽이 모두 읽힌다. 재번호 부여 없음 |
버전 필드는 레코드의 계수기이며, 표준이나 SDK의 버전과는 아무 관계가 없다. 둘을 혼동하는 것은 흔하고 비용이 큰
실수이다. 표준 예시처럼 모든 레코드에 "version": "1.0"을 써 넣는 저장소는 특정 개정을 인용하는 능력을
버린 것이며, 이후의 모든 인용이 모호해진다. 레코드 버전은 단순 정수 계수기로 두고(위 예시처럼 숫자 문자열로 직렬화한다), 표준 버전은 schema 멤버에
두며, 표준 자체의 시맨틱 버저닝은 8장에서 다룬다.
이 레코드들을 비교 가능하게 만드는 요소의 상당 부분은, 특정 필드가 정해진 목록의 값만 가질 수 있다는 점에서 나온다. SDK는 그 목록을 TypeScript 유니온 타입으로 표현하는데, 이는 한 언어 안에서 컴파일러가 강제한다는 뜻이고 그 밖에서는 아무것도 강제하지 않는다는 뜻이다. 문서 경계에서 강제하려면 그 목록을 스키마의 열거형으로 다시 적어야 하며, 보고에 쓰려면 자체 변경 절차를 가진 레지스트리로 다루어야 한다. 이는 메타데이터 레지스트리에 대해 ISO/IEC 11179가 형식화한 개념이다.
이 표준의 모든 어휘에는 두 규칙이 적용된다. 첫째, 열거 값은 한국어판 레코드와 영어판 레코드 모두에서 영어로
저장한다. 저장되는 토큰은 표시 문구가 아니라 식별자이다. 문서 안에서 그것을 번역하면 모든 질의, 모든 조인,
모든 내보내기 보고서가 깨진다. 지역화는 토큰을 키로 하는 라벨 표를 이용해 사용자 화면에서 수행한다. 둘째, 예외 값은
면죄부가 아니다. 이 표준의 여러 어휘는 Custom, other, Other 같은 예외 값을
제공하는데, 이를 사용하는 레코드는 인접한 자유 서술 필드에 의도한 값을 반드시 남겨야 한다. 설명 없는 예외 값은 1년
뒤 누구도 대답할 수 없는 질문이 된다.
| 필드 | 어휘(SDK 타입) | 예외 값 | 보고 용도 |
|---|---|---|---|
cas_system.type | CasSystemType | Custom | 계열별 레코드 묶음 |
cas_system.variant | variant 유니온 | Custom | 동일 계열 내 구분 |
cas_system.source | source 유니온 | 없음 | 구매·재고 시스템과의 조인 |
guide_rna.pam | PAMType | None | 라벨로만 사용. 이 책에서 설계적 의미는 없다 |
strategy.type | EditType | 없음 | 목적별 포트폴리오 보고 |
target.region_type | region 유니온 | other | 검색 패싯 |
target.purpose | purpose 유니온 | 없음 | 승인 경로 배정(5장) |
delivery.method | method 유니온 | other | 라벨로만 사용. 파라미터 권고는 없다 |
validation[].method | method 유니온 | Other | 증거 완결성 점검 |
safety.biosafety | BSL1|BSL2|BSL3|BSL4 | 없음 | 보고용 저장 라벨 |
safety.risk_level | low|medium|high | 없음 | 심의 대기열 우선순위 |
| organism(요청 수준) | Organism | custom | 참조 데이터 선택 |
safety.biosafety 행은 이 책의 범위 선언이 강조하는 바를 그대로 보여 준다. 레코드에서 이 필드는 네 개의
값 중 하나를 담는 문자열일 뿐이다. 즉 기관이 그 연구에 어떤 밀폐 등급 라벨을 부여했는지를 적은 라벨이다. 시설 규격이
아니며, 어떤 절차도 함의하지 않으며, 검증기가 그로부터 절차를 추론해서도 안 된다. 소프트웨어가 이 라벨로 정당하게 할
수 있는 일은 레코드를 적절한 검토자에게 배정하고, 보고서를 걸러 내고, 라벨과 승인 참조가 어긋날 때 상태 전이를
거부하는 것이다.
어휘에 값을 추가하는 것은 스키마 변경이며, 그중 가산적 변경에 해당한다. 기존 문서는 계속 유효하고, 기존 판독기는 모르는 토큰을 만나게 된다. 이것이 감당 가능한 것은 판독기가 모르는 열거 값을 "인식 불가, 보존하고 표시"로 다루도록 작성된 경우에 한한다. 값의 삭제나 개명은 파괴적 변경이며 §3.6에서 다룬다.
"유효하다"는 하나의 질문이 아니다. 어떤 문서는 완벽한 형식의 JSON이면서 승인 참조가 없을 수 있고, 모든 필수 필드를
갖추고도 스스로 표기한 어셈블리와 다른 유전체의 좌표를 담고 있을 수 있다. 검증을 계층으로 나누는 것은 구현이 무엇을
점검했는지 정확히 말할 수 있게 하는 장치이다. 그것이 증거로 쓸 수 있는 검증기와 그저 true를 돌려주는
검증기의 차이다.
| 계층 | 판정하는 질문 | 수단 | 오류 코드(SDK CRISPRErrorCode) |
|---|---|---|---|
| 1 — 구조 | 필수 멤버, 타입, 허용된 열거 값을 갖춘 정상 형식의 WIA-BIO-014 문서인가? | JSON Schema(2020-12) 또는 OpenAPI 3.0 스키마 객체. 참조 데이터 없이 동작한다 | C004 INVALID_SEQUENCE, C007 INVALID_PARAMETERS |
| 2 — 참조 | 값이 실제로 해소되는가? 어셈블리 라벨이 존재하고 좌표가 그 안에 들며, 인용된 식별자가 해당 시스템에 있는가? | 참조 데이터 및 인접 레지스트리 조회. 네트워크 또는 로컬 미러가 필요하다 | C008 GENOME_NOT_FOUND, C009 OFF_TARGET_ANALYSIS_FAILED |
| 3 — 정책 | 기관이 진행을 허용하는가? 유효기간 내 승인이 참조되어 있고, 필요한 확인이 있으며, 이 제출자에게 그 전이가 허용되는가? | 기관 규칙과 생애주기 상태(5장). 설정 가능하고 버전이 있으며 감사 가능해야 한다 | C006 ETHICS_VIOLATION |
| (레코드 검증 계층이 아님) | 레코드 검증이 아니라 점수 산출·설계 기능에 속하는 코드(참조 구현은 실제로 이 중 어느 것도 발생시키지 않는다. 4장 §4.5) | 전역 제외 규칙에 따라 이 책의 범위 밖. 코드 이름만 기록한다 | C001, C002, C003, C005 |
이 표에 대해 두 가지를 솔직히 밝혀 둔다. 표준 §8.4의 오류 표는 여섯 개 코드를 나열하지만 SDK의 열거형은 열 개를
정의하며, 추가된 네 개가 실제로 레코드 검증에 필요한 코드들이다. 이 책은 SDK의 집합을 사용하고, 코드별 호출자 대응은
4장이 전담한다. 반면 코드를 계층에 배정한 것은 이 책의 프로파일이다. 공개 자료는 코드를 분류하지 않으므로, 이 배정에
동의하지 않는 기관은 호출자가 추측하게 두지 말고 자체 매핑을 공개해야 한다. C010 VALIDATION_FAILED는 일부러 어느 계층에도 넣지 않았다. 이 책은 이
코드를 "검증기가 실행되지 못했다"는 뜻으로만 쓰며, 이는 어느 계층에서 일어나든 레코드에 관한 판정이 아니라 운영상의
장애다(4장 §4.5).
이 계층화에는 말하기는 쉽고 어기기도 쉬운 운영상의 귀결이 하나 있다. 검증 결과는 어떤 계층이 실행되었는지를 기록해야 한다. 구조 검사만 수행한 결과를 "검증됨"으로 보고하는 것은 적합성 주장이 거짓이 되는 가장 흔한 경로다. 위원회가 관심을 두는 정책 계층이 동시에 비용이 큰 계층이며, 바쁜 파이프라인이 가장 먼저 끄는 것도 그것이기 때문이다. 실행한 계층을 명시하는 결과는 그 누락을 보이지 않는 것에서 보이는 것으로 바꾼다.
따라서 오류 페이로드는 실패 위치, 그것을 찾아낸 계층, 호출자가 할 수 있는 조치를 함께 담아야 한다. 아래 형태는 RFC 9457 문제 상세 규약을 따르며, 문제가 된 멤버의 위치는 JSON 포인터(RFC 6901)로 지시한다.
{
"type": "https://wiastandards.example/errors/wia-bio-014/validation",
"title": "Protocol record failed validation",
"status": 422,
"protocol_id": "<INST>-CRISPR-2026-0007",
"record_version": "3",
"validator": { "name": "<검증기 이름>", "version": "<semver>", "policy_config": "<프로파일>@<버전>" },
"layers_run": ["structural", "referential", "policy"],
"is_valid": false,
"errors": [
{
"code": "C007",
"layer": "structural",
"pointer": "/validation/0/timing",
"detail": "필수 멤버가 null 이다. 값을 채우거나 해당 검증 항목을 제거할 것.",
"caller_action": "fix_document"
},
{
"code": "C006",
"layer": "policy",
"pointer": "/safety/approval_refs",
"detail": "in_execution 전이에 필요한 유효기간 내 승인 참조가 없다.",
"caller_action": "obtain_approval"
}
],
"warnings": [
{
"layer": "referential",
"pointer": "/target/coordinates/assembly",
"detail": "<날짜> 기준 로컬 미러에서 어셈블리 라벨을 해소하였다. 최신성 확인 필요."
}
],
"validated": "2026-02-02T14:05:11+09:00"
}
이 페이로드를 단순한 로그 한 줄이 아니라 증거로 만드는 부분은 validator 객체이다. 어떤 검증기가 어떤
버전과 어떤 정책 설정으로 산출했는지 말하지 못하는 검증 결과는 재현할 수 없고, 재현 불가능한 통과는 "이 레코드를 왜
받아들였는가"라는 질문 앞에서 거의 가치가 없다. 7장은 이 페이로드에 서명한 형태를 증거 패키지의 산출물로 다룬다.
참조 SDK는 CRISPR_CONSTANTS라는 동결 객체를 내보낸다. 그 멤버에는 OPTIMAL_GC_MIN,
OPTIMAL_GC_MAX, MAX_POLY_T, MIN_ON_TARGET_SCORE,
MIN_SPECIFICITY_SCORE, MAX_OFF_TARGETS, BE_WINDOW_START,
BE_WINDOW_END, PE_PBS_LENGTH, 그리고 이름이 TYPICAL_로 시작하는 네 개가
있다. 검증기는 제출된 값을 그중 몇몇과 비교해 경고나 오류를 낸다.
이 상수들에 대해 이 책은 다음을 분명히, 그리고 한 번만 밝힌다. 이 점을 오해한 독자는 표준을 잘못 쓰게 되기 때문이다. 그 값들은 출처가 없는 소프트웨어 기본값이다. 표준의 과학 문헌 참조 절은 비어 있으므로, 표준 자체의 문서 안에는 어떤 수치에 대한 근거도 존재하지 않는다. 그것은 과학적 권고가 아니고, 설계 임계값이 아니고, 합격 기준도 아니며, 그 객체의 어떤 값도 실험을 어떻게 계획해야 한다는 지침으로 읽혀서는 안 된다. 그것의 정체는 한 참조 구현의 경고 동작 설정값이며, 설정값은 버전 관리되는 설정 파일에, 기관이 소유하는 형태로 있어야 한다.
실무 지침은 외부화하는 것이다. 이름과 버전이 붙은 정책 설정에서 임계값을 읽는 검증기는 (가) 설정이 이력을 가진 파일이므로 감사할 수 있고, (나) 검증 결과가 설정 버전을 인용하므로 재현할 수 있으며, (다) 코드 배포 없이 정정할 수 있다. 숫자를 코드에 박아 둔 검증기는 그 어느 것도 하지 못하며, 더 나쁘게는 자신의 기본값을 표준의 요구사항처럼 보이게 만든다.
{
"policy_config": "<기관>-crispr-record-policy",
"policy_version": "2026.1",
"owner": "<역할. 예: 연구데이터 거버넌스>",
"basis": "기관 결정 사항. 표준은 이 키들에 대한 출처 있는 값을 제공하지 않는다.",
"thresholds": {
"OPTIMAL_GC_MIN": { "value": null, "action": "warn", "set_by": "<위원회 또는 역할>" },
"OPTIMAL_GC_MAX": { "value": null, "action": "warn", "set_by": "<위원회 또는 역할>" },
"MAX_POLY_T": { "value": null, "action": "warn", "set_by": "<위원회 또는 역할>" },
"MIN_ON_TARGET_SCORE": { "value": null, "action": "warn", "set_by": "<위원회 또는 역할>" },
"MIN_SPECIFICITY_SCORE": { "value": null, "action": "warn", "set_by": "<위원회 또는 역할>" },
"MAX_OFF_TARGETS": { "value": null, "action": "warn", "set_by": "<위원회 또는 역할>" }
},
"record_completeness": {
"require_approval_ref_before_execution": true,
"require_provenance_for_computed_scores": true,
"require_denominator_with_percentage": true
}
}
이 파일에서 무엇이 null이고 무엇이 아닌지에 주목할 필요가 있다. 수치 임계값은 비워 두었다. 이 책에는 거기에
넣을 출처 있는 값이 없고, 없는 값을 지어내지 않기 때문이다. 기관이 채우거나 그 키를 제거한다. 반대로 세 개의 완결성
규칙은 true로 두었다. 그것들은 생물학에 대한 주장이 아니라 데이터 모델에서 따라 나오는 기록 요구사항이기
때문이다. 이 장이 구현자에게 지켜 달라고 요청하는 선이 바로 여기다. 소프트웨어는 기관이 자기 레코드에 대해 결정한 것은
무엇이든 강제할 수 있고, 기관의 기본값을 과학적 사실로 꾸며서는 안 된다.
여기서 한 가지가 더 따라 나온다. 이 임계값들은 구조적 실패가 아니라 경고를 발생시키므로, 그것만으로 생애주기 전이를 막아서는 안 된다. 전이를 막는 경고는 예의 없는 오류일 뿐이고, 출처 없는 기본값에 근거한 오류는 예정된 감사 지적이다.
한 프로젝트보다 오래 사는 레코드 형식은 반드시 변한다. 유용한 구분은 기존 판독기가 견디는 변경과 그것을 깨뜨리는 변경 사이에 있으며, 필요한 규율은 병합하기 전에 자신이 어느 쪽을 하고 있는지 아는 것이다.
| 변경 | 분류 | 기존 레코드에 대한 영향 | 필요한 조치 |
|---|---|---|---|
| 선택적 멤버 추가 | 가산적 | 계속 유효 | 스키마 마이너 버전. 변경 이력에 기재 |
| 어휘에 값 추가 | 가산적 | 계속 유효 | 마이너 버전. 판독기는 미지 토큰을 견뎌야 한다 |
| 제약 완화(범위 확대, 패턴 삭제) | 가산적 | 계속 유효 | 마이너 버전 |
| 필수 멤버 추가 | 파괴적 | 기존 모든 레코드가 무효가 된다 | 메이저 버전, 또는 기본값을 명시한 선택적 멤버로 추가 |
| 멤버 개명 | 파괴적 | 판독기가 조용히 필드를 잃는다 | 메이저 버전. 제자리 개명은 금지 |
| 멤버 삭제 | 파괴적 | 재직렬화 시 데이터 손실 | 먼저 폐기 예고, 다음 메이저 버전에서 제거 |
| 멤버 타입 변경 | 파괴적 | 파싱 또는 형 변환 실패 | 메이저 버전. 새 멤버 이름이 대개 더 안전하다 |
| 어휘 값 삭제·개명 | 파괴적 | 기존 레코드가 허용되지 않는 토큰을 갖게 된다 | 메이저 버전 및 매핑 문서화 |
| 제약 강화 | 사실상 파괴적 | 일부 기존 레코드가 무효가 된다 | 파괴적 변경으로 취급. 먼저 기존 레코드를 전수 조사 |
표의 대부분을 덮는 규칙은 짧다. 개명이나 삭제는 버전 상승을 뜻하며, 한 번 쓴 멤버 이름을 다른 의미로 재사용하지 않는다. 후자를 엄격히 지켜야 하는 이유는, 이름을 재사용하면 검증은 통과하면서 내용은 거짓인 레코드가 생기기 때문이다. 그것은 검증에 실패하는 레코드보다 더 나쁘다. 원 자료에 관해 한 가지 짚어 둔다. PHASE 문서의 Annex E 스키마 진화 주석은 필드의 개명이나 제거가 "마이너 버전 상승을 유발해야 한다(MUST)"고 적고 있다. 이 책의 프로파일은 Annex H가 정의한 메이저 버전의 의미에 따라 이런 변경을 파괴적 변경, 곧 메이저로 분류한다. 기관은 두 해석 중 어느 쪽을 적용하는지 기록해 두어야 한다.
짝을 이루는 규칙은 스키마가 아니라 판독기에 관한 것이다. 검증기나 저장소가 모르는 멤버를 만났을 때 선택지는 세 가지다.
문서를 거부하거나, 그 멤버를 버리거나, 보존하는 것이다. 기관 간에 교환되는 레코드에서 허용되는 것은 세 번째뿐이다.
미지 멤버를 버리는 것은 조용한 데이터 손실이며, 그 손실은 최악의 시점에 드러난다. 즉 그 필드를 채워 보낸 상대 기관이
돌려받은 사본에서 왜 그 필드가 사라졌는지 물을 때이다. 구현은 미지 멤버를 보존 영역에 유지하고, 가능하면 바이트 단위로
왕복시키며, 그 존재를 오류가 아니라 경고로 보고해야 한다. 기관이 자체 필드를 추가해야 할 때에는 x_ 같은
예약 접두 규약이나 extensions 중첩 객체 하나를 쓰면 지역 추가가 장래의 표준 멤버와 충돌하지 않는다.
버전 간 이행 자체도 레코드를 생성하는 활동이다. 새 스키마 버전으로 다시 쓴 레코드는 이행 도구와 그 버전을 인용한 새 레코드 버전이며, 이행 전 문서는 보존된다. 이행이 남기는 감사 이벤트는 5장이, 표준 자체의 버저닝과 폐기 절차는 8장이 다룬다.
이 절의 실패들은 특이한 사례가 아니다. 몇 해에 걸쳐 레코드 집합을 조용히 훼손하는 흔한 실패이며, 하나같이 이와 같은 형태의 연구데이터 시스템에서 실제로 관찰된 것들이다.
분모 없이 저장된 백분율. 레코드 모델은 판독 수와 그로부터 유도한 백분율을 함께 담는다. JSON 수치는 통상 IEEE 754 binary64 배정도로 파싱되므로 백분율은 비율의 근사값이고, 근사값은 다시 검산할 수 없다. 더 나쁜 것은 분모 없는 백분율이 자신의 신뢰도를 숨긴다는 점이다. 같은 값이 매우 적은 판독에서 나왔을 수도 있고 매우 많은 판독에서 나왔을 수도 있으며, 어떤 독자도 그 차이를 알 수 없다. 2장에서 밝히고 여기서 완결성 점검으로 강제하는 규칙은, 계산된 백분율은 그것을 만든 분자와 분모 없이 저장하지 않으며, 백분율은 측정의 기록이 아니라 표시의 편의로 취급한다는 것이다.
오프셋 없이 직렬화된 Date. SDK 타입은 created, updated,
ValidationResult.validated를 Date로 선언한다. 전송 형식에서는 UTC 오프셋을 명시한 RFC 3339
date-time 문자열이어야 한다. 지역 벽시계 문자열로 기록한 시각은 두 번째 사이트가 그 레코드를 건드리는 순간 정렬이
불가능해지고, 에폭 숫자로 기록한 시각은 사람 검토자가 읽을 수 없다. 오프셋을 저장하고, 실제로 가진 정밀도만 기록하며,
updated에 대해서는 클라이언트가 보낸 값을 결코 받아들이지 않는다. 그 필드를 저장소가 소유해야 하는
이유는 5장에서 설명한다.
정규화되지 않은 비ASCII 텍스트. 설명, 세포 유형 라벨, 완화 조치 메모 같은 자유 서술 멤버에는 한국어, 분음부호가 붙은 라틴 문자, 때로는 유전자·단백질 명명에 쓰이는 그리스 문자가 들어온다. 문서는 UTF-8(RFC 3629)이어야 하며, 비교나 검색에 쓰이는 텍스트는 관례적으로 Unicode Annex #15가 기술하는 NFC로 정규화해야 한다. 정규화하지 않으면 겉보기에 같은 두 라벨이 다른 값으로 비교되어 중복 제거가 실패하고, 레코드를 검색해도 아무것도 나오지 않는다. 이 지점은 2장의 개인정보 규칙을 강제할 자리이기도 하다. 개인 식별 정보가 나타나는 곳은 바로 이 자유 서술 멤버이며, 스키마는 그것을 검출하지 못한다. 제출 시점의 점검과 검토자의 주의가 유일하게 작동하는 통제이다.
손실을 동반한 재내보내기. 언젠가 누군가는 보고서를 만들기 위해 레코드 집합을 스프레드시트로 내보낸다. CSV(RFC 4180)에는 중첩이 없고, 타입이 없고, 빈 값과 구별되는 null이 없으며, 여러 개의 검증 기록 배열을 표현할 방법도 임의의 관례를 만드는 것 말고는 없다. 내보내기는 한 방향의 투영이며 그렇게 표시되어야 한다. 즉 파생물이고, 날짜가 찍히며, 레코드가 아니다. 경계해야 할 실패 양상은 왕복이다. 내보낸 시트를 손으로 고쳐 다시 들여오면, 숫자는 스프레드시트가 재서식화하고, 식별자의 선행 0은 잘려 나가며, 날짜는 내보낸 사람의 로케일로 조용히 다시 쓰인다. 수작업 편집이 실제 요구사항이라면, 수신 경로는 내보내기의 재수입이 아니라 검증기가 받아들이는 생성된 템플릿이어야 한다.
정규화하지 않은 JSON에 대한 다이제스트. 이후의 장들은 문서를 해시한다. 4장의 증거 봉투, 5장의 감사 이벤트 체인, 7장의 증거 패키지는 모두 SHA-256 다이제스트로 내용을 묶는다. 같은 레코드의 두 직렬화는 키 순서, 공백, 숫자 표기에서 달라질 수 있으므로, 어느 한쪽에 대한 다이제스트가 일치하는 것은 우연일 뿐이다. JSON 문서에 대해 다이제스트를 계산할 때에는 먼저 JSON 정규화 방식(JCS, RFC 8785)으로 정규화하고, 정규화 방법을 다이제스트 옆에 기록한다. 이후 장들이 말하는 정규 JSON 규칙이 바로 이것이다.
| 함정 | 드러날 때의 증상 | 규칙 |
|---|---|---|
| 분모 없는 백분율 | 결과를 재유도하거나 방어할 수 없다 | 판독 수를 저장하고 백분율은 표시용으로 취급 |
| "미측정"에 0을 사용 | 측정하지 않은 값이 평균에 섞인다 | 부재는 null, 0은 측정된 0에만 |
| 오프셋 없는 시각 | 사이트 간 감사 이벤트를 정렬할 수 없다 | 오프셋을 명시한 RFC 3339. 저장소가 기록 |
| 정규화하지 않은 유니코드 | 중복 레코드, 검색 누락 | UTF-8 및 비교 전 NFC |
| 판독 시 미지 멤버 폐기 | 상대가 채운 필드가 반환 사본에서 사라진다 | 보존하고 경고. 조용히 버리지 않는다 |
| 식별자 재사용·재번호 | 회의록과 티켓의 인용이 다른 레코드를 가리킨다 | 식별자는 불변이며 재사용하지 않는다 |
| 문서 안에서 열거 값을 번역 | 판본 간 질의와 조인이 실패한다 | 토큰은 영어. 번역은 화면에서 |
| 예시 값을 설정으로 복사 | 자리표시자가 실제 레코드가 된다 | 예시는 눈에 보이게 무효인 자리표시자를 쓴다 |
| CSV 왕복 | 식별자와 날짜가 조용히 다시 쓰인다 | 내보내기는 파생물이며 한 방향이다 |
프로토콜 레코드는 그것을 만든 프로그램 밖에서도 쓸 수 있게 되려면 세 가지가 고정되어야 한다. 문서 형태, 식별자, 그리고 검증 게이트이다. 문서 형태는 JSON 멤버 이름과 SDK 속성 사이의 명시적 매핑을 요구하며, 여기에는 공개 자료끼리 어긋나는 세 멤버가 포함된다. 또한 값이 권고로 오해될 수 없는 예시를 요구한다. 식별자는 기관 범위를 가지고, 불변이며, 재사용되지 않아야 하고, 레코드 버전은 별도 필드로 두며, 정정은 편집이 아니라 새 버전으로 표현한다. 검증 게이트는 구조·참조·정책의 세 계층으로 나뉘어야 하고, 어떤 계층이 실행되었는지 보고해야 하며, 결과를 산출한 검증기와 정책 설정을 인용해야 한다. 참조 구현이 임계값으로 제공하는 모든 수치는 출처 없는 소프트웨어 기본값이며, 기관 설정 파일에 있어야 하고, 독자가 지침으로 오해할 수 있는 본문에는 결코 등장해서는 안 된다. 4장은 같은 레코드를 대상으로, 그것을 생산하고 점검하는 API 표면을 다룬다.