제7장

제7장: 적합성 시험과 인증

생물안전 기록 시스템이 WIA-BIO-017을 구현했음을 어떻게 입증하는가. 적합성 등급, 시험 벡터, 서명된 증거, 프로파일 매니페스트, 그리고 그 주장을 시간이 지나도 유효하게 유지하는 버전 규칙을 다룬다.

앞의 여섯 장은 WIA-BIO-017 기록 시스템이 무엇을 저장하고, 사고 워크플로를 어떻게 돌리며, 어떤 인터페이스를 내놓고, 기록을 어떻게 교환·감사하며, 어떻게 보호하는지를 설명했다. 이 장은 구매 담당자, 기관생물안전위원회(IBC) 사무국, 협력 기관이 결국 던지게 될 질문에 답한다. 이 시스템이 정말 그 모든 일을 한다는 것을 어떻게 아는가? WIA PHASE 문서가 내놓는 답은 로고나 벽에 거는 인증서가 아니다. 기계가 읽을 수 있는 증거의 묶음이다. 모든 요구사항을 실제로 돌려 보는 시험 벡터, 빠짐없이 공개하는 합격/불합격 매트릭스, 소프트웨어 자재명세서(SBOM), 그리고 어떤 요구사항을 지원하고 일부만 지원하며 제외했는지를 정직하게 밝힌 서명 매니페스트가 그것이다.

이 장 전체를 관통하는 구분 하나를 먼저 밝혀 둔다. 적합성 시험이 들여다보는 대상은 소프트웨어와 레코드이다. 실험실이 안전한지, 밀폐가 제대로 작동하는지, 연구자가 역량을 갖췄는지에 대해서는 아무것도 말하지 않는다. 그런 질문은 관할 당국의 시설 인증·점검 제도, 그리고 기관의 생물안전관리책임자와 IBC의 몫이다. 이 경계를 흐리는 것이 공급사나 기관이 저지를 수 있는 가장 해로운 실수이므로, 7.5절에서 다시 자세히 다룬다.

7.1 적합성 주장과 세 가지 레코드 수준 등급

7.1.1 공개 문서의 빈틈

각 PHASE 문서의 Annex E는 구현체가 운영 범위를 선언해야 한다고 하면서, 그 이유를 "하위 감사자가 Annex A의 올바른 적합성 등급에 따라 배포를 평가할 수 있도록"이라고 적는다. 그런데 어느 PHASE 문서에도 Annex A는 없고, 본 규격의 Appendix A는 전혀 다른 주제를 다룬다. Annex I 역시 구현체가 "규범적 요구사항 ID"를 나열하라고 요구하지만, PHASE 문서 어디에서도 그런 ID를 부여하지 않는다. 다시 말해 v1.0은 적합성의 기계 장치(벡터, 증거, 매니페스트)는 제법 자세히 설명하면서, 그 장치가 다룰 대상인 등급 구조와 번호가 매겨진 요구사항 목록은 빠뜨렸다.

구현자는 v1.1을 기다렸다가 증거를 공개할 수 없다. 그래서 이 책은 2장부터 6장까지의 레코드 모델과 워크플로에서 도출한 등급 구조와 요구사항 번호 체계를 제안한다. 이 절의 내용은 모두 이 책의 제안이며 표준 본문이 아니다. 나중에 공식 Annex A가 나오더라도 시험 벡터를 다시 쓰지 않도록 설계했다. 각 벡터는 요구사항 ID를 인용하므로, 이후 대응표 하나로 이 책의 ID를 공식 ID로 옮길 수 있다.

7.1.2 세 가지 등급

등급은 배포 규모가 아니라 레코드 모델의 층위를 따른다. 핵심 레코드(Core Records)은 어떤 주장에서든 필수다. 워크플로(Workflow)와 교환(Exchange)은 둘 다 핵심 레코드 위에 올라가지만 서로 독립적이다. 점검자용 읽기 전용 포털은 사고 워크플로 없이 교환만 필요할 수 있고, 한 부서 시범 운영은 워크플로 전체를 돌리면서 아무것도 교환하지 않을 수 있다.

등급(이 책의 제안)다루는 범위관련 장요구사항 ID 예
C — 핵심 레코드시설·교육·사고·준수 결과 레코드의 스키마 유효성, ID 접두사, ISO 8601 UTC 타임스탬프, 추가 전용 스키마 진화, 인증·교육 만료 탐지2BIO017-C-001 시설 스키마, BIO017-C-004 인증 만료, BIO017-C-005 출입 허가자의 교육 유효성, BIO017-C-009 같은 메이저 버전 안에서 호환 깨짐 금지
W — 워크플로사고 상태 기계와 허용 전이, 심각도별 통보 타이머, 근본원인 분석(RCA)·CAPA 레코드, 준수 지적 코드, 전이 시 역할 확인3, 4BIO017-W-002 적법한 상태 전이만 허용, BIO017-W-003 심각도별 통보 타이머, BIO017-W-006 CAPA 담당자·기한 필수
X — 교환메시지 봉투, 서명, 멱등성, 재전송 방지, 추가 전용 해시 체인 감사 이벤트, 인계·수령 기록, 외부 전송 시 인력 ID 가명처리5, 6BIO017-X-001 봉투 서명 검증, BIO017-X-004 감사 체인 무결성, BIO017-X-007 교환되는 사고 보고서에 직접 식별자 없음

