{
  "openapi": "3.0.3",
  "info": {
    "title": "SAC — Infra Controller / Gateway API",
    "version": "0.1.1",
    "description": "SAC(Site Access Control)의 HTTP API 레퍼런스.\n\nSAC = **Infra Controller + Infra Gateway** 계층으로, handover zone의 안전 판단·게이트/전광판 command·device fact 정규화를 담당한다. 업무(job/slot/Entry) 상태기계는 YMS/PMS 소유이며 SAC 범위 밖이다.\n\n## 데이터 경로\n- **HSS → Infra Controller (직접)**: 카메라 기반 사람/도어/정렬/높이/health fact.\n- **LiDAR / ICS → Infra Gateway → Infra Controller**: LiDAR 높이·ICS e-stop/lamp를 정규화 후 전달.\n- **Public Gateway(pg/1.0) ↔ SAC**: PMS/YMS 업무 context in / 결과 out. pg/1.0은 `PMS REST → YMS REST / FMS Redis / Infra REST|MQTT` 얇은 변환기.\n\n## Public GW ↔ SAC 채널 계약 (transport 무관, 의미는 SAC 소유)\n| channel | 방향 | 내용 |\n|---|---|---|\n| `pg/v1/{site}/{zone}/vehicle-events` | PG→SAC | LPR·gate·carry 이벤트 |\n| `pg/v1/{site}/{zone}/vehicle-context` | PG→SAC | 차량 제원 |\n| `pg/v1/{site}/{zone}/job-context` | PG→SAC | job_type(IN/OUT)·carry_phase |\n| `pg/v1/{site}/{zone}/operator-actions` | PG→SAC | 키오스크·운행가능·시스템 e-stop/정상화 |\n| `pg/v1/{site}/{zone}/safety-overrides` | PG→SAC | 권한 검증된 force-pass 활성화/취소 |\n| `infra/v1/{site}/{zone}/controller-status` | SAC→PG | 상태/force-pass lifecycle ACK |\n\n아래 경로는 **mock dashboard 실험 API**로, 위 채널 계약을 로컬에서 구동/관측하는 창구다.\n\n## 화면\n| 경로 | 화면 |\n|---|---|\n| `/` | 목업 조작 (차량 상태 조작판 · 시퀀스 · 물리 장치) |\n| `/?view=routes` | 메시지 로그 (SAC↔HSS/PGW/Infra GW 주고받은 것) |\n| `/api/docs` · `/api/redoc` | API 문서 |\n| `/error` | 오류 화면 (모듈별 원인·담당·조치) |\n| `/theother` | 상위 업무 연계 시험 (PGW/YMS/RMS 대행 · 받은편지함) |\n\n## 입차 한 건의 순서\n1. `POST /api/pg-lpr-report` — SAC→PGW `LPR_RECOGNIZED` 보고. `/theother` 받은편지함에 뜬다.\n2. `POST /api/pg-reservation-confirm` — PGW→SAC 예약 확인 + **차량 제원** + job(IN).\n   제원은 이후 HSS 로 나가는 `START_MONITORING` 의 `params.vehicle_info` 로 함께 간다.\n3. `POST /api/hss-check {\"check\":\"height\"}` — SAC→HSS 높이 검사.\n   통과하면 사전허용을 PGW 로 보고하고 **그 자리에서 입차 차단기를 연다**(예약이 확인된 차량에 한해).\n   초과하면 아무것도 열지 않고 전광판이 회차 안내로 바뀐다 — 입차 거부이며 비상정지가 아니다.\n4. `POST /api/vehicle-state {\"presence\":\"in\"}` → `{\"alignment\":\"good\"}` — 차량이 들어와 정렬된다.\n5. `POST /api/hss-check {\"check\":\"zone_ready\"}` — SAC→HSS 존 준비 검사. 거부는 상위에 보고하지 않는다.\n6. 상위 응답(운행가능·로봇 호출) → 상차 → `{\"presence\":\"out\"}` → `ZONE_RELEASED`.\n\n\n## 존 분리\n드랍존과 픽업존은 서로 다른 위치의 존이고, HSS 도 존별 토픽으로 보낸다. Infra Controller 도\n존별로 받고 존별로 보낸다 — 한 사이트에 프로세스 하나, **존은 ID 로 구분**한다.\n\n* 쓰기: 본문(또는 쿼리)의 `zone` 이 대상 존을 정한다. 없으면 기본 존.\n* 읽기: `GET /api/state?zone=pickupzone`\n* `POST /api/select-zone` 은 표시용 기본 존만 바꾼다 — 다른 존의 상태는 유지된다.\n* `zone` / `zone_id` / `zone_name` 은 **라우팅 키**이며 상태 필드가 아니다.\n"
  },
  "servers": [
    {
      "url": "/",
      "description": "Infra Controller dashboard (기본 18020)"
    }
  ],
  "tags": [
    {
      "name": "Dashboard / State",
      "description": "스냅샷 조회·리셋·zone 선택"
    },
    {
      "name": "Sequence & Events",
      "description": "입·출차 시퀀스 구동용 controller/Public GW 이벤트"
    },
    {
      "name": "HSS → Controller",
      "description": "HSS fact 직접 주입 (사람/도어/정렬/높이/health)"
    },
    {
      "name": "Infra Gateway (LiDAR/ICS)",
      "description": "LiDAR 높이·ICS e-stop/lamp — gateway 경유"
    },
    {
      "name": "Public Gateway (pg/1.0)",
      "description": "PMS/YMS 업무 context 연동"
    },
    {
      "name": "System / Device",
      "description": "시스템 비상·정상화·장치 command"
    },
    {
      "name": "public-gateway-inbound",
      "description": "PGW 가 SAC 을 호출하는 면 (미배선 — pgw 에 호출부 없음)"
    },
    {
      "name": "public-gateway-outbound",
      "description": "SAC 이 PGW 를 호출하는 면 (동작 중)"
    },
    {
      "name": "Vehicle State (mock)",
      "description": "차량의 물리 상태를 바꾸면 HSS 가 그 상태로 보고한다"
    },
    {
      "name": "Flow Steps",
      "description": "입차 흐름을 잇는 단계 — 예약 확인 · 높이/존준비 검사 · 사전허용"
    },
    {
      "name": "Upstream Links",
      "description": "PGW/YMS/RMS 연결 상태 (끊기면 진행이 막힌다)"
    }
  ],
  "paths": {
    "/api/state": {
      "get": {
        "tags": [
          "Dashboard / State"
        ],
        "summary": "현재 스냅샷 조회",
        "description": "controller 상태, 게이트 정책, 안전 blocks, 장치 상태, 명령/결과 로그, 메시지 route를 반환.",
        "responses": {
          "200": {
            "description": "현재 상태 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "zone": {
                      "type": "string",
                      "example": "drop-zone"
                    },
                    "controller_state": {
                      "type": "string",
                      "example": "ENTRY_PREAUTHORIZED"
                    },
                    "handover_state": {
                      "type": "string",
                      "example": "HOLD"
                    },
                    "expected_next_event": {
                      "type": "string",
                      "example": "PUBLIC_GATE_IN"
                    },
                    "active_safety_blocks": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "hss_facts": {
                      "type": "object"
                    },
                    "devices": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "site_profile": {
                      "type": "object",
                      "properties": {
                        "active_zone": {
                          "type": "string"
                        },
                        "available_zones": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/reset": {
      "post": {
        "tags": [
          "Dashboard / State"
        ],
        "summary": "상태 초기화",
        "responses": {
          "200": {
            "description": "초기화된 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/select-zone": {
      "post": {
        "tags": [
          "Dashboard / State"
        ],
        "summary": "판단 zone 선택 (dropzone/pickupzone/parkingzone)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "zone"
                ],
                "properties": {
                  "zone": {
                    "type": "string",
                    "example": "pickupzone"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "선택 결과",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/event": {
      "post": {
        "tags": [
          "Sequence & Events"
        ],
        "summary": "controller 이벤트 직접 주입",
        "description": "`public_gateway_*`, `vehicle_disappeared`, `hss_*` 등 controller InfraEvent를 직접 emit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event_type"
                ],
                "properties": {
                  "event_type": {
                    "type": "string",
                    "example": "public_gateway_carry_requested"
                  },
                  "payload": {
                    "type": "object",
                    "example": {
                      "vehicle_number": "12가3456",
                      "job_type": "IN"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "emit 결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/external-event": {
      "post": {
        "tags": [
          "Sequence & Events"
        ],
        "summary": "Public GW 외부 이벤트 (LPR/GATE_IN/CARRY_GATE_IN…)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event"
                ],
                "properties": {
                  "event": {
                    "type": "string",
                    "example": "CARRY_GATE_IN"
                  },
                  "vehicle_number": {
                    "type": "string",
                    "example": "12가3456"
                  },
                  "reservation_confirmed": {
                    "type": "boolean",
                    "example": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/public-gateway/event": {
      "post": {
        "tags": [
          "Sequence & Events"
        ],
        "summary": "Public GW robot 상태 이벤트",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "example": "robot_available"
                  },
                  "available": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/hss-gateway/event": {
      "post": {
        "tags": [
          "HSS → Controller"
        ],
        "summary": "HSS fact 주입 (Controller 직접)",
        "description": "height_pass/exceeded, zone_ready_ok/person/door_open, emergency_person_entered/cleared, vision_failure, health_online/offline 등.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event"
                ],
                "properties": {
                  "event": {
                    "type": "string",
                    "example": "zone_ready_ok"
                  },
                  "zone_name": {
                    "type": "string",
                    "example": "drop-zone"
                  },
                  "vehicle_number": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/lidar-height/event": {
      "post": {
        "tags": [
          "Infra Gateway (LiDAR/ICS)"
        ],
        "summary": "LiDAR 높이 fact (Infra Gateway 경유)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event"
                ],
                "properties": {
                  "event": {
                    "type": "string",
                    "example": "height_pass"
                  },
                  "zone_name": {
                    "type": "string",
                    "example": "drop-zone"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/ics-gateway/event": {
      "post": {
        "tags": [
          "Infra Gateway (LiDAR/ICS)"
        ],
        "summary": "ICS e-stop/lamp fact (Infra Gateway 경유)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event"
                ],
                "properties": {
                  "event": {
                    "type": "string",
                    "example": "estop_pressed"
                  },
                  "device_id": {
                    "type": "string",
                    "example": "dropzone_exit_estop"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/system-estop": {
      "post": {
        "tags": [
          "System / Device"
        ],
        "summary": "관리자 시스템 e-stop",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pressed": {
                    "type": "boolean",
                    "example": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/system-normalize": {
      "post": {
        "tags": [
          "System / Device"
        ],
        "summary": "시스템 정상화 (원인 모두 해소 시 latch 해제)",
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/device-command": {
      "post": {
        "tags": [
          "System / Device"
        ],
        "summary": "장치 command (차단기/경광등/전광판/estop)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "device_id": {
                    "type": "string",
                    "example": "dropzone_entrance_barrier"
                  },
                  "action": {
                    "type": "string",
                    "example": "open"
                  },
                  "params": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "결과 + 스냅샷",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/api/pg-vehicle-info": {
      "post": {
        "tags": [
          "Public Gateway (pg/1.0)"
        ],
        "summary": "gate-in 한 차량 정보 호출 (pg/1.0 경유)",
        "description": "활성 차량번호(또는 body의 vehicle_number)로 pg/1.0에 `carry-gate-in`을 호출해 배정 job/도착 슬롯/status를 받고, `GET /robot-parkings` 인벤토리에서 해당 차량을 매칭한다. base URL/토큰은 env(`PUBLIC_GATEWAY_BASE_URL`, `PUBLIC_GATEWAY_AUTH_TOKEN`)로 설정. dev는 무인증.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_number": {
                    "type": "string",
                    "example": "12가3456"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "pg/1.0 조회 결과",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "pg_vehicle_info": {
                      "type": "object",
                      "properties": {
                        "vehicle_number": {
                          "type": "string"
                        },
                        "carry_gate_in": {
                          "type": "object",
                          "description": "pg carry-gate-in 응답 (acsJobId/arrivalParkingSpace/status …)"
                        },
                        "parking_match": {
                          "type": "object",
                          "description": "robot-parkings 인벤토리에서 매칭된 차량 행"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{op}": {
      "post": {
        "tags": [
          "public-gateway-inbound"
        ],
        "summary": "PGW → SAC 수신구 (op 이름 무관)",
        "description": "Public Gateway 의 어댑터 규약(`POST {INFRA_BASE_URL}/api/v1/{op}`)에 맞춘 수신구다.\n\npgw 의 `app/adapters/infra_rest.py` 가 이 형태로 호출한다. 아직 pgw 안에 호출부가 없어\nop 이름이 확정되지 않았으므로, **어떤 이름이든 200 으로 받아** 기록한다. 아는 이름이면\n해당 사건으로 변환하고, 모르는 이름이면 기록만 한다(`recognized: false`).\n\n이 설계 의도: PGW 배선 작업이 SAC 의 400 응답 때문에 막히지 않게 한다.",
        "parameters": [
          {
            "name": "op",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "연동 op 이름. 현재 인식하는 값: carry-gate-in, gate-in, gate-pass, carry-gate-out, lpr-recognized, car-enter",
            "example": "carry-gate-in"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "vehicle_number": "12가3456",
                "job_id": "job-1"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "op": "carry-gate-in",
                  "mapped_event": "CARRY_GATE_IN",
                  "recognized": true
                }
              }
            }
          }
        }
      }
    },
    "/robot-parkings/carry-gate-in": {
      "post": {
        "tags": [
          "public-gateway-inbound"
        ],
        "summary": "PGW → SAC 입차 지시 (pg.yaml 경로 그대로)",
        "description": "pg.yaml 의 경로명을 그대로 받는 별칭 수신구. PGW 가 전용 경로를 새로 만들지 않아도 되게 열어두었다.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "vehicle_number": "12가3456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/robot-parkings/carry-gate-out": {
      "post": {
        "tags": [
          "public-gateway-inbound"
        ],
        "summary": "PGW → SAC 출차 통과 (pg.yaml 경로 그대로)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "vehicle_number": "12가3456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/pg-lpr-report": {
      "post": {
        "tags": [
          "public-gateway-outbound"
        ],
        "summary": "LPR 인식 → 내부 주입 + PGW 보고",
        "description": "SAC 내부에 LPR_RECOGNIZED 를 주입하고, 동시에 PGW `POST /rms/drop-zone/event` 로 보고한다.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "example": {
                "vehicle_number": "12가3456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/vehicle-state": {
      "post": {
        "tags": [
          "Vehicle State (mock)"
        ],
        "summary": "차량 상태 변경 → HSS 응답 발행",
        "description": "차량의 물리 상태를 바꾸고, HSS 가 그 상태를 인지했다는 전제로 **HSS 가 실제로 쓰는 형식 그대로** 발행한다(`VEHICLE_ACCESS`, `VEHICLE_HEIGHT`+LiDAR, `REALTIME_ALIGNMENT`, `PERSON_ENTERED/CLEARED`), 그리고 마지막에 현재 상태 전체에 대한 안전 판정(`ZONE_READY` 또는 `MONITORING_RESULT`)을 다시 낸다. 통과 판정은 실 HSS 처럼 3회 발행한다.\n\n필드: `presence`(none|in|leaving|out), `height_mm`(300~6000), `alignment`(good|outlined|skewed|crossed), `door`(closed|open), `person`(clear|detected), `vehicle_number`. 보낸 필드만 바뀐다.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "presence": "in",
                "height_mm": 1600,
                "alignment": "good",
                "door": "closed",
                "person": "clear"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "값이 계약에 맞지 않음 (알 수 없는 필드·범위 초과)"
          },
          "403": {
            "description": "읽기 전용(INFRA_CONTROLLER_READ_ONLY=1) 배포에서는 쓰기 금지"
          }
        }
      }
    },
    "/api/hss-check": {
      "post": {
        "tags": [
          "Flow Steps"
        ],
        "summary": "SAC → HSS 검사 요청 (높이 / 존 준비)",
        "description": "SAC 이 `START_MONITORING` 을 HSS 로 보내고(차량번호와 제원 포함), 목업 HSS 가 현재 차량 상태로 답한다. `check=height` 는 `VEHICLE_HEIGHT` 관측과 판정 권한을 가진 현장 2D LiDAR 결과를 함께 내고, 통과하면 `ENTRY_PRE_AUTHORIZED` 를 PGW 로 보고한 뒤 입차 차단기를 연다. `check=zone_ready` 는 `checks:[ALIGNMENT,DOOR,PERSON]` 으로 `ZONE_READY` 를 받는다. 존 준비 거부는 상위에 보고하지 않는다.\n\n응답: `passed`, `request_id`, `published`(발행된 HSS payload), `pre_authorized`, `entry_allowed`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "check": "zone_ready"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "값이 계약에 맞지 않음 (알 수 없는 필드·범위 초과)"
          },
          "403": {
            "description": "읽기 전용(INFRA_CONTROLLER_READ_ONLY=1) 배포에서는 쓰기 금지"
          }
        }
      }
    },
    "/api/pg-reservation-confirm": {
      "post": {
        "tags": [
          "Flow Steps"
        ],
        "summary": "PGW → SAC 예약 확인 + 차량 제원",
        "description": "예약 확인, 작업(job IN) 지시, 차량 제원을 한 번에 넣는다. 제원은 Controller 의 차량 맥락에 저장되고 이후 HSS 로 나가는 `START_MONITORING` 의 `params.vehicle_info` 로 전달된다. 예약 확인이 곧 입차 승인이며, 실제 개방은 높이 검사 통과 후에 일어난다.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "example": {
                "vehicle_number": "12가3456",
                "specs": {
                  "height_mm": 1600,
                  "total_length_mm": 4800
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "값이 계약에 맞지 않음 (알 수 없는 필드·범위 초과)"
          },
          "403": {
            "description": "읽기 전용(INFRA_CONTROLLER_READ_ONLY=1) 배포에서는 쓰기 금지"
          }
        }
      }
    },
    "/api/pg-entry-pre-authorized": {
      "post": {
        "tags": [
          "Flow Steps"
        ],
        "summary": "SAC → PGW 입차 사전허용 보고 (재보고)",
        "description": "`POST /rms/drop-zone/event` 로 `ENTRY_PRE_AUTHORIZED` 를 보고한다. 정상 흐름에서는 `/api/hss-check {\"check\":\"height\"}` 가 통과할 때 자동으로 나가므로, 이 경로는 재보고용이다.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "example": {
                "vehicle_number": "12가3456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "값이 계약에 맞지 않음 (알 수 없는 필드·범위 초과)"
          },
          "403": {
            "description": "읽기 전용(INFRA_CONTROLLER_READ_ONLY=1) 배포에서는 쓰기 금지"
          }
        }
      }
    },
    "/api/upstream-link": {
      "post": {
        "tags": [
          "Upstream Links"
        ],
        "summary": "상위 시스템 연결 상태 토글",
        "description": "PGW/YMS/RMS 의 연결 상태를 만든다. 끊긴 동안에는 `UPSTREAM_DISCONNECTED:<system>` 으로 인계가 진행되지 않는다 — 작업 지시를 줄 주체가 없기 때문이다. 복구하면 해제된다. `/theother` 화면의 토글이 이 경로를 쓴다.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "system": "rms",
                "connected": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "값이 계약에 맞지 않음 (알 수 없는 필드·범위 초과)"
          },
          "403": {
            "description": "읽기 전용(INFRA_CONTROLLER_READ_ONLY=1) 배포에서는 쓰기 금지"
          }
        }
      }
    },
    "/api/device-health": {
      "post": {
        "tags": [
          "System / Device"
        ],
        "summary": "장치 heartbeat 주입 (연결/끊김)",
        "description": "장치나 HSS 의 heartbeat 를 만든다. `INFRA_GATEWAY_DEVICE_TIMEOUT_MS` 이상 끊기면 `DEVICE_DISCONNECTED:<id>` 로 차단하고 비상 래치를 건다. 복구 후 정상화로 해제한다. 연결 상태판(좌측 사이드바)의 연결/끊김 버튼이 이 경로를 쓴다.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "device_id": "dropzone_entrance_barrier",
                "alive": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "수신됨",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "값이 계약에 맞지 않음 (알 수 없는 필드·범위 초과)"
          },
          "403": {
            "description": "읽기 전용(INFRA_CONTROLLER_READ_ONLY=1) 배포에서는 쓰기 금지"
          }
        }
      }
    },
    "/api/errors": {
      "get": {
        "tags": [
          "Dashboard / State"
        ],
        "summary": "현재 문제 목록 (JSON)",
        "description": "`/error` 화면과 같은 내용. 모듈·담당·원인·조치가 함께 온다.",
        "responses": {
          "200": {
            "description": "문제 목록",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "OkSnapshot": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          },
          "snapshot": {
            "type": "object",
            "description": "GET /api/state와 동일 형태의 상태 스냅샷"
          }
        }
      }
    }
  }
}
