🚚 WIA 배송 로봇 표준 전자책 | 제7장 (총 8장)
앞의 네 장은 WIA ROB-010 구현이 무엇을 해야 하는지를 다루었다. 1단계 페이로드를 내보내고, 2단계 REST 인터페이스를 제공하며, 3단계 내비게이션 계약에 따라 행동하고, 4단계 플릿에 연결되는 것이다. 이 장은 그것을 증명하는 방법을 다룬다. 구매자, 건물 운영자, 도시의 인허가 담당 부서는 소스 코드를 읽으려 하지 않는다. 그들이 원하는 것은 "이 로봇 스택의 이 빌드는 WIA ROB-010 v1.0.0의 X 등급에 적합하며, 그 증거는 여기 있다"라는 검증 가능한 주장이다. 이 장에서는 표준의 세 가지 적합성 등급이 실무에서 무엇을 뜻하는지, 이미 알고 있는 스키마와 오류 사례로부터 시험 벡터를 만드는 방법, 증거 패키지를 조립하고 서명하는 방법, 참조 CLI가 검사하는 것과 검사하지 않는 것, 3단계 행동 목표를 정직하게 측정하는 방법, 그리고 WIA 적합성이 끝나고 제품 안전 인증이 시작되는 경계를 설명한다.
이 장 전체에 걸쳐 한 가지 주의가 필요하다. v1.0.0의 적합성 체계는 세 곳에 흩어져 있다. README(등급), 각 PHASE 문서 말미에 반복되는 비규범적 부속서 E–G, 그리고 cli/delivery-robot.sh 스크립트다. 이 중 어느 것도 등급별 요건을 상세히 정의하지 않는다. 이 장이 그 문서들의 내용을 넘어서는 부분에서는 그 사실을 밝히고 저자의 권고로 표시한다.
README는 적합성 등급을 각각 한 줄로 정의한다. Surface는 데이터 형식, Verified는 연 1회 제3자 감사, Anchored는 지속적인 증거 패키지다. 등급은 취지상 누적적이다. 적합하지 않은 JSON을 내보내는 구현이 Anchored라고 주장하는 것은 의미가 없다. 부속서 G와 함께 읽으면 실무적 해석은 다음과 같다.
$id에 대해 검증하여 위반이 있는지 직접 확인할 수 있다.| 등급 | README 정의 | 최소 증거 | 확인 주체 | 주기 |
|---|---|---|---|---|
| Surface | 데이터 형식 | 1단계(및 2단계 오류 사례) 벡터 매트릭스, OpenAPI 문서, 선언된 프로필 | 공급자. 어떤 고객이든 재확인 가능 | 릴리스마다 |
| Verified | 연 1회 제3자 감사 | Surface 증거 + 3단계 행동 시험 기록, 4단계 통합 로그, 서명된 감사 보고서 | 독립 감사인 | 최소 연 1회, 그리고 주 버전마다 |
| Anchored | 지속적인 증거 패키지 | Verified 증거 + SBOM, 투명성 로그 항목이 있는 Sigstore 서명 매니페스트, 분기별 재공개 | 소비자의 자동 검증 | 분기마다, 그리고 릴리스마다 |
등급 정의가 말하지 않는 두 가지를 분명히 해 둔다. 첫째, 공개 문서는 누가 Verified 등급의 감사인이 될 자격이 있는지, 그리고 감사인을 위한 WIA 인정 제도를 정하지 않는다. 그런 제도가 생기기 전까지는 독립성과 역량을 입증할 수 있는 감사인을 선택하고(예: ISO/IEC 17065(제품 인증)에 따라 인정받은 인증기관이나 ISO/IEC 17025에 따라 인정받은 시험기관), 그 이름을 증거 패키지에 적는다. 둘째, 어떤 등급도 안전 인증이 아니다. 로봇은 Anchored 등급에 적합하면서도 붐비는 보도에서 위험할 수 있다. 이 문제는 7.7절에서 다시 다룬다.
부속서들은 등급 채점을 다루는 부속서와 또 다른 부속서를 언급하지만, 네 개의 PHASE 파일 어디에도 그런 부속서는 없다. 자체 문서에서 이를 인용하지 말고, 등급 명칭은 README를, 증거 형태는 부속서 G를 인용한다.
같은 소프트웨어라도 배포 방식은 크게 다를 수 있으며, 감사인은 그 방식을 모르면 격리·가용성·증거 최신성을 판단할 수 없다. CLI의 profiles 명령은 네 가지 명명된 프로필을 나열한다.
| 프로필 | CLI 설명 | 배송 로봇의 전형적 사례 | 시험에서 추가로 보여야 할 것 |
|---|---|---|---|
single-tenant | 조직당 하나의 런타임 | 자체 플릿 관리자를 운영하는 캠퍼스 운영자 | 기본 벡터 외에 없음. 감사가 가장 단순하다 |
multi-tenant | 공유 런타임, 헤더 기반 격리 | 여러 가맹점이나 건물주에게 서비스하는 플릿 플랫폼 | 테넌트 A의 토큰으로 테넌트 B의 로봇·패키지·텔레메트리 토픽을 읽을 수 없음을 증명하는 부정 벡터 |
federated | 런타임 간 P2P 메시 | 한 건물의 엘리베이터와 출입문을 공유하는 두 운영자 | 런타임 간 신원, 시각 합의, 오프라인 상태인 상대 런타임 처리 |
air-gapped | 일괄 증거 패키지, 실시간 연결 없음 | 외부 인터넷이 없는 병원이나 공장 현장 | 일괄 반출되는 증거, 오프라인 서명 검증(7.4절 참조) |
부속서 E는 구현이 운영 범위를 OpenAPI 문서에 선언하도록 요구하지만 세 가지(single-tenant, multi-tenant, federated)만 열거하고, CLI는 네 가지를 열거한다. 해당하는 것을 air-gapped까지 포함해 선언하고, 이 불일치를 적합성 메모에 기록한다. OpenAPI 3.1에는 이를 위한 전용 필드가 없으므로, info 객체의 확장 필드가 가장 무난한 위치다.
openapi: 3.1.0
info:
title: Example Fleet Platform — WIA ROB-010 Phase 2
version: 2.4.0
x-wia-conformance:
standard: WIA ROB-010
standardVersion: 1.0.0
tier: surface
profile: { named: multi-tenant, version: 1 }
tenantHeader: X-Tenant-Id
x-wia-conformance 키는 표준 문안이 아니라 저자의 제안이다. x- 접두사는 OpenAPI가 확장을 위해 제공하는 장치다. 멀티테넌트 프로필에서는 격리 헤더를 명시해 두어야 감사인이 추측 없이 테넌트 간 부정 벡터를 작성할 수 있다.
부속서 G의 규칙은 간단하다. 각 PHASE의 모든 규범적 요건에는 최소 하나의 긍정 벡터와 하나의 부정 벡터가 있어야 하며, 이는 PHASE마다 하나씩 있는 tests/phase-vectors/<phase>/ 디렉터리(phase-1-data-format, phase-2-api-interface, phase-3-protocol, phase-4-integration)에 둔다. 적합성을 주장하는 구현은 CI에서 모든 벡터를 실행하고 통과/실패 매트릭스를 공개해야 한다. 부속서 G가 이 벡터들을 설명하고는 있지만, 이 글을 쓰는 시점에 공개 저장소에 완전한 공식 벡터 세트가 있다고 가정해서는 안 된다. 저장소를 직접 확인하고, 자체 벡터를 작성해 매트릭스와 함께 공개할 준비를 해야 한다.
긍정 벡터는 올바른 입력이 수용되거나 올바른 행동이 일어남을 보여 준다. 부정 벡터는 잘못된 입력이 거부되거나, 고장이 요구된 대응을 촉발함을 보여 준다. 실제 결함을 잡아내는 것은 부정 벡터다. 무엇이든 받아들이도록 느슨하게 설정된 검증기는 모든 긍정 벡터를 통과한다.
부속서 G는 벡터의 파일 형식을 정의하지 않는다. 다음 형태는 저자의 제안으로, 각 벡터가 스스로를 설명하게 하여 매트릭스의 각 행을 그것이 시험하는 요건까지 추적할 수 있게 한다.
{
"vector_id": "P1-RS-BATTERY-NEG-01",
"phase": "phase-1-data-format",
"requirement": "RobotState.battery_level is a number in [0, 100]",
"kind": "negative",
"schema": "https://wiastandards.com/schemas/delivery-robot/v1/robot-state.json",
"input": {
"robot_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-09-01T09:15:00Z",
"version": "1.0.0",
"position": { "latitude": 37.5665, "longitude": 126.9780, "source": "fused" },
"battery_level": 104
},
"expect": { "valid": false, "error_path": "/battery_level" }
}
error_path가 중요하다. "무효"만 단언하는 부정 벡터는 전혀 다른 이유로 거부되어도 통과한다. 예를 들어 검증기가 $ref를 해석하지 못해 실패하는 경우다. 예상 실패 위치를 고정해야 벡터가 자신이 시험한다고 주장하는 규칙을 실제로 시험하게 된다.
필요한 규칙은 거의 모두 제3장에 있다. 스키마의 키워드를 하나씩 따라가며 규칙마다 한 쌍을 작성한다.
required의 각 항목(RobotState: robot_id, timestamp, version, position, battery_level) — 해당 필드를 뺀 부정 벡터minimum/maximum — 경계값(긍정: battery_level 0과 100, heading 360)과 경계 바로 바깥 값(부정: −0.1, 100.1)enum — 목록에 있는 값(긍정)과 그럴듯한 근사값(부정: mode = "navigating". 3단계 상태이지만 1단계 모드는 아니다)pattern — version "1.0", 접근 코드 "12ab"를 부정 벡터로format — 2단계 예시 값인 pkg-2025-12-26-0001처럼 UUID가 아닌 package_id를 부정 벡터로처음 시도할 때 대부분 두 가지 기술적 함정에 걸린다. 첫째, JSON Schema 2020-12에서 format은 기본적으로 단언이 아니라 주석(annotation)이다. 형식 검사를 켜지 않으면 많은 검증기가 "robot_id": "hello"를 받아들이며, 일부 형식 검사기는 선택 의존성이 없으면 date-time 검사를 조용히 건너뛴다. format 부정 벡터는 바로 이 문제를 즉시 드러내기 위해 존재한다. 둘째, 공개된 RobotState 스키마는 CargoStatus, ActiveRoute, Error를 참조하지만 1단계 어디에도 정의되어 있지 않다(3.7절). 이 정의를 채우기 전에는 스키마가 컴파일되지 않는다. 참조가 로컬 참조(#/$defs/CargoStatus)이므로 로컬 사본 robot-state.json 안에서 해석되어야 한다. 사양이 따로 제시한 $defs(Position, SensorReadings)를 모두 모아 사본을 조립하고, 누락된 항목에는 명확한 이름을 붙인 자체 정의 파일을 가리키는 항목을 추가한다. 예를 들면 "CargoStatus": { "$ref": "https://schemas.example.com/wia-dr-local-defs.json#/$defs/CargoStatus" }와 같다. 감사인이 무엇을 채웠는지 정확히 볼 수 있도록 두 파일을 모두 증거 패키지에 넣는다.
2단계 벡터는 문서가 아니라 HTTP 교환이다. 요청, 예상 상태 코드, 그리고 실패의 경우 예상되는 오류 봉투의 code로 구성된다. 제4장의 요건이 그대로 옮겨진다.
| 요건 | 긍정 벡터 | 부정 벡터 |
|---|---|---|
| Bearer 토큰 필수 | 유효한 토큰 → 200 | Authorization 헤더 없음 → 오류 봉투와 함께 401 |
| 범위(스코프) 강제 | robot:control 토큰으로 내비게이션 시작 → command_id와 함께 202 | robot:read 전용 토큰으로 commands/start 호출 → 403 |
| 존재하지 않는 자원 | 존재하는 로봇 → 200 | 알 수 없는 robot_id → 404, code = ROBOT_NOT_FOUND |
| 입력 검증 | 유효한 패키지 → 201 | weight_kg = −1인 패키지 → 400 |
| 호출 한도 | 200 응답에 X-RateLimit-* 헤더 존재 | 한도 초과 → 429, code = RATE_LIMIT_EXCEEDED, retry_after 존재 |
| 오류 봉투 형태 | — | 모든 4xx/5xx 본문에 code, message, timestamp, request_id 존재 |
마지막 행은 별도의 벡터를 둘 가치가 있다. 구현이 표준에서 벗어나는 곳은 대개 오류 경로이기 때문이다. 프레임워크의 기본 404 페이지나 리버스 프록시의 502 본문이 WIA 봉투 형식을 따르는 경우는 드물다. 멀티테넌트 배포라면 표 7-2의 테넌트 간 부정 벡터를 추가한다.
행동 요건은 문서 검증으로 확인할 수 없다. 3단계 벡터는 시나리오다. 기록되거나 합성된 센서 스트림을 자율주행 스택에 입력하고, 그 결과로 나온 명령과 텔레메트리에 대해 단언한다. 예를 들어 "LiDAR 스트림이 5초 동안 끊기면 → 지령 속도 상한이 50% 낮아진다", "GPS 측위가 30초 동안 없으면 → position.source가 gps에서 벗어난다"와 같다. 이에 대응하는 부정 벡터는 대응이 너무 일찍 일어나지 않음을 확인한다. LiDAR가 1초 끊겼다고 속도를 절반으로 줄여서는 안 된다. 작은 끊김마다 성능을 떨어뜨리는 로봇 역시 사양을 벗어난 것이기 때문이다. 4단계 벡터도 메시지 수준에서 비슷하다. fleet/robots/{robot_id}/state에 중복 전달된 QoS 1 메시지는 소비자가 중복 제거해야 하고(제6장), 배터리가 18%인 로봇에게는 할당 규칙이 제외하는 작업이 제안되어서는 안 된다.
다음 Python 실행기는 문서형 벡터(1단계와 2단계의 봉투 검사)를 처리한다. jsonschema 라이브러리의 referencing 레지스트리를 사용해 $ref를 로컬 스키마 사본으로 해석하고, 형식 단언을 켠다.
# tools/run_vectors.py — 설치: pip install "jsonschema[format-nongpl]"
import json, pathlib, sys
from jsonschema import Draft202012Validator
from referencing import Registry, Resource
SCHEMAS = pathlib.Path("schemas") # 로컬 사본(자체 $defs 포함)
VECTORS = pathlib.Path("tests/phase-vectors")
def load_registry():
resources = []
for p in SCHEMAS.glob("*.json"):
doc = json.loads(p.read_text())
resources.append((doc["$id"], Resource.from_contents(doc)))
return Registry().with_resources(resources)
def run():
registry, rows = load_registry(), []
for vf in sorted(VECTORS.rglob("*.json")):
v = json.loads(vf.read_text())
if v.get("skip"):
rows.append({"id": v["vector_id"], "result": "skipped",
"justification": v.get("justification")})
continue
schema = registry.contents(v["schema"])
validator = Draft202012Validator(
schema, registry=registry,
format_checker=Draft202012Validator.FORMAT_CHECKER)
errors = list(validator.iter_errors(v["input"]))
ok = (not errors) == v["expect"]["valid"]
if ok and errors and "error_path" in v["expect"]:
ok = any(e.json_path.replace("$", "", 1).replace(".", "/")
== v["expect"]["error_path"] for e in errors)
rows.append({"id": v["vector_id"], "result": "pass" if ok else "fail"})
json.dump(rows, sys.stdout, indent=2)
bad = [r for r in rows if r["result"] == "fail"
or (r["result"] == "skipped" and not r.get("justification"))]
sys.exit(1 if bad else 0)
if __name__ == "__main__":
run()
경로 비교는 일부러 단순하게 만들었으며 /battery_level처럼 평면적인 필드 이름에서 동작한다. 벡터가 배열 인덱스를 다뤄야 한다면 확장한다. 종료 규칙에 주목하라. 부속서 G는 정당한 사유를 기록하지 않고 건너뛴 시험을 실패로 취급한다고 명시하며, 이 실행기는 정확히 그 규칙을 강제한다. 사유가 기록된 건너뛰기도 매트릭스에 보고되므로, 감사인이 그 사유의 타당성을 판단할 수 있다.
부속서 G는 적합성 패키지를 네 가지를 담은 tarball로 설명한다. 소프트웨어 자재 명세서(SBOM, CycloneDX 1.5 또는 SPDX 2.3), OpenAPI 문서, 시험 벡터 매트릭스, 서명된 매니페스트다. 실무에서는 실제로 검증에 사용한 스키마(직접 채운 $defs 포함)와, Verified·Anchored 등급의 경우 행동 시험 기록을 추가하는 것이 좋다. 찾기 쉬운 배치는 다음과 같다.
evidence-2026Q3/
├── manifest.json # 빌드의 신원과 주장
├── manifest.sigstore.json # Sigstore 번들: 서명, 인증서, Rekor 항목
├── sbom.cdx.json # CycloneDX 1.5 (또는 sbom.spdx.json, SPDX 2.3)
├── openapi.yaml # 실제로 제공하는 2단계 인터페이스
├── schemas/ # 1단계 스키마 + 자체 $defs
├── vector-matrix.json # 실행기 출력(건너뛴 항목 포함)
├── behaviour/ # 3단계 시나리오 결과와 현장 로그
└── attestations/ # in-toto 진술(빌드, 시험)
SBOM은 "이 빌드 안에 무엇이 들어 있는가?"라는 질문에 답한다. 배송 로봇이라면 로봇 탑재 이미지(ROS 2 패키지, 드라이버, 인지 모델)와 클라우드 서비스가 모두 해당하며, 보통 둘은 별도의 빌드이므로 SBOM도 따로 만든다. SBOM은 개발자 워크스테이션이 아니라 실제로 출하하는 산출물에서 생성해야 매니페스트의 빌드 다이제스트와 일치한다. 예를 들어 Syft를 쓰면 syft <image> -o cyclonedx-json=sbom.cdx.json으로 CycloneDX JSON 문서를 만들 수 있다. specVersion 필드가 주장하는 버전(부속서의 1.5)과 일치하는지 확인한다.
부속서 G는 DSSE 봉투와 Rekor 투명성 로그 항목을 사용하는 Sigstore 서명을 지정하여, 소비자가 "사설 CA를 신뢰하지 않고도" 출처를 검증할 수 있게 한다. 세 요소의 역할은 서로 다르다. DSSE(Dead Simple Signing Envelope)는 서명 대상 페이로드를 그 유형과 함께 감싸므로, 매니페스트에 대한 서명을 다른 대상에 대한 서명으로 재사용할 수 없다. Sigstore의 키리스(keyless) 방식은 장기 개인 키 대신 워크로드 신원(예: CI 워크플로)에 발급된 단기 인증서에 서명을 묶는다. Rekor는 서명 사건을 추가 전용 공개 로그에 기록하며, 이것이 Anchored의 "지속적"이라는 말에 실체를 부여한다. 누구든 3분기 패키지가 서명되었다는 사실과 그 시점을 확인할 수 있다.
cosign 도구를 사용한다면, 매니페스트를 in-toto 증명(attestation)으로 증거 tarball에 붙이는 방식이 하나의 실용적 패턴이다(cosign v2 계열 명령 기준 예시이며, 설치한 버전의 플래그를 확인할 것).
# CI에서, cosign이 사용할 수 있는 OIDC 신원이 있는 상태
tar -czf evidence-2026Q3.tar.gz evidence-2026Q3/
cosign attest-blob --yes \
--predicate evidence-2026Q3/manifest.json \
--type https://wiastandards.com/attestations/delivery-robot/conformance/v1 \
--bundle evidence-2026Q3.sigstore.json \
evidence-2026Q3.tar.gz
# 소비자 측
cosign verify-blob-attestation \
--bundle evidence-2026Q3.sigstore.json \
--type https://wiastandards.com/attestations/delivery-robot/conformance/v1 \
--certificate-identity-regexp 'https://github.com/example-org/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
evidence-2026Q3.tar.gz
위의 술어 유형(predicate type) URI는 저자가 정한 것이며 표준은 이를 정의하지 않는다. 중요한 것은 이 값이 안정적이고 공개되어 있어 검증자가 이를 요구할 수 있다는 점이다. air-gapped 프로필에서는 번들을 tarball 옆에 함께 둔다. 검증자가 Sigstore 신뢰 루트의 사본을 별도 경로로 받아 두었다면, Sigstore 번들에는 오프라인 검증에 필요한 자료가 들어 있다.
CLI는 참조 프레임워크로 in-toto Attestation Framework를 나열한다. 적합성 증명 외에도 두 가지 진술이 유용하다. 이미지 다이제스트를 소스 커밋에 연결하는 빌드 출처 진술과, 벡터 매트릭스를 같은 다이제스트에 연결하는 시험 진술이다. 둘을 함께 두면 소비자는 시험한 빌드와 출하한 빌드가 같은 것인지 확인할 수 있다. 이것은 감사인이 가장 자주 손으로 답해야 하는 질문이다.
부속서 G는 소스가 바뀌지 않았더라도 분기마다 패키지를 다시 공개하도록 요구한다. 새 컴파일러, 갱신된 기반 이미지, 패치된 의존성 같은 환경 변화를 소비자가 알아챌 수 있게 하기 위해서다. 다시 빌드하고, SBOM을 재생성하고, 벡터를 재실행하고, 다시 서명한다. 소스 커밋은 같은데 새 SBOM이 이전 것과 다르다면, 그 차이가 바로 분기 규칙이 드러내려는 변화다. 숨기지 말고 매니페스트 메모에 기록한다.
증거 패키지의 보존은 텔레메트리 보존과 별개의 문제다. 4단계는 개인정보 보호 조치로 "오래된 텔레메트리 자동 삭제(예: 90일)"를 제시하는 반면, 증거 패키지와 감사 기록은 감사인·인증기관·규제기관이 요구하는 기간 동안 보존한다. 부속서 E는 7년이라는 수치를 들며 그 근거를 ISO/IEC 17065 감사 기대치에 돌리지만, 그 연결의 출처는 제시하지 않는다. 보존 기간은 실제로 적용되는 요건에서 도출하고, 텔레메트리 삭제가 증거 삭제로 이어지지 않도록 두 시계를 분리한다.
저장소에는 네 가지 명령을 가진 Bash 스크립트 cli/delivery-robot.sh(버전 1.0.0)가 포함되어 있다. 전형적인 세션은 다음과 같다.
$ ./cli/delivery-robot.sh info
WIA delivery-robot
Version: 1.0.0
Conformance tiers (see README.md): Surface / Verified / Anchored.
Conformance package follows CycloneDX 1.5 + Sigstore (DSSE + Rekor).
$ ./cli/delivery-robot.sh profiles
Supported conformance profiles for delivery-robot:
- single-tenant one runtime per organization
- multi-tenant shared runtime, header-based isolation
- federated peer-to-peer mesh of runtimes
- air-gapped batch evidence package, no live connectivity
...
$ ./cli/delivery-robot.sh manifest-template > manifest.json
$ ./cli/delivery-robot.sh validate manifest.json
OK: manifest.json declares standardSlug=delivery-robot
$ ./cli/delivery-robot.sh validate robot-state.json
standardSlug mismatch: got '', expected 'delivery-robot'
이 장에서 가장 교훈적인 것은 마지막 두 줄이다. 완벽하게 유효한 RobotState 페이로드는 validate에서 실패하고, 자리표시자 값으로 가득한 매니페스트는 통과한다. validate가 단 한 가지, 즉 파일의 standardSlug가 delivery-robot인지만 검사하기 때문이다. 이것은 매니페스트의 기본 점검일 뿐 페이로드 검증기가 아니며, 1–4단계 적합성에 대해 아무것도 증명하지 않는다.
manifest-template이 출력하는 템플릿에는 최상위 필드가 여섯 개 있다. 가상의 릴리스로 채우면 다음과 같다(모든 값은 예시).
{
"documentId": "wia-delivery-robot-0007",
"standardSlug": "delivery-robot",
"version": "1.0.0",
"implementation": {
"name": "example-sidewalk-stack",
"build": "sha256:3f5c0d…(image digest)",
"sbom": "https://evidence.example.com/2026Q3/sbom.cdx.json"
},
"profile": { "named": "multi-tenant", "version": 1 },
"issuedAt": "2026-09-30T00:00:00Z"
}
| 필드 | 의미 | 실무 메모 |
|---|---|---|
documentId | 이 매니페스트의 식별자 | 템플릿 값 wia-delivery-robot-XXXX는 반드시 교체. 공개 건마다 고유하게 유지 |
standardSlug | 어느 WIA 표준인지 | 항상 delivery-robot. CLI가 검사하는 유일한 필드 |
version | 주장하는 표준 버전 | 제품 버전이 아니라 표준의 버전 |
implementation.name | 제품 또는 서비스 이름 | 템플릿 값 REPLACE_ME |
implementation.build | Git SHA 또는 이미지 다이제스트 | 이미지 다이제스트를 권장. SBOM과 서명이 대상으로 삼는 것이 그것이다 |
implementation.sbom | SBOM의 위치 | 패키지 내부 파일이나 지속적으로 유지되는 사본을 가리켜야 한다 |
profile | 명명된 배포 프로필과 그 버전 | 7.2절의 네 프로필 중 하나 |
issuedAt | 발행 시각 | 템플릿에 고정 날짜가 박혀 있으므로 반드시 덮어쓴다 |
매니페스트에 없는 것에 주목하라. 등급, 대상 PHASE 목록, 벡터 매트릭스를 가리키는 포인터가 없다. 템플릿이 이 필드들을 갖추기 전까지는, 장래의 템플릿이 다르게 정의할 수도 있는 새 최상위 키를 만들어 내기보다 이름공간이 분명한 확장 객체 아래에 둔다(예: "x-wia": { "tier": "anchored", "phases": [1,2,3,4], "vectorMatrix": "vector-matrix.json" }).
validate의 좁은 범위 외에도, 포크에서 고쳐 둘 만한 구현상의 세부 사항이 두 가지 있다. jq가 없을 때 쓰이는 Python 대체 경로는 파일 이름을 Python 소스 코드 안에 끼워 넣기 때문에, 경로에 따옴표가 들어 있으면 동작하지 않는다. 경로는 인자로 넘겨야 한다. 또 스크립트는 서명을 전혀 검증하지 않으므로 서명된 패키지와 서명되지 않은 패키지를 구별하지 못한다. 최소한의 확장으로, 벡터 실행기의 검증기에 위임하는 validate-payload 명령을 추가할 수 있다.
validate_payload() {
local schema_id="$1" file="$2"
python3 - "$schema_id" "$file" <<'PY'
import json, sys
sys.path.insert(0, "tools")
from run_vectors import load_registry
from jsonschema import Draft202012Validator
schema_id, path = sys.argv[1], sys.argv[2]
reg = load_registry()
v = Draft202012Validator(reg.contents(schema_id), registry=reg,
format_checker=Draft202012Validator.FORMAT_CHECKER)
errs = sorted(v.iter_errors(json.load(open(path))), key=lambda e: e.json_path)
for e in errs:
print(f"{e.json_path}: {e.message}")
sys.exit(1 if errs else 0)
PY
}
# case 문에 추가:
# validate-payload) validate_payload "$2" "$3" ;;
이렇게 하면 validate-payload https://wiastandards.com/schemas/delivery-robot/v1/robot-state.json robot-state.json이 문서 하나에 대해 실질적인 Surface 등급 검사를 수행하며, CI에서도 같은 코드 경로가 실행된다.
3단계 §8은 성능 목표를 나열하고, §6은 비상 정지와 고장 모드의 시간 요건을 추가한다. 이 목표들은 말하기는 쉽지만 잘못 측정하기도 쉽다. 핵심 어려움은 각 목표가 시간 구간이나 비율에 관한 진술인데, 사양이 그 구간이 정확히 어디서 시작하는지, 분모가 무엇인지를 정의하지 않는다는 데 있다. 시험 계획에서 이 정의를 확정하고 증거 패키지에 적어 두어야 한다.
| 사양 목표 | 선언해야 할 시작/끝 또는 분모 | 계측 방법 |
|---|---|---|
| 위치 오차 0.5 m 미만, 시간의 95% | 기준 궤적 대비 수평 오차, 샘플 단위, 선언된 경로 전체 | 측량된 RTK 수신기나 측량 표지로 얻은 기준 궤적. 평균이 아니라 95번째 백분위수를 보고 |
| 장애물 감지율 99.9% 초과 | 감지된 장애물 ÷ 제시된 장애물, 각본화된 조우 단위 | 레이블이 붙은 대상과의 각본화된 조우. 아래 표본 크기 설명 참조 |
| 오탐률 5% 미만 | 감지 단위, 조우 단위, 킬로미터 단위 중 무엇인지 선언 | 레이블된 로그. 실제 장애물이 없었던 모든 정지를 검토 |
| 반응 시간 100 ms 미만, 감지부터 회피 행동까지 | 센서 촬영 타임스탬프 기준(최악) 또는 인지 출력 기준(최선) — 둘 다 보고 | 센서와 모터 제어기 간 공통 시계(부속서 E에 따라 NTPv4 또는 PTP) |
| 비상 정지 모터 차단 100 ms 미만 | 트리거(버튼 신호 에지, 로봇의 명령 수신, 인지 이벤트)부터 측정된 모터 전류 강하까지 | 소프트웨어 로그가 아니라 로직 분석기나 전류 프로브 같은 외부 측정 |
| 경로 이탈 10% 미만 | 실제 경로 길이 ÷ 기준 최적 길이 − 1 | "최적"을 계산한 기준 계획기와 지도를 선언 |
| 고장 모드 타이머(GPS 30초, LiDAR 5초, 통신 60초, 정체 30초, 배터리 15% 미만) | 고장 주입부터 텔레메트리에서 관찰되는 대응까지 | 시뮬레이션과 폐쇄 코스에서의 고장 주입. 조기 발동에 대한 부정 벡터 |
비상 정지에 왜 외부 계측이 필요한가? 소프트웨어 로그는 정지 명령이 발행되었음을 증명할 뿐, 모터로 가는 전류가 끊겼음을 증명하지 않는다. 안전과 관련된 시간 요건의 핵심은 물리적 효과이며, 수십 밀리초 늦게 동작하는 릴레이나 모터 드라이버는 애플리케이션 로그에 드러나지 않는다.
간단한 운동학만으로도 이 두 수치를 함께 시험해야 하는 이유가 드러난다. 예시로 1.5 m/s로 주행하는 로봇을 생각해 보자. 모터가 차단되기까지의 100 ms 동안 로봇은 0.15 m를 이동한다. 이어서 2 m/s²로 감속한다고 가정하면 제동 거리는 v²/(2a) = 1.5² / 4 ≈ 0.56 m다. 전체 정지 거리는 약 0.71 m로, §6.1의 장애물 트리거 거리 0.3 m의 두 배를 넘는다. 여기서 얻는 결론은 사양이 틀렸다는 것이 아니라, 0.3 m 트리거가 최후의 문턱값이라는 것이다. 제5장의 지역 경로 계획기와 속도 제한은 비상 정지가 유일한 방어선이 되는 일이 드물도록 로봇을 충분히 느리게, 충분히 멀리 유지해야 한다. 따라서 좋은 3단계 시나리오 벡터는 차단 지연만이 아니라 로봇의 최대 허용 속도에서의 정지 거리를 측정한다.
비율 목표에는 그에 걸맞은 표본 크기가 필요하다. 널리 쓰이는 통계적 경험칙인 "3의 규칙(rule of three)"에 따르면, 독립 시행 n회에서 어떤 사건이 한 번도 관찰되지 않았을 때 그 확률의 95% 신뢰 상한은 약 3/n이다. 놓침 없이 "놓침률 0.1% 미만"(감지율 99.9% 초과)을 뒷받침하려면 대략 3,000회의 독립 조우가 필요하며, 한 번이라도 놓치면 더 많이 필요하다. 시험 트랙에서 20번 통과했다는 것은 99.9% 목표에 대해 거의 아무것도 말해 주지 않는다. 시행 횟수와 신뢰 상한을 증거 패키지에 적고, 조우가 실제로 독립적이도록 조명·날씨·장애물 종류·접근 각도를 다양하게 한다.
건전한 진행은 세 단계로 이루어진다. 시뮬레이션은 물리적으로 재현하기에 위험하거나 비현실적인 고장 주입을 포함해 3단계 시나리오 벡터를 대규모로 실행한다. 초기에 수천 회의 조우에 도달하는 사실상 유일한 방법이다. 폐쇄 코스 시험은 실제 센서, 실제 제동, 외부 시간 계측 장비로 대표적인 부분 집합을 반복하고, 시뮬레이션 결과를 어디까지 신뢰할 수 있는지 보정한다. 이어서 적용되는 지역 승인 아래 목표 환경에서 시범 운영을 하면, 아래 운영자 KPI를 뒷받침하는 현장 로그가 나온다.
표준과 함께 공개된 시뮬레이터에는 시나리오·센서 구성 선택기, "안전 테스트 실행" 버튼, 규정 준수 체크리스트를 갖춘 "안전 및 테스트" 패널이 있다. 이것은 시험 보고서가 어떤 모습일지 보여 주는 시연이다. 출력되는 결과는 실행할 때마다 같은 시간 값과 "passed": true 필드를 담은 고정 객체이며, 체크리스트 항목(예: GPS 정확도 수치나 방진·방수 등급)은 PHASE 문서의 요건이 아니다. 이해관계자에게 개념을 설명하는 데 쓰되, 그 출력을 증거 패키지에 넣어서는 안 된다.
4단계 §6.1은 목표값이 붙은 플릿 KPI를 나열한다. 이 지표들은 구현이 적합한지가 아니라 서비스가 얼마나 잘 운영되는지를 나타내므로, 고객과 합의하는 운영자 목표로 다루는 것이 가장 좋다.
| KPI | 사양 목표 | 사양의 정의 | 측정 메모 |
|---|---|---|---|
| 배송 성공률 | 99% 초과 | 배송 완료 ÷ 전체 배송 | 고객 취소와 반송을 어떻게 셀지 선언 |
| 평균 배송 시간 | 15분 미만 | 할당부터 배송까지의 평균 | 분포도 함께 보고. 평균은 긴 꼬리를 가린다 |
| 플릿 가동률 | 80% 초과 | 실제 배송 중인 시간 ÷ 전체 시간 | 충전·정비 시간이 분모에 들어가는지 선언 |
| 에너지 효율 | 5 km/kWh 초과 | 주행 거리 ÷ 소비 에너지 | 충전기 또는 배터리 관리 시스템 중 한 곳에서 일관되게 측정 |
| 고객 만족도 | 4.5/5 초과 | 설문 평점 | 기술적 측정이 아니므로 적합성 주장에서 제외 |
| 배송 1,000건당 사고 | 1 미만 | 안전 사건 ÷ 전체 배송 | 시범 운영 전에 아차 사고를 포함해 "안전 사건"을 정의 |
WIA ROB-010은 데이터와 프로토콜 표준이다. 적합성은 Anchored 등급이라 해도 "이 구현이 공통 언어를 올바르게 구사하고 그것을 지속적으로 증명하는가?"라는 질문에 답한다. "이 로봇은 사람들 사이에서 운행해도 안전한가?"나 "이 로봇은 이 보도에서 운행해도 되는가?"에는 답하지 않는다. 그것들은 각각의 기관과 절차를 가진 별개의 경로다.
| 경로 | 답하는 질문 | 일반적인 평가 주체 | 산출물 |
|---|---|---|---|
| WIA ROB-010 적합성 | 데이터, API, 행동 계약, 플릿 통합의 정확성 | 공급자(Surface), 독립 감사인(Verified), 공개 검증(Anchored) | 벡터 매트릭스, 서명된 증거 패키지, 감사 보고서 |
| 제품 안전 | 로봇이 수용 가능한 수준으로 안전한가? 예: 인증기관이 적용 가능하다고 판단하는 경우 ISO 13482에 따른 평가 | ISO/IEC 17065에 따라 인정받은 인증기관 | 제품 인증서 |
| 운행 허가 | 이곳에서 운행해도 되는가? 예: 한국의 실외이동로봇 운행안전인증, 도시의 보도 운행 허가 | 국가·지방 당국과 그 지정 기관 | 조건이 붙은 인증서·허가·면허 |
| 정보보호 | 조직의 정보보호 관리가 적절한가? | ISO/IEC 27001 인증기관 | 경영시스템 인증서 |
| 개인정보 | 카메라·위치 데이터가 적법하게 처리되는가? | 개인정보 보호법(PIPA), GDPR 등 적용 법률에 따른 내부·외부 평가 | 영향평가, 처리 기록 |
각 경로는 서로를 보강한다. 비상 정지 기능을 심사하는 안전 평가자는 외부 계측으로 얻은 표 7-5의 측정 결과를 반길 것이고, 시범 운영 신청을 검토하는 당국은 4단계의 사고 기록을 활용할 수 있다. 그러나 증거는 한 방향으로만 흐른다. WIA 표시를 안전 주장처럼, 또는 하나가 다른 하나를 함의하는 것처럼 보이도록 안전 주장 옆에 제시해서는 결코 안 된다.
부속서 E는 테넌트 데이터를 노출하지 않고 운영자를 비교할 수 있도록 "WIA 보고 프로필에 정의된 형식으로" p95 지연 시간, 오류율, 위반 시간(violation hours)을 담은 월간 오류 예산 요약을 권고한다. 그러나 그 보고 프로필은 v1.0.0과 함께 공개되지 않았다. 공개되기 전까지는 작더라도 명시적인 문서가 없는 것보다 낫다. 아래 형태는 저자의 제안이며 값은 자리표시자다.
{
"standardSlug": "delivery-robot",
"period": "2026-08",
"profile": "multi-tenant",
"slo": { "latency_p95_ms": 300, "error_rate": 0.001 },
"observed": {
"latency_p95_ms": 0,
"error_rate": 0.0,
"violation_hours": 0
},
"scope": ["/api/v1/robots", "/api/v1/packages", "/api/v1/routes", "/api/v1/fleet"],
"method": "server-side histograms, 1-minute buckets, 5xx and timeouts counted as errors"
}
첫 보고서 전에 "위반 시간"을 정의하고(예: SLO가 한 번이라도 깨진 매 정시 1시간 구간) 그 정의를 바꾸지 않는다. 정의가 바뀌면 월별 비교는 의미를 잃는다. 부속서의 개인정보 보호 취지를 지키기 위해 테넌트별 수치가 아니라 테넌트 전체의 집계를 보고한다.
아래 목록은 이 장의 내용을 한 번에 점검할 수 있도록 모은 것이다. 등급을 주장하기 전에 팀이 차례로 확인하면 된다.
CargoStatus, ActiveRoute, Error, Contact, Location)를 별도 스키마 파일로 채우고 공개한다.issuedAt까지 포함해 교체한다.변경이 있을 때마다, 그리고 분기마다 한 번씩 벡터를 실행하는 GitHub Actions 워크플로는 다음과 같이 구성할 수 있다.
name: wia-rob010-conformance
on:
push:
pull_request:
schedule:
- cron: "0 3 1 1,4,7,10 *" # 분기별 재점검(부속서 G)
jobs:
vectors:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install "jsonschema[format-nongpl]"
- run: python tools/run_vectors.py > vector-matrix.json
- uses: actions/upload-artifact@v4
if: always()
with: { name: vector-matrix, path: vector-matrix.json }
sign:
needs: vectors
if: github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions: { id-token: write, contents: read }
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3
- run: ./tools/build_evidence.sh # SBOM, tarball, cosign attest-blob(7.4절)
업로드 단계의 if: always()는 실패한 매트릭스도 공개되도록 보장한다. 부속서 G는 공급자가 부정적 결과를 긍정적 결과와 같은 충실도로 보고하도록 요구하는데, 빨간불일 때마다 사라지는 매트릭스는 그 규칙을 무력화한다. build_evidence.sh는 7.4절의 단계를 묶은 자체 스크립트를 가리킨다. 다음 장에서는 표준이 아직 다루지 않는 미션 매니페스트, 인수인계 이벤트, 사고 보고를 살펴보고, 여기서 쌓은 적합성 체계를 깨뜨리지 않고 표준을 확장하는 방법을 다룬다.
← 제6장: 4단계 플릿 통합 — 텔레메트리·배차·충전·OTA | 인덱스로 돌아가기 | 제8장: 확장과 미래 — 미션 매니페스트·인수인계·사고 보고 그리고 로드맵 →