그러면 주장은 WIA-BIO-017 v1.0 — Core Records + Workflow처럼 짧은 형태가 되고, 반드시 7.4절에서 설명할 프로파일 매니페스트가 함께 붙는다. 세부 내용은 매니페스트가 담는다. 머리 주장에는 모든 요구사항이 지원되거나, 감사자가 받아들인 사유와 함께 제외된 등급만 이름을 올린다.

7.1.3 비율이 아니라 합격/불합격

PHASE 문서의 Technical Requirements 표는 합격 기준으로 ">95% accuracy"를 적어 두었다. 이 수치는 템플릿 문구다. 기록 시스템에서 무엇을 재는지 정의되어 있지 않고, 적합성에 그대로 적용하면 요구사항 스무 개 중 하나가 실패해도 적합하다고 부를 수 있다는 뜻이 된다. 이 책은 더 엄격하고 단순한 규칙을 쓴다. 한 등급 안의 모든 요구사항이 긍정·부정 벡터를 통과했거나 매니페스트에서 명시적으로 제외된 경우에만 그 등급을 주장한다. 종합 점수는 없다.

같은 이유로 SDK의 complianceScore 필드(4장)를 적합성 지표로 써서는 안 된다. v1.0.0 소스를 읽어 보면 이 값은 통과한 검사의 비율이 아니라 경험적 계산이다. 위반 1건·경고 0건이면 50, 위반 2건이면 33이 나오고, 분모가 지적 건수에 따라 바뀐다. 대시보드에서 대략적인 정렬 기준으로 쓰는 정도가 한계다.

7.1.4 주장 문구 쓰기

주장에 쓰는 낱말은 그 뒤의 증거만큼이나 중요하다. 허용되는 예: "당사 생물안전 기록 플랫폼은 WIA-BIO-017 v1.0 핵심 레코드·워크플로 등급에 적합하다(자체 선언, 증거 패키지 분기별 공개)." 허용되지 않는 예: "WIA 인증 BSL-3 소프트웨어", "생물안전 법규 준수 보장", 그리고 소프트웨어 자체에 밀폐 등급을 붙이는 모든 문장. 소프트웨어에는 생물안전 등급이 없다. 등급은 시설에 붙으며, 그것을 부여하는 것은 관할 당국뿐이다.

7.2 시험 벡터

7.2.1 Annex G가 요구하는 것

Annex G는 벡터 규율을 다섯 가지로 정리하며, 이는 생물안전 기록에도 그대로 적용된다. 모든 규범적 요구사항에는 긍정 벡터(반드시 받아들여야 하는 입력)와 부정 벡터(특정 지적과 함께 반드시 거부해야 하는 입력)가 각각 하나 이상 있어야 한다. 벡터는 tests/phase-vectors/<phase>/ 아래, 예컨대 tests/phase-vectors/phase-1-data-format/에 둔다. 구현체는 모든 벡터를 지속적 통합(CI)에서 실행하고 그 합격/불합격 매트릭스를 공개한다. 부정적 결과도 긍정적 결과와 같은 충실도로 보고한다. 그리고 사유를 기록하지 않고 건너뛴 벡터는 감사자가 실패로 간주한다.

행동을 바꾸는 것은 마지막 규칙이다. 보통의 테스트 모음에서 건너뛴 테스트는 불편함에 그치지만, Annex G 아래에서는 그 자체가 지적 사항이다. 어떤 배포가 인계 기록을 구현하지 않는다면, 올바른 대응은 프로파일 매니페스트(7.4절)에서 인계 관련 요구사항을 excluded로 표시하고 그 사유를 붙인 채로 벡터를 건너뛰는 것이다. 조용히 지워 버려서는 안 된다.

7.2.2 벡터의 구성

PHASE 문서는 벡터 파일 형식을 정하지 않는다. 실용적인 형식은 네 부분으로 이루어진다. 시험 대상 요구사항, 고정된 평가 시각, 입력 레코드, 기대 결과다. 아래에서 asOf라고 부른 고정 시각은 생물안전 기록 시스템에서 특히 중요하다. 검사의 상당수가 만료에 관한 것이기 때문이다. 교육 기록을 "지금"과 비교하는 벡터는 오늘은 통과하고 내년에는 소프트웨어와 무관한 이유로 실패한다. 검증기의 모든 날짜 비교는 평가 시각을 매개변수로 받아야 하고, 모든 벡터는 그 시각을 고정해야 한다.

{
  "vectorId": "C-005-neg-01",
  "requirement": "BIO017-C-005",
  "phase": "phase-1-data-format",
  "polarity": "negative",
  "asOf": "2026-03-02T00:00:00Z",
  "policy": { "trainingExpiryField": "expirationDate" },
  "input": {
    "facility": {
      "facilityId": "FAC-EXAMPLE-0012",
      "name": "Example Institute Teaching Lab B",
      "bslLevel": "BSL-2",
      "authorizedPersonnel": ["PER-0142", "PER-0187"]
    },
    "trainingRecords": [
      { "personnelId": "PER-0142", "trainingType": "refresher",
        "trainingDate": "2025-02-10T00:00:00Z",
        "expirationDate": "2026-02-10T00:00:00Z",
        "certificationIssued": true },
      { "personnelId": "PER-0187", "trainingType": "initial",
        "trainingDate": "2025-11-03T00:00:00Z",
        "expirationDate": "2026-11-03T00:00:00Z",
        "certificationIssued": true }
    ]
  },
  "expected": {
    "valid": false,
    "findings": [
      { "code": "B009", "subject": "PER-0142" }
    ]
  }
}

