홍수 경보 체계의 신뢰도는 그것을 뒷받침하는 증거를 넘어서지 못한다. 이 장은 WIA-ENE-033 구현체가 무엇을 지원하는지를 어떻게 입증하는지 다룬다. v1.0 문서에 공존하는 두 가지 적합성 어휘, 시험 벡터, 서명된 증거 패키지, 상호운용 프로필, 버전 정책, 그리고 가동 이후의 운영 성과를 검증하는 MRV 보고를 차례로 살펴보고, 감사인을 부르기 전에 스스로 돌려 볼 수 있는 자체 점검 절차로 마무리한다.
앞선 장들은 구현체가 무엇을 해야 하는가를 물었다. 이 장은 구현체가 무엇을 보여 줄 수 있는가를 묻는다. 둘은 같은 질문이 아니다. 예측 노드가 매일 올바른 RiverLevelData와 유효한 CAP 1.2 경보를 만들어 내더라도, 어떤 빌드가 특정 경보를 만들었는지, 어떤 요구사항이 시험되었는지, 어떤 시험이 왜 생략되었는지를 아무도 추적할 수 없다면 감사를 통과하지 못한다. ENE-033의 적합성 부속서는 바로 이 추적 가능성을 다룬다. 이 장은 데이터 모델을 읽을 때와 같은 태도로 부속서를 읽는다. 즉 v1.0이 실제로 공개한 것, 이름만 언급한 것, 문서끼리 서로 어긋나는 곳을 분명히 구분한다.
이하의 논의를 좌우하는 사실은 세 가지다. 첫째, 적합성 관련 장치(부속서 G, H, I, J)는 네 PHASE 문서 모두에서 비규범적(non-normative)으로 표시되어 있다. 이 부속서들은 증거의 형태를 정할 뿐이며, 인정 기관이 뒷받침하는 인증 제도를 정의하지 않는다. 둘째, 부속서가 의존하는 산출물 가운데 여러 개(부속서 A, tests/phase-vectors/ 디렉터리, api/openapi.yaml 문서)는 인용만 되었을 뿐 v1.0 저장소에 존재하지 않는다. 셋째, 부속서 E부터 J까지는 PHASE 이름만 다를 뿐 네 문서에서 문장이 완전히 같다. 따라서 한 PHASE에 적힌 규칙은 같은 문장으로 네 PHASE 모두에 적용되며, 서로 충돌하는 규칙 역시 그대로 네 번 반복된다.
"ENE-033의 적합성 등급"을 찾는 독자는 서로 무관한 두 목록을 만나게 된다. 릴리스 노트는 3단계 모델을 제시한다. Surface(데이터 포맷), Verified(연 1회 제3자 감사), Anchored(Sigstore로 서명한 증거 패키지의 지속 발행)가 그것이다. 릴리스 노트는 이 모델이 "README와 부속서 A에" 기술되어 있다고 하지만, 실제로 v1.0 저장소의 README에는 해당 내용이 없고 부속서 A는 공개되지 않았다. 한편 PHASE-4 §14.1은 '인증 요구사항'이라는 제목 아래 네 단계의 인증 레벨을 따로 둔다. Bronze(기본 예측 기능), Silver(조기 경보 시스템), Gold(침수 시뮬레이션), Platinum(완전 통합 시스템)이다. 이 제목과 내용은 PHASE-4 안에 똑같이 두 번 실려 있는데, 새로운 정보가 없는 편집상의 중복이다.
두 목록은 서로 다른 것을 잰다는 점을 분명히 해 두는 편이 좋다. Surface / Verified / Anchored는 증거가 얼마나 강한가를 나타낸다. 스스로 선언한 포맷 점검에서, 특정 시점의 외부 감사를 거쳐, 계속 갱신되고 누구나 검증할 수 있는 증거로 올라간다. Bronze부터 Platinum까지는 기능 범위가 얼마나 넓은가를 나타낸다. 예측만 하는지, 아니면 경보 발령, 침수 모의, 외부 시스템 연동까지 하는지를 가른다. 두 목록 모두 시험 기준을 정의하지 않고, 누가 등급을 부여하는지도 밝히지 않으며, 둘 사이의 관계도 설명하지 않는다.
| 구분 | 증거 등급(tier) | 기능 배지(badge) |
|---|---|---|
| 출처 | 릴리스 노트(README와 부속서 A를 가리킴) | PHASE-4 §14.1 |
| 단계 | Surface, Verified, Anchored | Bronze, Silver, Gold, Platinum |
| 평가 대상 | 증거의 강도와 최신성 | 구현된 기능의 범위 |
| 정의 문서 | 부속서 A(인용되었으나 v1.0에 미공개) | 단계별 한 줄 설명뿐, 기준 없음 |
| 평가 주체 | 자체(Surface), 제3자 감사인(Verified), 서명의 공개 검증(Anchored) | 명시 없음 |
| 대표적인 ENE-033 객체 범위 | 프로필에 따라 다름 | Bronze: FloodPredictionResult / Silver: + FloodAlert·CAP / Gold: + 모의 결과로서의 inundationArea / Platinum: + SystemIntegration과 대피 데이터 |
이 책은 부속서 A가 공개되기 전까지 두 목록을 모두 쓸모 있게 유지하는 한 가지 해석을 제안한다. 두 목록을 격자의 두 축으로 보는 것이다. 그러면 적합성 주장은 "Silver / Verified"와 같은 형태가 된다. 이는 예측과 CAP 경보 발행이 범위에 포함되고, 최근 1년 안에 외부 감사인이 증거를 검토했다는 뜻이다. 상류 예보를 받아 지역 경보만 내는 도시 조기경보 시스템이라면 정직하게 "Silver / Anchored"를 주장할 수 있다. 침수 모델을 돌리고 스마트시티 플랫폼과 연동하지만 증거 관리를 아직 수작업으로 하는 국가 기관이라면 "Platinum / Surface"가 될 것이다. 이 해석은 표준의 문언이 아니라 이 책의 해석이다. 사용한다면 문서에 그 사실을 밝히고, 공식 제도인 것처럼 제시하지 말아야 한다.
ENE-033 v1.0은 인정 기관도, 인증서 양식도, 인증 제품 등록부도 정하지 않는다. 구현자가 사실대로 공개할 수 있는 것은 서명된 증거 패키지와, 다른 사람이 확인할 수 있는 프로필 매니페스트뿐이다. "ENE-033 Gold 인증" 같은 홍보 문구는 공개된 어떤 기준으로도 검증할 수 없으며, 조달 담당자는 그 점을 감안해 읽어야 한다. 구현체는 선언한 프로필과 공개한 증거로 설명하는 것이 옳다.
부속서 G는 이후의 모든 적합성 작업이 기대는 규칙을 정한다. 모든 규범적 요구사항에는 긍정 벡터와 부정 벡터가 각각 하나 이상 있어야 한다는 것이다. 긍정 벡터는 적합한 구현체가 받아들이고 올바르게 처리해야 하는 입력이다. 부정 벡터는 구현체가 거부해야 하는 입력, 또는 결코 만들어 내서는 안 되는 출력 조건이다. 벡터는 PHASE별로 tests/phase-vectors/phase-1-data-format/, phase-2-api-interface/, phase-3-protocol/, phase-4-integration/ 아래에 둔다. 적합성을 주장하는 구현체는 모든 벡터를 지속적 통합(CI)에서 실행하고 합격/불합격 매트릭스를 공개해야 한다(MUST).
v1.0 시점에는 이 디렉터리가 아직 없다. 따라서 구현자는 지금 자체 벡터를 작성하고, 공식 벡터가 공개되면 그것으로 교체해야 한다. 이는 생각만큼 낭비가 아니다. 홍수 예측에서 중요한 벡터 대부분은 데이터 모델과 SDK에서 곧바로 도출되며, 벡터를 작성하는 과정에서 제2~4장에서 살펴본 명세와 SDK의 차이를 어떻게 처리할지 팀이 결정하지 않을 수 없게 된다. 아래 표는 PHASE-1과 PHASE-2 시험 모음이 갖추어야 할 쌍의 예다. 벡터 식별자는 예시다.
| 벡터(예시 ID) | 극성 | 입력 | 기대 동작 |
|---|---|---|---|
p1-river-level-pos-01 | 긍정 | current, levels, trend, status를 모두 갖춘 RiverLevelData | 수용되며 단위(m, m³/s, m/h)가 보존됨 |
p1-river-level-neg-01 | 부정 | current.waterLevel을 문자열 "4.2m"로 전송 | 해당 필드를 지목하는 검증 오류와 함께 거부 |
p1-request-neg-01 | 부정 | timeHorizon: "12h" | 거부. 유효한 값은 1h, 6h, 24h, 72h뿐임(isValidTimeHorizon 참조) |
p1-result-neg-01 | 부정 | 키 이름이 "contributing factors"(공백 포함)인 FloodPredictionResult | 거부하거나 경고. 조용히 버려서는 안 됨 |
p1-result-neg-02 | 부정 | riskLevel: "LEVEL-5" | 거부. RiskLevel은 LEVEL-4에서 끝남 |
p2-warning-pos-01 | 긍정 | warningPublish로 발행한 FloodAlert | WIA 봉투와 CAP 1.2 문서가 함께 생성됨 |
p2-warning-neg-01 | 부정 | sent 시각에 문자 Z를 쓴 CAP 출력 | 생성되어서는 안 됨. CAP 1.2는 알파벳 시간대 표기를 금지하므로 UTC는 -00:00으로 적음 |
p2-warning-neg-02 | 부정 | 첫 좌표쌍과 마지막 좌표쌍이 다른 CAP polygon | 생성되어서는 안 됨. CAP 1.2는 좌표쌍 네 개 이상으로 닫힌 고리를 요구함 |
이 표에서 강조할 점은 세 가지다. 첫째, 부정 벡터는 스키마의 모양만이 아니라 동작을 시험한다. 두 CAP 벡터는 입력이 아니라 변환기의 출력을 검사한다. ENE-033의 ISO 8601 시각(끝이 Z)을 CAP 필드로 그대로 옮기는 변환기는 아무 오류도 내지 않은 채 무효한 CAP를 만들어 내기 때문이다. 둘째, 어떤 벡터는 명시된 결정과 함께 읽어야만 의미가 있다. include* 플래그를 생략한 FloodPredictionRequest를 보자. PHASE-1 §5.1은 이 플래그들을 필수로 적었지만 SDK는 선택 항목으로 만들었다. 이 벡터의 합격 여부는 구현체가 어느 쪽을 택했느냐에 달려 있으며, 그 선택은 프로필 매니페스트(§7.4)에 기록해야 한다. 셋째, PHASE-3 시험 모음은 "LEVEL-3이고 확률이 60 %인 결과는 evacuationRequired를 참으로 만든다"와 같은 결정론적 검사와, 예보 기술(skill) 같은 통계적 성능을 구분해야 한다. 벡터 매트릭스에는 결정론적 검사만 들어가며, 예보 기술은 MRV(§7.6)로 보고한다.
부속서 G는 공급자가 부정 결과를 긍정 결과와 같은 충실도로 보고하도록 요구하고, "기록된 사유 없이 생략된 시험은 감사인이 불합격으로 취급한다"고 적는다. 이 한 문장이 매트릭스의 형식을 결정한다. 모든 행에는 결과가 있어야 하고, 깔끔한 합격이 아닌 행에는 반드시 이유가 있어야 한다. 아래는 그런 매트릭스의 한 가지 배치다. 필드 이름은 예시이며, 부속서는 내용을 정할 뿐 스키마를 정하지 않는다.
{
"standard": "WIA-ENE-033",
"standardVersion": "1.0",
"phase": "phase-1-data-format",
"implementation": { "name": "basin-forecast-node", "version": "2.4.1" },
"vectorSet": { "source": "local", "revision": "example-rev-17" },
"results": [
{ "id": "p1-river-level-pos-01", "requirement": "P1-5.4-RiverLevelData",
"polarity": "positive", "result": "pass" },
{ "id": "p1-river-level-neg-01", "requirement": "P1-5.4-RiverLevelData",
"polarity": "negative", "result": "pass",
"observed": "HTTP 400, 오류가 current.waterLevel 을 지목" },
{ "id": "p1-request-neg-01", "requirement": "P1-5.1-FloodPredictionRequest",
"polarity": "negative", "result": "fail",
"observed": "구형 파서가 timeHorizon 12h 를 수용",
"tracking": "issue #212 (예시)" },
{ "id": "p1-drainage-pos-01", "requirement": "P1-5.6-DrainageData",
"polarity": "positive", "result": "skipped",
"justification": "프로필 매니페스트에서 DrainageData 를 제외함(7.4 참조)" }
],
"summary": { "pass": 2, "fail": 1, "skippedWithJustification": 1,
"skippedWithoutJustification": 0 }
}
본받을 만한 설계 선택이 두 가지 있다. vectorSet.source 필드는 결과가 자체 벡터에서 나왔는지 공식 벡터에서 나왔는지를 기록한다. 공식 벡터가 나오면 어느 매트릭스를 다시 돌려야 하는지 곧바로 알 수 있다. 또 요약에서 사유 없는 생략을 0건이라도 따로 센다. 감사인이 그것을 찾아 헤맬 필요가 없도록 하기 위해서다.
벡터 매트릭스와 프로필 매니페스트는 모두 "규범적 요구사항 ID"를 가리키지만, v1.0의 어느 PHASE 문서도 요구사항에 번호를 매기지 않았다. 이 장에서 쓰는 ID(P1-5.4-RiverLevelData 등)는 PHASE, 절 번호, 객체 또는 오퍼레이션을 잇는 자체 규칙을 따른다. 규칙을 하나 정해 증거와 함께 공개하고, 공식 ID가 생기면 그것에 대응시킬 계획을 세워 두어야 한다. 자체 제작한 ID를 표준의 일부인 것처럼 제시해서는 안 된다.
부속서 G는 적합성 패키지를 네 가지를 담은 tarball로 정의한다. CycloneDX 1.5 또는 SPDX 2.3 형식의 소프트웨어 자재 명세서(SBOM), OpenAPI 문서, 시험 벡터 매트릭스, 서명된 매니페스트다. 서명에는 Sigstore를 쓴다. 서명 대상을 DSSE(Dead Simple Signing Envelope)로 감싸고 Rekor 투명성 로그에 항목을 남기므로, 누구든 사설 인증기관을 신뢰하지 않고도 출처를 검증할 수 있다. 릴리스 노트는 여기에 in-toto Attestation 1.0을 추가한다. PHASE-4는 참조 목록에 CycloneDX 1.5를 명시하고, 패키지를 "관할 당국"에 제출하는 것으로 설명한다.
아래 트리는 부속서 G를 충족하면서 이후 부속서와 MRV가 요구하는 항목을 담을 여지를 남긴 패키지 배치의 한 예다. 디렉터리 이름은 예시다.
ene033-evidence-2026Q3/ (예시 패키지 이름)
├── manifest.json 색인: 모든 파일과 SHA-256 다이제스트
├── manifest.sigstore.json manifest.json 의 Sigstore 번들(DSSE + Rekor 항목)
├── sbom/
│ └── forecast-node.cdx.json CycloneDX 1.5(또는 SPDX 2.3) SBOM
├── openapi/
│ └── openapi.yaml 배포본이 실제로 제공하는 OpenAPI 문서
├── vectors/
│ ├── phase-1-data-format.matrix.json
│ ├── phase-2-api-interface.matrix.json
│ ├── phase-3-protocol.matrix.json
│ └── logs/ 매트릭스가 참조하는 CI 원본 로그
├── profiles/
│ ├── WIA-P1-DATA-FORMAT.profile.json
│ ├── WIA-P2-API-INTERFACE.profile.json
│ └── *.sigstore.json 서명(SBOM 과 같은 서명 신원)
├── attestations/
│ └── build.intoto.jsonl 소스·빌드·이미지 다이제스트를 잇는 in-toto 진술
├── changelog-mapping.json PHASE 버전 → 빌드 / 컨테이너 다이제스트 / SDK 버전
└── mrv/
└── error-budget-2026-08.json 월간 p95 지연, 오류율, 위반 시간
각 구성 요소는 읽는 사람의 서로 다른 질문에 답한다. SBOM은 "이 빌드 안에 무엇이 들어 있는가"에 답한다. 예측 노드라면 대개 수치 수리(水理) 라이브러리, 머신러닝 런타임, GIS 라이브러리가 들어 있고, 저마다 취약점과 라이선스 문제를 안고 있다. OpenAPI 문서는 "이 배포본이 무엇을 제공한다고 주장하는가"에 답한다. 이것은 SDK가 기대하는 모양이 아니라 배포본이 실제로 제공하는 문서여야 한다. 제4장에서 보았듯 SDK에는 수집(ingest) 엔드포인트가 없고, README의 /evacuation/capacity는 SDK의 /evacuation/shelters/{id}와 다르다. 패키지는 실제로 존재하는 것을 기록해야 한다. 벡터 매트릭스는 "무엇을 시험했고 결과가 어땠는가"에, 프로필 매니페스트는 "이 배포본이 어떤 요구사항을 주장하는가"에 답한다. in-toto 진술은 소스 커밋과 빌드 과정을 컨테이너 다이제스트에 연결한다. 부속서 H가 권고하는 변경 이력 매핑이 있으면 감사인은 매트릭스를 다시 돌리지 않고도 "PHASE-2 v1.0"에서 특정 이미지까지 따라갈 수 있다.
부속서 I는 프로필 매니페스트를 "SBOM에 쓴 것과 같은 Sigstore 키로" 서명하라고 한다. 그런데 Sigstore에서 흔히 쓰는 키리스(keyless) 방식에는 장기 보관 키가 없다. OIDC 신원(예를 들어 CI 워크로드 신원)에 대해 수명이 짧은 인증서가 발급되고, 서명은 Rekor에 기록된다. 이 경우 "같은 키"는 "같은 서명 신원"으로 읽어야 한다. 검증자는 키 지문이 아니라 인증서의 신원과 발급자를 확인한다. 관리형 키 쌍을 쓰는 배포라면 키를 어디에 보관하고 어떻게 교체하는지 문서화해야 한다. PHASE-2는 매니페스트 서명 키를 "배포 정책에 따라" 교체한다고만 적고, 교체 주기는 정하지 않는다.
Rekor 항목은 처음 보기보다 중요하다. 경보를 발행한 기관이 나중에 놓친 경보나 허위 경보를 해명해야 할 때, 그날 유효했던 증거 패키지가 무엇이었는지를 공개 로그로 보여 줄 수 있다. 로그 항목이 사고보다 먼저 기록되었으므로 기관 자신을 신뢰할 필요도 없다. 부속서 G가 변경이 없어도 분기마다 재발행하도록 요구하는 이유도 같다. 겉보기에 그대로인 빌드 밑에서도 컴파일러, 의존성, 운영체제 갱신이 조금씩 환경을 바꾸며, 날짜가 찍힌 새 서명은 증거가 다시 점검되었음을 보여 준다.
부속서 G는 패키지를 어디에 공개할지 정하지 않는다. PHASE-2는 서명된 매니페스트와 함께 /.well-known/wia/ene-033이라는 디스커버리 지점(제4장에서 다룬 RFC 8615 방식)을 둔다. 합리적인 방법은 이 디스커버리 문서가 현재 패키지를 다이제스트로 가리키고, 패키지 자체는 일반적인 릴리스 저장 위치에 두는 것이다. 연합형이나 망분리 배치 토폴로지(부속서 J, 제6장)는 전달 경로를 바꿀 뿐 내용은 바꾸지 않는다. 망분리된 런타임은 배치마다 서명된 패키지를 만들고, 운영자가 이를 물리적으로 옮긴다. 반대편에서도 서명은 그대로 검증된다.
모든 것을 구현하는 홍수 시스템은 드물다. 유역 관리 기관은 예측과 경보는 운영하되 도시 배수는 지자체에 맡길 수 있다. 도시 시스템은 수리 모델 없이 예보를 받아 쓰기만 할 수 있다. 부속서 I는 이런 배포가 부분 적합을 완전 적합처럼 보이게 하지 않으면서 자기 범위를 밝힐 수 있도록 존재한다. 각 구현체는 JSON 형식의 프로필 매니페스트를 공개하고, 요구사항 ID마다 세 상태 가운데 하나를 적는다. supported(지원), partial(부분 지원, reason 필드 동반), excluded(제외, justification 필드 동반)다. 매니페스트는 SBOM과 같은 신원으로 서명한다.
프로필에는 작업반이 관리하는 공개 등록부의 이름이 붙는다. 등록부 항목은 변경할 수 없으며, 새로운 형태는 새 이름으로 추가된다. 이름에는 유의적 버전(Semantic Versioning)의 주 버전 번호가 붙는다. 그래서 WIA-P2-API-INTERFACE-Edge-only/2는 PHASE-2의 "Edge-only" 프로필 두 번째 주 버전에 적합하다는 주장이지, 그 프로필의 두 번째 배포라는 뜻이 아니다. "Edge-only"와 "Federated-with-replay"는 부속서가 든 예시일 뿐 그 내용은 공개되지 않았다. 등록부가 공개될 때까지는 어떤 등록부 이름이든 독자에게 직접 정의해 주어야 하는 이름표로 다루어야 한다.
아래 매니페스트는 유역 예측 노드의 PHASE-1 프로필 예시다. 세 가지 상태 값과 reason/justification 필드는 부속서 I에서 왔고, 나머지 필드 이름은 예시다.
{
"profile": "WIA-P1-DATA-FORMAT-Edge-only/1",
"standard": "WIA-ENE-033",
"phase": "PHASE-1-DATA-FORMAT",
"standardVersion": "1.0",
"requirementIdConvention": "자체 규칙: PHASE-절-객체 (v1.0 에는 공식 ID 없음)",
"implementation": {
"name": "basin-forecast-node",
"version": "2.4.1",
"image": "sha256:(배포된 컨테이너의 다이제스트)"
},
"topology": "single-tenant-edge",
"requirements": [
{ "id": "P1-5.3-PrecipitationData", "status": "supported" },
{ "id": "P1-5.4-RiverLevelData", "status": "supported" },
{ "id": "P1-5.5-DamStatusData", "status": "supported" },
{ "id": "P1-5.1-FloodPredictionRequest", "status": "partial",
"reason": "include* 플래그를 선택 항목으로 수용(SDK 동작). PHASE-1 5.1 은 필수로 규정. 누락 시 true 로 간주." },
{ "id": "P1-5.6-DrainageData", "status": "excluded",
"justification": "유역 노드는 배수 펌프장·저류지를 운영하지 않으며, DrainageData 는 지자체 시스템이 발행함." }
],
"evidence": {
"package": "ene033-evidence-2026Q3.tar.gz",
"sha256": "(패키지의 다이제스트)"
}
}
이 파일에서 가장 쓸모 있는 줄은 partial 항목이다. 표준의 모호함을 명시적이고 시험 가능한 동작("누락 시 true로 간주")으로 바꾸어 놓았기 때문이다. 소비자는 이를 전제로 설계할 수 있고, 벡터는 이를 검사할 수 있다. 반대로 매트릭스에는 생략과 불합격이 있는데 매니페스트는 전부 supported라고 적는다면 문서가 스스로 모순된다. 예보가 아무리 정확하더라도 감사인은 이를 지적해야 한다.
광역 관측망을 묶는 국가 기관처럼 연합형으로 배포된 경우에는 구성원 프로필의 합집합과 교집합을 요약한 통합 매니페스트를 공개한다. 더 중요한 것은 교집합이다. 디렉터리 서비스는 교집합을 보고, 어떤 상호작용에 필요한 최소 공통 프로필로 요청을 보낸다. 광역 관측망 하나가 DamStatusData를 지원하지 않는다면, 국가 상황판은 모든 유역에 댐 자료가 있다고 가정해서는 안 된다. 배포가 더 넓은 프로필로 옮겨 가더라도 이전 매니페스트는 부속서 H의 폐기 유예 기간 동안 서명된 채 유효하게 남는다. 그래서 작년 경보에 대한 감사도 당시에 적용되던 프로필을 기준으로 확인할 수 있다. 부속서 J는 토폴로지가 바뀌면(예를 들어 단일 테넌트 엣지에서 멀티 테넌트 게이트웨이로) 반드시 새 매니페스트 서명에 반영해야 한다고(MUST) 덧붙인다.
부속서 H는 각 PHASE 문서에 유의적 버전 2.0.0(Semantic Versioning 2.0.0)을 적용한다. 주 버전은 규범적 요구사항의 하위 호환 불가 변경을, 부 버전은 기존 구현을 깨지 않는 규범적 요구사항의 추가를, 수 버전은 편집상의 수정만을 뜻한다. 수 버전 수준의 정오표는 폐기 유예 기간 없이 발행되고, 공개 정오표 등록부에 기록되며, 항목마다 작업반 의장이 서명한다. 폐기된 주 버전은 12개월의 일몰 기간을 거친다. 이 기간 동안 등록부는 해당 버전을 Deprecated로 표시하고, 무엇이 요구사항을 대체했으며 왜 바뀌었는지 설명하는 이행 안내를 붙인다.
구현자가 알아야 할 불일치가 세 가지 있다. 모두 실제 이행 계획의 날짜나 버전 번호를 바꾸는 문제다.
| 주제 | 한 부속서의 규정 | 다른 부속서의 규정 | 권장 처리 |
|---|---|---|---|
| 하위 호환 불가 변경 후 병행 기간 | 부속서 F: WIA 참조 구현에서 이전 주 버전과 최소 90일 중첩 | 부속서 H: 이전 주 버전을 최소 180일 병행 유지 | 180일을 기준으로 계획한다. 두 수치를 모두 밝히고 어느 하나만 "규칙"으로 인용하지 않는다. |
| 필드 이름 변경·삭제 | 부속서 E: 부 버전을 올려야 함(MUST) | 부속서 H: 하위 호환 불가 변경은 주 버전을 올림 | 이름 변경과 삭제는 호환 불가(주 버전)로 취급한다. 어느 쪽이든 소비자의 파서는 깨진다. |
| 버전 관리 대상 | PHASE 문서: 부속서 H에 따른 SemVer | 부속서 F: 참조 자료(벡터, 시뮬레이터, TypeScript SDK)는 별도로 버전 관리 | changelog-mapping.json에 둘 다 기록한다. SDK 1.0.0과 PHASE v1.0은 같은 것이 아니다. |
세 번째 행은 숫자가 비슷해서 놓치기 쉽다. TypeScript SDK와 CLI는 모두 1.0.0을 표시하고, SDK는 모든 요청에 X-WIA-Version: 1.0.0을 보낸다. PHASE 문서는 "Version 1.0"이다. 부속서 F는 PHASE 문서를 개정하지 않고도 참조 자료를 고칠 수 있도록 명시적으로 허용한다. 예컨대 estimateTimeToOverflow의 버그를 고친 SDK 1.0.3은 표준의 버전에 대해 아무것도 말해 주지 않는다. 변경 이력 매핑이 이 둘을 분리한다. 지원하는 PHASE 버전마다 한 행을 두고, 각 행에 그 버전을 충족하는 빌드, 컨테이너 다이제스트, SDK 버전을 적는다.
발행일도 같은 주의가 필요하다. README는 "Version 1.0.0 (2025-12-25)"라고 적고, 릴리스 노트는 발행일을 2026-04-26으로 적는다. 증거 패키지에서는 표준을 "WIA-ENE-033 v1.0 (Stable)"로 지칭하고, 날짜가 필요하다면 어느 문서에서 온 것인지 함께 밝히는 편이 좋다.
적합성 증거는 구현체가 표준의 포맷과 프로토콜을 올바르게 다룬다는 것을 보여 줄 뿐, 예보가 쓸 만한지, 경보가 제때 사람들에게 닿는지는 말해 주지 않는다. README의 MRV(측정·보고·검증) 프로토콜은 이 두 번째 질문을 세 단계의 보고로 다룬다. 여기서 "tier"라는 말이 이 표준 안에서 세 번째 의미를 얻는다는 점에 유의해야 한다. §7.1의 적합성 등급, 부속서 F의 안정도 구분(Stable / Provisional / Reference)과 더불어서다. 증거와 보고서에는 어느 의미인지 항상 밝혀 두어야 한다.
| MRV 단계 | 주기 | 대상 | 내용(README) | 가장 가까운 SDK 타입 |
|---|---|---|---|---|
| Tier 1 — 공개 대시보드 | 실시간 | 주민, 언론 | 현재 홍수 위험 단계, 주요 하천 수위 현황, 활성 경보 현황, 대피 중인 인원 수 | MonitoringDashboard, GET /alert/active |
| Tier 2 — 관리 보고 | 일일 | 운영자, 감독 기관 | 예측 정확도 통계, 경보 발령·해제 이력, 시스템 가동 현황, 데이터 품질 지표 | KPIDashboard(PredictionKPIs, ResponseKPIs) |
| Tier 3 — 정밀 분석 | 월간 | 경영진, 감사인 | 상세 예측 모델 성능 분석, 센서 캘리브레이션 기록, 대응 훈련 평가, 제3자 검증 보고서 | FloodResponseReport(사건 단위)와 부속서 E의 오류 예산 요약 |
MRV의 골격은 SDK 타입이 제공한다. PredictionKPIs는 accuracy(전체·단기·중기·장기), leadTime(평균·최소·최대), reliability(falseAlarmRate, missedEventRate, confidenceScore)를 묶는다. ResponseKPIs는 경보 전달(전달 시간, 전달 성공률, 주민 인지도), 대피(완료율, 평균 소요 시간, 대피소 이용률), 피해 감소를 다룬다. FloodResponseReport는 사건 단위 보고서로, PredictionPerformance(선행 시간[시간], 정확도[%], 허위 경보 건수, 놓친 사건 건수), ResponsePerformance, 경제적 손실을 원화로 적는 ImpactAssessment, 그리고 LessonsLearned를 합친다.
여러 필드에는 단위나 정의가 적혀 있지 않다. falseAlarmRate는 비율일 수도 백분율일 수도 있고, 발령된 경보를 분모로 삼을 수도, 비사건을 분모로 삼을 수도 있다. 제5장에서 보았듯 드문 홍수에서는 두 방식의 숫자가 크게 다르다. publicAwareness와 overallScore(0–100)에는 산출 방법이 아예 없다. 다른 기관과 비교할 수 있는 MRV 보고서라면 KPI마다 단위, 산식, 사건 정의(어느 관측소에서 어느 기준 수위를 넘으면 홍수로 셀 것인가), 검증 기간을 밝혀야 한다. 이 정의는 Tier 3 보고서에 싣고 버전을 매겨 관리한다. 사건 정의 하나를 바꾸는 것만으로도 모델을 개선한 것보다 KPI가 더 크게 움직일 수 있기 때문이다.
README는 단기(6시간) 예측 정확도 85 %, 중기(24시간) 정확도 75 %, 허위 경보율 15 % 이하, 평균 조기 경보 시간 6시간 이상, 경보 전달 5분 이내, 시스템 가용성 99.9 % 같은 KPI 값을 나열한다. PHASE-3에도 비슷한 targetPerformance 값이 있다. 이것들은 명세에 적힌 설계 목표이지 측정된 결과가 아니며, 어떤 증거 패키지도 이를 달성한 성과처럼 되풀이해서는 안 된다. 또한 "정확도"와 "허위 경보율"이 공통된 정의 없이 제시되었다는 점에도 유의해야 한다. 드문 사건에서는 정확도 수치가 높으면서도 홍수 대부분을 놓치는 시스템이 있을 수 있다. 모든 백분율 뒤에 있는 분할표의 건수를 함께 보고하라.
MRV에는 개인정보 문제도 걸려 있다. Tier 1은 대피 중인 인원 수를 보여 주고, ResponsePerformance는 대피 인원과 개설된 대피소 수를 센다. 이런 수치는 가구를 식별할 수 없는 수준으로 집계해 공개하고, 수신자 명단과 대피소 수용 기록은 증거 패키지에서 완전히 빼야 한다. 데이터 최소화는 제6장에서 다루었다. 적합성의 관점에서 규칙은 단순하다. 증거 패키지는 공개를 전제로 하므로, 배포본이 따르는 개인정보 규정상 공개할 수 없는 것은 아무것도 담아서는 안 된다.
Tier 3는 "제3자 검증 보고서"를 요구하지만 누가 제3자인지는 정하지 않는다. 표준이 인정 제도를 지정하지 않으므로, 보고서에는 검증자가 운영자로부터 얼마나 독립적인지와 검토 범위를 밝혀야 한다. 부속서 E의 감사 기록 보존 조항도 마찬가지다. 이 조항은 대부분의 관할에서 7년이면 ISO/IEC 17065:2012의 감사 기대 수준을 충족한다고 적지만, ISO/IEC 17065는 인증기관에 대한 요구사항을 정한 표준이며 7년이라는 기간을 규정하지 않는다. 보존 기간은 운영자에게 실제로 적용되는 국내 규정을 근거로 정하고, 그 출처를 기록해야 한다.
아래 절차는 이 장의 내용을 실제 작업으로 옮긴 것이다. 감사인을 부르기 전에 팀이 스스로 수행할 수 있으며, 각 단계는 §7.3의 증거 패키지에 들어갈 파일을 하나씩 만든다. Node.js 18 이상(SDK가 명시한 엔진), xmllint, SBOM 생성기, Sigstore의 cosign 도구가 있다고 가정한다. 명령은 예시이므로 사용하는 도구 버전의 옵션을 확인해야 한다.
PHASE-1은 스키마를 JSON Schema 2020-12로 표현한다고 적지만, v1.0이 실제로 공개한 것은 JSON Schema 파일이 아니라 TypeScript 인터페이스다. 공식 스키마가 나올 때까지는 types.ts에서 스키마를 생성하고, 그렇게 했다는 사실을 기록한다.
$ npx ts-json-schema-generator \
--path api/typescript/src/types.ts \
--type RiverLevelData \
> evidence/schemas/RiverLevelData.schema.json
# FloodPredictionRequest, FloodPredictionResult, DamStatusData,
# DrainageData, PrecipitationData, FloodAlert 에 대해 반복한다.
# 주의: 생성기의 기본 출력은 2020-12 가 아닌 이전 JSON Schema 초안을 선언한다.
# 이 편차를 매니페스트에 기록한다.
이 단계에서 생성된 스키마가 추가 속성을 허용할지 결정해야 한다. 허용하면 "contributing factors" 키에 대한 부정 벡터는 결코 실패할 수 없다. 철자가 틀린 키가 그냥 무시되기 때문이다. 적합성 시험에는 엄격한 설정(추가 속성 불허)이 더 쓸모 있다.
$ npx ajv-cli validate -s evidence/schemas/RiverLevelData.schema.json \
-d "vectors/positive/river-level-*.json"
$ for f in vectors/negative/river-level-*.json; do
if npx ajv-cli validate -s evidence/schemas/RiverLevelData.schema.json -d "$f" \
>/dev/null 2>&1; then
echo "FAIL (accepted): $f"
else
echo "pass (rejected): $f"
fi
done
시험 모음이 가장 자주 틀리는 곳이 부정 벡터의 판정을 뒤집는 부분이다. 부정 벡터가 "검증을 통과했다"는 것은 구현체의 불합격을 뜻한다. 동작 벡터(isValidTimeHorizon과 evacuationRequired 검사, 잘못된 요청에 대한 API 응답)는 프로젝트의 일반 테스트 러너에서 실행한다. SDK의 package.json에는 이미 Jest가 설정되어 있다. 그 결과도 §7.2와 같은 매트릭스 형식에 넣는다.
PHASE-2는 warningPublish가 WIA 봉투와 함께 CAP 1.2를 내보내도록 요구한다. 시험용 FloodAlert 객체들을 자체 변환기로 CAP 문서로 바꾸고, OASIS가 공개한 CAP 1.2 XML 스키마로 검사한다.
$ xmllint --noout --schema cap-1.2.xsd out/cap/alert-*.xml
out/cap/alert-0001.xml validates
out/cap/alert-0002.xml validates
스키마 유효성은 필요조건일 뿐 충분조건이 아니다. XSD는 요소 구조와 일부 값 형식을 검사하지만, polygon은 XSD에게 그저 문자열이므로 닫히지 않은 고리도 통과한다. 이런 경우를 위해 §7.2의 규칙 기반 검사를 유지하고, 제3장의 매핑 결정이 지켜지는지도 확인한다. 심각도·긴급도·확실도가 CAP의 고정 값으로 대응되는지, event.onset/expiry가 CAP의 onset/expires로 옮겨지는지가 그것이다.
$ syft dir:. -o cyclonedx-json > evidence/sbom/forecast-node.cdx.json
# "specVersion" 필드를 확인한다. 도구가 허용하면 CycloneDX 1.5 로 고정하고,
# 아니면 SPDX 2.3 으로 생성한다.
$ sha256sum evidence/**/*.json evidence/openapi/openapi.yaml \
> evidence/digests.txt # manifest.json 의 입력
이어서 프로필 매니페스트(§7.4)와 변경 이력 매핑(§7.5)을 작성한다. 서명하기 전에 매니페스트를 매트릭스와 대조한다. supported로 표시한 요구사항마다 합격한 긍정 벡터와 부정 벡터가 하나 이상 있어야 하고, excluded로 표시한 요구사항에는 같은 사유를 인용한 생략 벡터가 있어야 한다.
$ cosign sign-blob --yes \
--bundle evidence/profiles/WIA-P1-DATA-FORMAT.profile.sigstore.json \
evidence/profiles/WIA-P1-DATA-FORMAT.profile.json
$ cosign verify-blob \
--bundle evidence/profiles/WIA-P1-DATA-FORMAT.profile.sigstore.json \
--certificate-identity "(CI 워크로드 신원)" \
--certificate-oidc-issuer "(OIDC 발급자)" \
evidence/profiles/WIA-P1-DATA-FORMAT.profile.json
Verified OK
$ tar -czf ene033-evidence-2026Q3.tar.gz -C evidence .
검증은 항상 외부인의 입장에서, 공개된 번들과 기대하는 신원만으로 수행한다. 빌드 머신에서만 검증되는 서명은 하류 지자체에게 아무것도 증명하지 못한다. 마지막으로 tarball을 색인하는 매니페스트에 서명하고, 디스커버리 문서가 새 패키지 다이제스트를 가리키게 한 뒤, 다음 분기 재발행 일정을 달력에 넣는다.
| # | 점검 항목 | 근거 | 증거 파일 |
|---|---|---|---|
| 1 | 적합성 주장이 어느 어휘를 뜻하는지 밝히고, "인증"이라는 표현을 쓰지 않음 | 릴리스 노트, PHASE-4 §14.1 | 패키지의 README |
| 2 | 자체 요구사항 ID 규칙이 문서화됨 | 부속서 G, I | manifest.json |
| 3 | 주장한 요구사항마다 긍정·부정 벡터가 각각 하나 이상 있음 | 부속서 G | vectors/*.matrix.json |
| 4 | 사유 없는 생략이 없고, 불합격은 추적 번호와 함께 기재됨 | 부속서 G | vectors/*.matrix.json |
| 5 | CAP 1.2 출력을 시간대·폴리곤 규칙까지 포함해 검증함 | PHASE-2(API Operations) | CAP 검증 로그 |
| 6 | 명세와 SDK의 차이를 사유와 함께 partial로 정리함 | 부속서 I | profiles/*.profile.json |
| 7 | CycloneDX 1.5 또는 SPDX 2.3 SBOM이 배포된 빌드와 일치함 | 부속서 G | sbom/ |
| 8 | OpenAPI 문서가 실제로 제공되는 엔드포인트와 일치함 | 부속서 G | openapi/openapi.yaml |
| 9 | 매니페스트와 프로필을 하나의 Sigstore 신원으로 서명하고 독립적으로 검증함 | 부속서 G, I | *.sigstore.json |
| 10 | PHASE 버전, 빌드, 컨테이너 다이제스트, SDK 버전이 대응됨 | 부속서 F, H | changelog-mapping.json |
| 11 | 토폴로지를 선언했고, 토폴로지 변경 후 새로 서명함 | 부속서 J | 프로필 매니페스트 |
| 12 | KPI 정의(단위, 산식, 사건 정의)를 공개했고, 목표치를 실적처럼 제시하지 않음 | README MRV / KPI | mrv/ |
| 13 | 패키지에 개인정보가 없고, 보존 기간은 적용되는 국내 규정에서 가져옴 | PHASE-4 §13.2, 부속서 E | 개인정보 처리 설명 |
| 14 | 분기별 재발행 일정이 잡혀 있음 | 부속서 G | 릴리스 일정표 |
ENE-033 v1.0은 적합성 증거의 형태를 분명히 제시한다. 쌍으로 이루어진 벡터, 서명된 패키지, 부분 지원을 인정하는 프로필, 폐기 절차가 공개되는 SemVer가 그것이다. 그러나 공급자 간 비교를 가능하게 할 내용, 즉 공식 요구사항 ID, 공식 벡터, 부속서 A, 단일한 적합성 어휘는 아직 제공하지 않는다. 올바른 대응은 기다리는 것이 아니다. 명확히 표시한 자체 규칙으로 지금 증거 파이프라인을 구축하고, 문서끼리 어긋나는 모든 지점과 그에 대한 선택을 기록하며, MRV 보고서에서 목표치와 측정치를 정직하게 구분하는 것이다. 빠진 부분이 공개되면, 이렇게 준비한 팀은 ID만 다시 대응시키면 된다. 반면 "Gold 인증"을 내세워 온 팀은 그것이 무슨 뜻이었는지 해명해야 할 것이다. 제8장은 v1.1 로드맵에서 이 빠진 조각들을 다시 다룬다.