모든 식별자는 가상이며, 이 픽스처에는 병원체나 재고 데이터가 전혀 없다. policy 블록은 벡터가 기대는 기관 차원의 가정을 기록한다. 여기서는 교육 기록에 적힌 만료일을 기준으로 삼는다는 가정이다. 재교육 주기는 표준이 아니라 기관 정책이 정한다(2장). 따라서 벡터에 "교육일로부터 1년" 같은 주기를 박아 넣어서는 안 된다. 다른 기관이 자체 정책표로 만료를 계산한다면 그 기관의 정책 블록과 벡터를 따로 쓰면 되고, 요구사항 ID는 그대로 유지된다.

7.2.3 네 가지 예제 벡터

다음 네 사례는 단순한 스키마 검사로는 잡을 수 없는 결함을 하나씩 잡아내기 때문에 골랐다. 각 사례마다 부정 벡터를 설명하며, 짝이 되는 긍정 벡터는 유효하게 만드는 한 가지 세부만 다르다.

1. 만료된 인증. 시설 레코드가 certification.certified: true이면서 expirationDate는 asOf보다 앞선다. 이 레코드는 스키마상 유효하며, 바로 그 점이 위험하다. 불리언 값만 읽는 대시보드는 이 시설을 인증된 것으로 보여 준다. 검증기는 날짜로부터 실효 상태를 도출하고 지적 코드 B010(SDK의 BiosaftyErrorCode 열거형에서 CERTIFICATION_REQUIRED)을 보고해야 한다. 참조 SDK의 validateBSLLevel은 이 검사를 하지 않는다는 점에 주의한다. 이 검사는 구현자의 레코드 검증기가 맡는다. 짝이 되는 긍정 벡터는 만료일을 asOf 이후로 옮긴다.

2. 출입 허가자의 교육 만료. 위에 보인 벡터다. authorizedPersonnel에 올라 있는 사람의 최신 교육 기록 만료일이 asOf보다 앞서면, 검증기는 그 사람의 ID를 지목하며 B009(TRAINING_EXPIRED)를 보고해야 한다. 두 레코드 유형을 조인해야 하는 검사라서 자주 빠진다. 유용한 두 번째 부정 벡터는 출입 허가자로 등재되어 있는데 교육 기록이 아예 없는 경우다. 기대 지적 코드는 같지만, 보고에는 "만료"가 아니라 "기록 없음"이라고 적어야 한다. 조치가 다르기 때문이다.

3. 허용되지 않는 사고 상태 전이. status가 closed인 사고에 3장에서 정의한 명시적 재개 이벤트 없이 곧바로 under-investigation으로 바꾸는 갱신이 들어온다. 두 번째 부정 벡터는 open을 곧장 resolved로 보낸다. 해결은 조사를 전제로 하므로 제3장이 의도적으로 두지 않은 경로다. 둘 다 HTTP 409로 거부되어야 하고, 사고 레코드는 바뀌지 않아야 한다. SDK의 오류 열거형에는 이 경우를 위한 전용 코드가 없다. 실패의 성격을 가려 버리는 B007(INVALID_PARAMETERS)을 빌려 쓰는 대신, 벡터는 HTTP 상태와 invalid-transition 같은 문제 유형을 확인하고, 이 빈틈은 v1.1 이슈로 기록한다(8장).

4. 호환을 깨는 필드 이름 변경. 새 빌드가 메이저 버전은 그대로 둔 채 nextTrainingDue를 nextDueDate로 바꾼다. 이 벡터는 성격이 다르다. 레코드가 아니라 스키마를 대상으로 돈다. CI 작업은 후보 JSON Schema를 마지막으로 공개한 스키마와 비교해, 필드가 삭제되거나 이름이 바뀌었거나 선택 필드가 필수로 바뀌었으면 실패시킨다. 짝이 되는 긍정 벡터는 새 선택 필드를 추가하는데, 이는 추가 변경이므로 허용된다. Annex E가 더 작은 버전 증가를 시사하는데도 이름 변경을 호환 깨짐으로 보는 이유는 7.6절에서 설명한다.

벡터 ID요구사항등급입력(요약)기대 결과
C-004-pos-01BIO017-C-004C인증된 시설, 만료일이 asOf 이후유효, 지적 없음
C-004-neg-01BIO017-C-004Ccertified: true, 만료일이 asOf 이전무효, B010
C-005-pos-01BIO017-C-005C출입 허가자 전원 교육 유효유효
C-005-neg-01BIO017-C-005C출입 허가자 1명 교육 만료무효, 해당 ID를 지목한 B009
C-005-neg-02BIO017-C-005C교육 기록이 없는 출입 허가자무효, B009(사유 "기록 없음")
W-002-pos-01BIO017-W-002W권한 있는 검토자가 resolved → closed수용, 감사 이벤트 기록
W-002-neg-01BIO017-W-002W재개 없이 closed → under-investigationHTTP 409, 레코드 불변
W-002-neg-02BIO017-W-002Wopen → resolvedHTTP 409, 레코드 불변
W-003-neg-01BIO017-W-003W심각도 3 사고, 1시간 목표(규격 §7.1.1)가 지나도 통보 없음통보 지연 플래그 발생
C-009-pos-01BIO017-C-009C새 선택 필드 추가스키마 비교: 추가 변경
C-009-neg-01BIO017-C-009CnextTrainingDue 이름 변경스키마 비교: 호환 깨짐, 빌드 실패

7.2.4 CI에서 벡터 실행하기

벡터 실행기는 작은 프로그램이다. 각 벡터를 읽고, 고정된 asOf로 검증기를 호출하고, 지적 결과를 기대와 비교한 뒤, 매트릭스에 한 행을 추가한다. 설계의 핵심은 검증기가 시스템 시계를 읽는 대신 평가 시각을 전달받는다는 점이다.

import { readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { validateRecords } from '../src/record-validator'; // 구현자의 검증기

type Row = { vectorId: string; requirement: string; result: 'pass' | 'fail' | 'skipped'; note?: string };

export function runVectors(dir: string, excluded: Map<string, string>): Row[] {
  const rows: Row[] = [];
  for (const file of readdirSync(dir).filter((f) => f.endsWith('.json'))) {
    const v = JSON.parse(readFileSync(join(dir, file), 'utf8'));
    const reason = excluded.get(v.requirement);
    if (reason) {
      rows.push({ vectorId: v.vectorId, requirement: v.requirement, result: 'skipped', note: reason });
      continue; // 프로파일 매니페스트의 사유를 붙여서만 건너뛴다
    }
    const got = validateRecords(v.input, { asOf: new Date(v.asOf), policy: v.policy });
    const codes = got.findings.map((f) => `${f.code}:${f.subject ?? ''}`).sort();
    const want = (v.expected.findings ?? []).map((f: any) => `${f.code}:${f.subject ?? ''}`).sort();
    const ok = got.valid === v.expected.valid && JSON.stringify(codes) === JSON.stringify(want);
    rows.push({ vectorId: v.vectorId, requirement: v.requirement, result: ok ? 'pass' : 'fail' });
  }
  return rows;
}

제외 목록은 프로파일 매니페스트에서 읽어 온다. 따라서 벡터를 건너뛰는 유일한 방법은 서명된 문서에서 해당 요구사항을 제외로 선언하는 것이다. 실행기를 둘러싼 파이프라인도 짧다.

# .github/workflows/conformance.yml (예시)
name: conformance
on: [push, pull_request]
jobs:
  vectors:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npm run typecheck && npm test
      - run: node dist/tools/run-vectors.js tests/phase-vectors --manifest profile-manifest.json --out evidence/vector-matrix.json
      - run: node dist/tools/schema-diff.js schemas/published schemas/current   # 호환 깨짐이면 실패
      - run: npx @cyclonedx/cyclonedx-npm --output-file evidence/sbom.cdx.json
      - uses: actions/upload-artifact@v4
        with: { name: evidence, path: evidence/ }

7.2.5 부정적 결과 보고는 자기 집에서부터

부정적 결과를 보고하라는 Annex G의 요구는 참조 자료에도 똑같이 적용되며, v1.0.0 SDK 디렉터리를 정직하게 읽어 보면 배울 점이 있다. 공개된 api/typescript 폴더에는 package.json과 src/만 있다. tsconfig.json도, 테스트 파일도 없다. test 스크립트가 호출하는 Jest는 테스트를 하나도 찾지 못하면 --passWithNoTests를 주지 않는 한 0이 아닌 종료 코드로 끝난다. 두 소스 파일을 TypeScript 5.3 strict 모드로 타입 검사하면 컴파일러는 34개의 오류를 보고하는데, 모두 같은 종류다. 선언부에서 이미 export한 이름을 각 파일 끝의 export type { … }, export { … } 블록이 한 번 더 export한다(TS2323, TS2484). 해결은 기계적이다. 중복된 export 목록을 지우면 된다.

CLI에도 비슷한 함정이 있다. wia-bio-017 validate-bsl은 위반을 찾으면 "NON-COMPLIANT"를 출력하지만, 스크립트는 결과와 상관없이 exit 0으로 끝난다. 종료 상태에 기대는 CI 단계는 그래서 늘 통과한다. CLI가 지적 시 0이 아닌 상태를 돌려주기 전까지는 파이프라인이 출력을 직접 검사해야 한다.

out="$(./cli/wia-bio-017.sh validate-bsl --pathogen BSL-2 --facility BSL-2 --procedure standard)"
printf '%s\n' "$out"
if printf '%s' "$out" | grep -q 'NON-COMPLIANT'; then
  echo "record-level mismatch found" >&2
  exit 1
fi

이 가운데 어느 것도 표준을 불신할 이유는 아니다. 증거 규율은 바로 이런 발견을 드러내려고 존재한다. 이를 문서로 남기고, 우회하고, 상류에 이슈로 올리는 구현자는 적합성 문화를 훼손하는 것이 아니라 오히려 보여 주는 것이다.

7.3 증거 패키지

7.3.1 구성

Annex G는 준수 패키지를 정해진 내용물을 담은 tarball로 정의한다. Annex H와 Annex I는 같은 묶음에 들어가야 할 항목 두 가지를 더한다. 이 모두가 있으면 감사자나 협력 기관은 구현자의 시험 매트릭스를 다시 돌리지 않고도 주장을 검증할 수 있다.

항목근거목적
CycloneDX 1.5 또는 SPDX 2.3 형식 SBOMAnnex G모든 구성요소와 버전을 나열해, 소비자가 알려진 취약점과 라이선스 문제를 확인할 수 있게 한다
OpenAPI 문서Annex G, PHASE Technical Requirements(OpenAPI 3.0)실제 배포된 인터페이스를 기술하고, Annex E가 요구하는 운영 범위(단일 테넌트, 다중 테넌트, 연합)를 선언한다
벡터 합격/불합격 매트릭스Annex G벡터마다 한 행. 건너뛴 벡터와 그 사유도 포함한다
프로파일 매니페스트Annex I요구사항별 상태: 지원, 일부 지원, 제외(7.4절)
변경 이력 대응표Annex H지원하는 PHASE 버전마다 빌드, 컨테이너 다이제스트, SDK 버전을 대응시킨다
서명 매니페스트Annex G위 파일 전부의 다이제스트. Sigstore로 DSSE 봉투에 서명하고 Rekor에 기록한다
evidence-2026Q1/
├── sbom.cdx.json
├── openapi.yaml
├── vector-matrix.json
├── profile-manifest.json
├── changelog-map.json
├── evidence-manifest.json        # 위 각 파일의 sha256
└── evidence-manifest.sigstore.json   # Rekor 기록을 포함한 DSSE 번들

7.3.2 서명과 투명성

Annex G가 Sigstore를 고른 이유는 "하위 소비자가 사설 인증기관을 신뢰하지 않고도 출처를 검증할 수 있도록" 하기 위해서다. 실무에서는 cosign 같은 Sigstore 도구로 매니페스트에 서명하며, 그 결과물인 번들에는 서명, 서명 인증서, Rekor 투명성 로그 포함 증명이 들어간다. 협력 기관은 이를 통해 받은 패키지가 공급사가 공개한 바로 그것이며 그 뒤로 바뀌지 않았음을 확인할 수 있다.

투명성 로그에는 생물안전 담당 조직이 첫 서명 전에 생각해 둬야 할 결과가 따른다. Rekor는 공개된 추가 전용 로그다. 키 없는 서명에 쓰인 신원, 흔히 이메일 주소나 CI 워크로드 신원은 그 로그에 영구히 남는다. 개인 메일함 대신 조직 차원의 워크로드 신원을 쓰고, 서명 대상의 이름이나 주석 어디에도 시설, 호실, 과제가 드러나지 않게 한다.

7.3.3 패키지에 절대 넣지 말아야 할 것

증거 패키지는 공유하려고, 때로는 공개하려고 만든다. 6장에서 시설 위치, 재고 메타데이터, 인력 데이터를 생물안전 기록 시스템의 민감한 핵심으로 짚었다. 그중 어느 것도 적합성 증거에 들어가서는 안 된다. 시험 픽스처는 처음부터 합성 데이터다. 가상의 시설 ID, 가상의 인력 ID를 쓰고, 병원체나 물질 등록부 항목은 넣지 않으며, 실재하는 건물·호실 이름도 쓰지 않는다. OpenAPI 문서는 코드에서 생성한다. 서버 목록에 내부 호스트 이름이 들어 있을 수 있는 운영 게이트웨이에서 내보내지 않는다. 매트릭스에는 벡터 ID와 결과만 적고 페이로드는 적지 않는다. 서명하면 실수가 공개 로그에 영구히 남으므로, 서명 전에 검토자가 6장의 가림 점검표를 패키지 전체에 적용해야 한다.

7.3.4 분기별 재공개

Annex G는 소스 코드가 바뀌지 않았더라도 분기마다 패키지를 다시 공개하라고 요구한다. 이유는 타당하다. 코드가 그대로여도 그 아래의 컴파일러, 의존성, 베이스 이미지, 운영체제는 바뀌며, 새 SBOM과 매트릭스는 무엇이 어긋났는지를 소비자에게 보여 준다. 기관 입장에서도 분기 주기는 위원회 일정과 자연스럽게 맞물린다. 생물안전관리책임자는 IBC 정기 시스템 보고에 최신 패키지를 첨부할 수 있고, 협력 기관은 연속된 두 매트릭스를 비교해 무엇이 바뀌었는지 정확히 볼 수 있다.

7.4 프로파일 매니페스트

7.4.1 상태와 서명

Annex I는 프로파일 매니페스트를 구현체가 요구사항마다 supported(지원), partial(일부 지원, 사유 첨부), excluded(제외, 정당화 첨부) 가운데 무엇인지를 밝히는 곳으로 정의한다. 매니페스트는 SBOM에 쓴 것과 같은 Sigstore 신원으로 서명한다. 부속서의 표현을 빌리면, 목적은 범위가 서로 다른 배포가 "부분 적합을 완전 적합으로 잘못 내세우지 않으면서 자신이 충족하는 규범 요구사항의 부분집합을 알리도록" 하는 것이다.

프로파일 이름에는 SemVer 버전이 붙는다. WIA-P1-DATA-FORMAT-Edge-only/2를 내세우는 배포는 PHASE-1의 "Edge-only" 프로파일 두 번째 메이저 버전에 적합하다고 주장하는 것이지, 두 번째 배포라는 뜻이 아니다. 레지스트리 항목은 바꿀 수 없으며, 새로운 배포 형태는 기존 항목을 고치지 않고 새 이름으로 추가한다. 매니페스트는 Annex J의 배포 토폴로지(단일 테넌트 엣지, 다중 테넌트 게이트웨이, 연합 메시, 망분리 배치)도 선언하며, 토폴로지가 바뀌면 새로 서명해야 한다. 연합 배포는 구성원 프로파일의 합집합과 교집합을 요약한 집계 매니페스트를 공개해, 호출자가 교환을 가장 낮은 공통 프로파일에 맞춰 보낼 수 있게 한다.

7.4.2 제외를 정직하게 선언하기

이 책은 레거시 SDK 함수 두 개, assessExposure와 generatePPERequirements를 관리 기록 시스템의 범위 밖에 둔다(경계는 4장에서 밝혔다). 이들의 출력은 자격을 갖춘 전문가와 당국 지침에 속하는 기술적 안전 판단이며, 배포에서는 기능 플래그로 꺼 두어야 한다. 그 결정을 감사자에게 드러내는 곳이 매니페스트다. 아무 언급 없이 두면 지원한다고 암시하게 된다. 사유와 함께 excluded로 표시하는 것이 정직하며, Annex I 아래에서 주장을 유효하게 유지하는 올바른 방법이기도 하다.

위험 점수 훅은 양극단보다 partial이 맞다. 기관 자체의 위해성 평가 결과는 레코드로 저장하고(4장), SDK의 calculateRiskScore는 쓰지 않는다. 명시된 0–100 범위가 자신의 입력 범위와 맞지 않고 임계값도 보정되지 않았기 때문이다. 매니페스트는 바로 그 내용을 적는다.

{
  "manifestVersion": "1.0.0",
  "implementation": {
    "name": "Example University Biosafety Records Platform",
    "version": "3.4.0",
    "sdk": "@wia/bio-017@1.0.0 (patched: duplicate exports removed)"
  },
  "standard": "WIA-BIO-017",
  "standardVersion": "1.0",
  "requirementScheme": "book-proposed/1",
  "profile": "WIA-BIO017-Campus-Core-Workflow/1",
  "topology": "single-tenant-edge",
  "operationalScope": "single-tenant",
  "tiersClaimed": ["C", "W"],
  "requirements": [
    { "id": "BIO017-C-001", "status": "supported" },
    { "id": "BIO017-C-004", "status": "supported" },
    { "id": "BIO017-C-005", "status": "supported" },
    { "id": "BIO017-C-009", "status": "supported" },
    { "id": "BIO017-W-002", "status": "supported" },
    { "id": "BIO017-W-003", "status": "partial",
      "reason": "Timers run for severity 2-4; severity 1 is batched into a daily digest by institutional policy." },
    { "id": "BIO017-X-001", "status": "excluded",
      "justification": "No inter-institution exchange in this deployment; Exchange tier not claimed." },
    { "id": "SDK-calculateRiskScore", "status": "partial",
      "reason": "Institution-supplied risk-assessment result stored as a record; SDK formula not used (range inconsistency, uncalibrated thresholds)." },
    { "id": "SDK-assessExposure", "status": "excluded",
      "justification": "Technical safety assessment is reserved to qualified professionals and authority guidance; function disabled by feature flag." },
    { "id": "SDK-generatePPERequirements", "status": "excluded",
      "justification": "Technical safety guidance is out of scope for a records system; function disabled by feature flag." }
  ],
  "evidence": {
    "vectorMatrix": "vector-matrix.json",
    "sbom": "sbom.cdx.json",
    "openapi": "openapi.yaml"
  }
}

이 예제의 두 가지 세부는 의도한 것이다. requirementScheme 필드는 ID가 이 책의 제안을 따른다고 밝혀, 2년 뒤의 독자가 이를 공식 번호와 혼동하지 않게 한다. 그리고 sdk 필드는 참조 SDK에 패치를 적용했다는 사실을 기록한다. Annex H의 변경 이력 대응표가 보존하려는 것이 바로 이런 사실이다.

7.4.3 프로파일이 넓어질 때

나중에 기관이 협력 기관과의 교환을 추가해 X 등급을 주장하게 되면, 새 프로파일 버전으로 새 매니페스트를 공개한다. Annex I는 이전 매니페스트가 Annex H의 폐기 예고 기간 동안 서명된 채 유효하게 남아 있어야 한다고 요구한다. 이는 감사에서 중요하다. 작년 사고를 검토하는 점검자는 오늘의 프로파일뿐 아니라 그 당시 시스템이 어떤 프로파일을 주장했는지를 볼 수 있어야 한다.

7.5 소프트웨어 적합성은 시설 인증이 아니다

7.5.1 서로 다른 세 가지 결정

WIA-BIO-017 시스템을 운영하는 기관은 적어도 세 종류의 승인과 함께 살아가며, 각 승인은 서로 다른 주체가 서로 다른 증거로 내린다. 이를 혼동하면 실제 피해가 생긴다. 적합한 소프트웨어가 실험실을 법규에 맞게 만든다고 암시하는 공급사는 고객을 오도하는 것이고, 초록색 대시보드를 인증의 증거로 여기는 기관은 규제상의 판단을 컴퓨터에 넘긴 것이다.

측면WIA-BIO-017 소프트웨어 적합성시설 인증·허가IBC 승인
대상소프트웨어 시스템과 그 기록 처리물리적 시설과 그 운영연구 과제 또는 계획서
결정 주체구현자가 자체 선언하고, 협력 기관과 감사자가 증거를 검증관할 국가 당국 또는 그 지정 기관기관생물안전위원회
증거벡터, 매트릭스, SBOM, 서명 매니페스트당국이 정한 점검, 문서, 기준위원회가 검토하는 위해성 평가와 과제 문서
기록상 위치기록 저장소 밖의 프로파일 매니페스트BiosaftyFacility의 certification 블록승인 ID로 참조하는 승인 레코드(2장)
입증하는 것시스템이 규정대로 기록을 저장·검증·교환한다시설이 당국의 요구사항을 충족한다과제를 명시된 조건 아래 진행해도 된다

7.5.2 인증 블록은 기록할 뿐 결정하지 않는다

certification 블록(certified, certificationDate, expirationDate, certifyingAuthority, nextInspectionDate)은 당국 결정을 옮겨 적은 것이다. 이를 지키는 규칙은 세 가지다. 첫째, 어떤 코드 경로도 계산으로 certified를 true로 만들 수 없다. 이 필드는 생물안전관리책임자 역할을 가진 사람만 쓰며, 쓸 때는 당국 문서 번호를 참조해야 한다. 둘째, 모든 쓰기는 변경 전후 해시를 담은 감사 이벤트(5장)를 남겨 상태 변경의 책임 소재를 분명히 한다. 셋째, 시스템은 만료일이 지나면 실효 상태를 자동으로 낮출 수는 있지만(벡터 C-004가 요구하는 동작이다), 결코 올릴 수는 없다.

한국에서 연구시설의 허가와 신고는 유전자변형생물체법(LMO법) 체계가 규율하며, 시설 등급별 요구사항은 관할 당국이 정하고 갱신한다. 이 책은 그 내용을 옮겨 적지 않는다. 시스템이 할 일은 당국 결정에 대한 참조를 보관하고, 날짜가 다가오면 알리는 것이다. 현행 요구사항은 기관이 관할 당국과 국가 바이오안전성정보센터에 직접 확인해야 한다.

7.5.3 기본 모델로서의 자체 선언

PHASE 문서는 WIA 표준 레지스트리(Annex H), 프로파일 레지스트리(Annex I), 디렉터리 서비스(Annex J) 같은 공용 서비스를 기술하지만, 이 책은 제3자 WIA 인증 서비스가 운영되고 있다고 가정하지 않는다. Annex J가 단일 테넌트 배포에 대해 설명하는 모델, 즉 조직이 자신의 매니페스트에 직접 서명하는 방식만으로도 충분하고 검증 가능하다. 누구나 서명, 투명성 로그 기록, 벡터 매트릭스를 확인할 수 있기 때문이다. 기관이 독립적인 보증을 원하면 증거 패키지에 대한 감사를 의뢰할 수 있지만, 그 감사 역시 소프트웨어에 관한 진술일 뿐이다. 마찬가지로 기관의 생물위해 관리 체계를 ISO 35001에 대해 인증하는 일은 경영시스템 인증기관이 수행하는 또 다른 문제다. 적합한 기록 시스템은 그 심사를 수월하게 해 줄 수는 있어도 대신할 수는 없다.

7.6 버전 관리와 폐기 예고

7.6.1 유의적 버전

Annex H는 PHASE 문서에 Semantic Versioning 2.0.0을 채택한다. 메이저 버전은 규범 요구사항의 하위 호환되지 않는 변경을, 마이너 버전은 기존 구현을 깨지 않는 요구사항 추가를, 패치는 편집상의 변경만을 뜻한다. 구현체도 자기 스키마와 매니페스트의 프로파일 이름에 같은 규칙을 적용해야 한다.

7.6.2 본문의 두 가지 충돌과 이 책의 정리

부속서끼리 두 군데에서 서로 어긋나며, 한쪽만 따르고 다른 쪽을 보지 않은 독자는 잘못된 정책을 세우게 된다.

주제Annex F / Annex EAnnex H이 책
호환 깨짐 이후 병행 기간최소 90일(Annex F)최소 180일 병행 유지180일. 더 엄격한 값이 양쪽을 모두 충족한다
필드 이름 변경·삭제마이너 버전 증가(Annex E)호환되지 않는 변경은 메이저 증가메이저 증가. 이름 변경은 옛 이름을 읽는 모든 소비자를 깨뜨리며, 이것이 SemVer가 말하는 비호환이다

두 번째 정리의 실무적 효과는 벡터 C-009가 메이저 버전 안에서 필드 이름을 바꾼 빌드를 모두 실패시킨다는 것이다. 이때 팀의 선택지는 두 가지다. 옛 필드를 폐기 예정 별칭으로 새 필드와 나란히 두거나(추가 변경이므로 마이너 릴리스에서 허용), 이름 변경을 다음 메이저 버전으로 미루고 폐기 예고 절차를 온전히 밟는 것이다. 거의 언제나 첫 번째가 싸게 먹힌다. 2장이 SDK의 "Biosafty" 철자에 대해 권한 방식도 같다. 원래 식별자를 글자 그대로 유지하고, 철자가 바른 별칭을 더한다.

7.6.3 폐기 예고 일정

Annex H는 두 가지 기간을 정한다. 최소 180일의 병행 기간 동안 옛 메이저 버전과 새 메이저 버전은 WIA 레지스트리에서 모두 Stable로 표시되며, 어느 쪽을 인용해도 적합하다. 그 뒤 옛 버전은 12개월의 지원 종료(sunset) 기간에 들어가 Deprecated로 표시되고, 무엇이 그 요구사항을 대체했으며 왜 바뀌었는지를 설명하는 이전 안내가 붙는다. 이 책은 두 기간을 연속된 것으로 읽으며, 그러면 새 메이저 버전이 어떤 날짜(예시)에 나왔을 때 일정은 다음과 같다.

단계기간옛 메이저의 상태구현자가 할 일
병행 기간출시일로부터 최소 180일Stable새 버전으로 빌드·시험하고, 옛 버전의 증거도 계속 공개한다
지원 종료 기간12개월Deprecated저장된 레코드와 협력 기관을 이전하고, 새 매니페스트를 공개하며, 옛 매니페스트도 검증 가능하게 유지한다
지원 종료 이후—Retired옛 버전에 대한 주장은 더 이상 "WIA 적합"이 아니다. 과거 증거는 보관한다

패치 수준의 정오표는 규범 동작을 바꾸지 않으므로 기간이 필요 없다. Annex H는 이를 공개 정오표 대장에 올리고 항목마다 작업반 의장이 서명하도록 요구한다. 8장에 정리한 v1.0 이슈 가운데 식별자 오타나 문서 표기 같은 것은 정오표 후보다. 반면 위험 점수 범위의 불일치는 동작을 바꾸므로 적어도 마이너 버전이 필요하다.

7.6.4 저장된 레코드의 버전 관리

생물안전 기록 시스템에는 상태 없는 API에 없는 의무가 하나 있다. 레코드를 여러 해에 걸쳐, 스키마가 여러 번 바뀌는 동안 보관한다는 것이다. 그러므로 저장된 모든 레코드에는 기록 당시의 스키마 버전을 함께 남겨야 한다. 한 메이저 버전에서 다음 메이저 버전으로의 이전 자체도 감사 대상 작업이다. 이름이 붙은 작업으로 실행하고, 배치마다 변경 전후 해시를 담은 감사 이벤트를 남기며, 옛 형식을 참조한 과거 증거 패키지는 결코 다시 쓰지 않는다. 레코드와 증거의 보존 기간은 각 관할권의 법령과 기관 정책이 정한다. PHASE 문서가 제시하는 7년은 현지 법령과 대조해 볼 실무상의 최소치로 다뤄야 하며, 법적으로 충분하다는 진술로 받아들여서는 안 된다.

끝으로 Annex H는 구현체가 지원하는 PHASE 버전마다 그것을 충족하는 구체적인 빌드, 컨테이너 다이제스트, SDK 버전을 대응시킨 변경 이력표를 공개하라고 요구한다. 분기별 증거 패키지, 보관된 매니페스트와 합치면 감사자는 끝까지 추적할 수 있다. 어느 날짜를 짚든 그때 시스템이 어떤 표준 버전을 주장했고, 어떤 빌드가 돌고 있었으며, 벡터 매트릭스가 무엇이라고 했는지를 알 수 있다.

요약

WIA-BIO-017 적합성은 배지로 주장하는 것이 아니라 증거로 입증하는 것이다. v1.0에는 부속서가 가리키는 Annex A 등급표와 번호 매긴 요구사항이 없으므로, 이 책은 세 가지 레코드 수준 등급(핵심 레코드, 워크플로, 교환)과 벡터가 인용할 수 있는 요구사항 번호 체계를 제안했다. 모든 요구사항에는 평가 시각을 고정한 긍정·부정 벡터가 있어야 하고, 건너뛴 벡터에는 서명된 프로파일 매니페스트의 사유가 있어야 한다. 증거 패키지는 SBOM, OpenAPI 문서, 벡터 매트릭스, 매니페스트, 변경 이력 대응표를 Sigstore 서명 아래 묶으며, 합성 데이터만 담고, 분기마다 다시 공개한다. 매니페스트는 범위 밖 SDK 함수를 제외로 선언한다. 소프트웨어 적합성은 당국의 시설 인증이나 IBC 승인을 결코 대신하지 않는다. 버전 관리는 SemVer를 따르며, 180일 병행 기간과 12개월 지원 종료 기간을 두어 Annex E·F·H 사이의 충돌을 더 엄격한 규칙 쪽으로 정리한다.

복습 문제

  1. 만료와 관련된 모든 벡터가 asOf 시각을 고정해야 하는 이유는 무엇이며, 검증기가 시스템 시계를 읽으면 무엇이 잘못되는가?
  2. 어느 공급사의 매트릭스가 벡터 96% 통과, 건너뛴 행 없음을 보여 준다. 이 장의 규칙에 따르면 핵심 레코드 등급을 주장할 수 있는가?
  3. 증거 패키지의 어떤 항목이 민감한 시설 정보를 흘릴 수 있으며, 서명 전에 이를 어떻게 막겠는가?
  4. 배포에서 이미 꺼 둔 assessExposure를 굳이 프로파일 매니페스트에 적어야 하는 이유를 설명하라.
  5. 개발자가 Annex E를 근거로 마이너 릴리스에서 필드 이름을 바꿨다. 어떻게 대응하고, 어떤 대안을 제시하겠는가?

기술적 생물안전 내용은 공식 생물안전 당국에 확인할 것. 이 장은 소프트웨어와 기록의 적합성만 다룬다. 밀폐 요구사항, 시설 인증 기준을 비롯한 모든 기술적 생물안전 지침은 관할 국가 당국(한국의 경우 질병관리청과 과학기술정보통신부, 국가 바이오안전성정보센터), WHO 실험실 생물안전 매뉴얼(LBM), BMBL을 따른다.